diff --git a/AGENTS.md b/AGENTS.md index b8915526..02e73dc0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,7 @@ Ahead-of-time build artifacts that live under `src/` - the shadow-root styleshee ## Conventions - RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin::` (matching the plugin's `@devframes/plugin-` package name). +- **No magic event names — use the centralized event maps.** Every event, broadcast, shared-state key, and channel name lives in one of two source-of-truth maps: `DEVFRAME_EVENTS` (`packages/devframe/src/events.ts`, re-exported from `devframe/constants`) for the core runtime, and `HUB_EVENTS` (`packages/hub/src/events.ts`, re-exported from `@devframes/hub/constants`) for the hub. Reference `DEVFRAME_EVENTS.*` / `HUB_EVENTS.*` at call sites (`.events.emit`/`.on`, `rpc.broadcast({ method })`, `sharedState.get(key)`, `defineHubRpcFunction({ name })`, `rpc.call`) instead of re-typing a string literal. The two maps and the [`docs/guide/events.md`](docs/guide/events.md) Events Reference are kept in lockstep: adding, renaming, or removing a name means editing the map **and** that page in the same change — every name in the maps appears in the tables, and vice versa. The only literals left are unavoidable type-position keys (the `EventEmitter<…>` maps in `types/*` and the `DevframeRpcClientFunctions`/`DevframeRpcServerFunctions` augmentations), which mirror the maps; a package that deliberately avoids a hub dependency (e.g. `@devframes/plugin-terminals`, which models the hub bridge structurally) keeps a local literal rather than importing `HUB_EVENTS`. - **Stay validator-neutral.** `devframe` and every `@devframes/*` package must not introduce a preferred schema validator dependency - no `valibot`, `zod`, `arktype`, etc. in their runtime `dependencies`. `args`/`returns`/flag schemas are typed against [Standard Schema](https://standardschema.dev/) (`@standard-schema/spec`, types-only); first-party code that needs to author a schema uses the built-in zero-dep `devframe/utils/simple-schema` builder (deliberately minimal - not a general validator). JSON-schema conversion uses each schema's own Standard JSON Schema converter (`~standard.jsonSchema`, implemented by e.g. zod 4) when present and degrades to a permissive object otherwise - no converter library and no vendor dependency is required. Docs, by contrast, should point *users* at a real validator for their own integrations - recommend **valibot** (lightest) or **zod** (worth reusing if they already pull it via the JSON-render or MCP integrations). - Shared state via `devframe/utils/shared-state`; keep values serializable. - Utility imports use the package-path form `devframe/utils/*`, never relative `../utils/*`. diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index c6c62964..7c3753df 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -50,6 +50,7 @@ function guideGroups(prefix: string) { { text: 'Serve a Hub Anywhere', link: `${prefix}/guide/hub-initiate` }, { text: 'Cross-Plugin Services', link: `${prefix}/guide/services` }, { text: 'Deep Linking', link: `${prefix}/guide/deep-linking` }, + { text: 'Events Reference', link: `${prefix}/guide/events` }, ], }, { diff --git a/docs/guide/events.md b/docs/guide/events.md new file mode 100644 index 00000000..d4b3f024 --- /dev/null +++ b/docs/guide/events.md @@ -0,0 +1,104 @@ +--- +outline: deep +--- + +# Events Reference + +Devframe carries change notifications across a few distinct channels. What separates them is **direction and reach**: an in-process event bus that never leaves the node process, server RPC methods a client calls, and server-pushed broadcasts and shared state a client reads. + +Two naming prefixes mark the wire surface: `hub:` for hub-layer server RPC (client → server actions), and `devframe:` for the client-facing devframe protocol (broadcasts, shared state, and streams pushed server → client). The internal event bus mirrors the same plural subsystem vocabulary (`docks`, `terminals`, `messages`, `commands`), so each internal event lines up with its wire counterpart — `docks:activate` fans out to `devframe:docks:activate`. + +Every name on this page has one home in code: the [`HUB_EVENTS`](https://github.com/devframes/devframe/blob/main/packages/hub/src/events.ts) map (`@devframes/hub/constants`) backs the hub tables, and the [`DEVFRAME_EVENTS`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/events.ts) map (`devframe/constants`) backs the core tables. Call sites reference `HUB_EVENTS.*` / `DEVFRAME_EVENTS.*` rather than re-typing a literal, and this page and those maps move together — changing one without the other is a bug. + +## Hub events + +### Internal node event bus + +Each subsystem host emits on `ctx..events`. These fire and are consumed **inside the same node process** — chiefly by `createHubContext`, which fans them out onto the wire. They never cross to the browser. + +| Event | Emitted by | Consumed by | Payload | +|---|---|---|---| +| `docks:entry:updated` | `DocksHost.register` / `update` | context → `devframe:docks` shared state | `DevframeDockUserEntry` | +| `docks:activate` | `DocksHost.activate()` | context → broadcast + `devframe:docks:active` | `DevframeDockActivation` | +| `terminals:session:updated` | `TerminalsHost` register / update / remove / status change | context → `devframe:terminals:updated`; terminals plugin | `DevframeTerminalSession` | +| `messages:added` / `messages:updated` / `messages:removed` / `messages:cleared` | `MessagesHost` mutations | context → `devframe:messages:updated`; messages plugin | entry / entry / id / — | +| `commands:registered` / `commands:unregistered` | `CommandsHost` register / update / unregister | context → `devframe:commands` shared state | entry / id | + +The `docks:entry:updated` and `terminals:session:updated` middle nouns (`entry`, `session`) name the specific record type; the messages and commands subsystems imply their record in the subsystem name, so they carry the verb directly. + +### Server RPC methods — client → server + +A connected client (any mounted iframe or panel, on its own RPC client) calls these; the hub node handles them. Carry the `hub:` prefix. + +| Method | Signature | Purpose | +|---|---|---| +| `hub:docks:activate` | `({ dockId, params? }) => void` | Ask the viewer to switch its active dock — see [Deep Linking](./deep-linking). | +| `hub:commands:execute` | `(id, ...args) => unknown` | Invoke a registered server command by id. | +| `hub:messages:add` | `(input) => DevframeMessageEntry` | Add a message to the feed (marked `from: 'browser'`). | +| `hub:messages:update` | `(id, patch) => DevframeMessageEntry \| undefined` | Patch a message by id. | +| `hub:messages:remove` | `(id) => void` | Remove a message by id. | +| `hub:messages:clear` | `() => void` | Remove every message. | +| `hub:terminals:write` | `(id, data) => void` | Send input to an interactive PTY session. | +| `hub:terminals:resize` | `(id, cols, rows) => void` | Resize an interactive PTY session. | +| `hub:terminals:terminate` | `(id) => void` | Kill a session's process, keeping it registered. | +| `hub:terminals:restart` | `(id) => void` | Re-run a session's command in place. | +| `hub:terminals:remove` | `(id) => void` | Kill a session's process and drop it from the registry. | + +### Broadcasts & shared state — server → client + +The server pushes these; a hub-aware client reads or subscribes. Carry the `devframe:` prefix. A UI subscribes to broadcasts via `rpc.client.register(...)`; the [client host](./client-context) registers the `devframe:docks:activate` handler for you. + +| Name | Kind | Carries | +|---|---|---| +| `devframe:docks:activate` | broadcast | Live "switch active dock" request — the client host calls its local `switchEntry`. | +| `devframe:terminals:updated` | broadcast | Terminal sessions changed; re-read terminal state. | +| `devframe:messages:updated` | broadcast | Message list changed; re-read message state. | +| `devframe:docks` | shared state | Projected dock entry list (`DevframeDockEntry[]`). | +| `devframe:docks:active` | shared state | Most recent `DevframeDockActivation`, so a dock that mounts in response still converges on it. | +| `devframe:commands` | shared state | Serializable command list, handlers stripped (`DevframeServerCommandEntry[]`). | +| `devframe:user-settings` | shared state | Persisted per-workspace hub settings (`DevframeDocksUserSettings`). | +| `devframe:terminals` | streaming channel | Live terminal output stream, keyed by session id. | + +The [`devframe:docks:active`](./shared-state) mirror pairs with the `devframe:docks:activate` broadcast: the broadcast reaches docks already on screen, while the mirror lets a dock that mounts *because* of the switch converge on the same request instead of missing it. + +## Core devframe events + +The core `devframe` runtime (below the hub) carries its own notification channels — the agent host's change events, the client connection lifecycle, and the server-pushed broadcasts that power shared state and streaming. These are backed by `DEVFRAME_EVENTS` (`devframe/constants`). + +This map covers notifications only. The request/response RPC endpoints of the shared-state, streaming, and auth-handshake protocols (`devframe:rpc:server-state:*`, `devframe:streaming:subscribe`, `anonymous:devframe:auth`, …) are defined at their handlers and typed in `types/rpc-augments.ts` — they aren't events. + +### Node host bus + +Emitted on `ctx.agent.events` as the agent-exposed tool/resource surface changes; protocol adapters (e.g. the MCP server) subscribe to re-publish their manifest. + +| Event | Emitted by | Payload | +|---|---|---| +| `agent:manifest:changed` | any tool/resource/provider change | — | +| `agent:tool:registered` / `agent:tool:unregistered` | `registerTool` / `unregisterTool` | `AgentTool` / id | +| `agent:resource:registered` / `agent:resource:unregistered` | `registerResource` / `unregisterResource` | `AgentResource` / id | + +### Client connection events + +Emitted on the RPC client's `rpc.events` emitter (`RpcClientEvents`) for a UI to track connection lifecycle and surface errors. + +| Event | Carries | +|---|---| +| `rpc:is-trusted:updated` | Trust gate flipped (`boolean`). | +| `rpc:error` | An RPC call rejected (`error`, `method`). | +| `connection:status` | Connection status changed (`status`, `previous`). | +| `connection:error` | A connection-level error (WebSocket errored, or trust refused). | + +### Broadcasts — server → client + +Pushed from the server to subscribed clients over the `devframe:` protocol. Wired by the framework's own hosts; not registered manually. + +| Name | Carries | +|---|---| +| `devframe:auth:revoked` | This connection's bearer token was revoked; the client drops to untrusted. | +| `devframe:rpc:client-state:updated` | Full shared-state snapshot for a key. | +| `devframe:rpc:client-state:patch` | Incremental shared-state patch for a key. | +| `devframe:streaming:chunk` | A streaming chunk for a subscribed channel/id. | +| `devframe:streaming:end` | A streaming terminator (optionally an error). | +| `devframe:streaming:upload-cancel` | Server-side cancel of an in-flight upload. | + +Plus one `postMessage` channel, `devframe:remote-assets-error`, that the remote-assets fallback page posts to `window.parent` so an embedding viewer can replace the bare 502 page with its own UI. diff --git a/docs/guide/hub.md b/docs/guide/hub.md index dc521284..9f04247a 100644 --- a/docs/guide/hub.md +++ b/docs/guide/hub.md @@ -275,7 +275,7 @@ A hub-aware UI doesn't import any hub classes; it reads three shared-state keys | `hub:commands:execute` RPC | `(id, ...args) => unknown` | Server-side command dispatch. | | `hub:docks:activate` RPC | `({ dockId, params? }) => void` | Switch the active dock from any client. | -Plus broadcast notifications (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`) that a UI can subscribe to via `rpc.client.register(...)`. The client host registers the `devframe:docks:activate` handler for you. +Plus broadcast notifications (`devframe:docks:activate`, `devframe:terminals:updated`, `devframe:messages:updated`) that a UI can subscribe to via `rpc.client.register(...)`. The client host registers the `devframe:docks:activate` handler for you. The [Events Reference](./events) tables every channel across all four subsystems. ## Running plugin code in the host page diff --git a/packages/devframe/src/adapters/mcp/build-server.ts b/packages/devframe/src/adapters/mcp/build-server.ts index 81548a2c..f0a3d04f 100644 --- a/packages/devframe/src/adapters/mcp/build-server.ts +++ b/packages/devframe/src/adapters/mcp/build-server.ts @@ -8,6 +8,7 @@ import { Server } from '@modelcontextprotocol/server' import { createHostContext } from 'devframe/node' import { toAgentToolName } from 'devframe/utils/agent-tool-name' import { join } from 'pathe' +import { DEVFRAME_EVENTS } from '../../events' import { diagnostics } from '../../node/diagnostics' import { formatMcpError, stringifyForMcp } from './stringify' import { argsToJsonSchema, returnToJsonSchema } from './to-json-schema' @@ -70,7 +71,7 @@ export function buildMcpServerFromContext( const notify = (method: string): void => { server.notification({ method }).catch(() => { /* ignore transport errors */ }) } - const offManifest = ctx.agent.events.on('agent:manifest:changed', () => { + const offManifest = ctx.agent.events.on(DEVFRAME_EVENTS.bus.agentManifestChanged, () => { notify('notifications/tools/list_changed') notify('notifications/resources/list_changed') }) diff --git a/packages/devframe/src/client/rpc-live.ts b/packages/devframe/src/client/rpc-live.ts index c3546ad3..74968292 100644 --- a/packages/devframe/src/client/rpc-live.ts +++ b/packages/devframe/src/client/rpc-live.ts @@ -3,6 +3,7 @@ import type { ConnectionMeta, DevframeRpcClientFunctions, DevframeRpcServerFunct import type { DevframeConnectionStatus } from './connection' import type { DevframeClientRpcHost, DevframeRpcClientMode, DevframeRpcClientOptions, RpcClientEvents } from './rpc' import { createRpcClient } from 'devframe/rpc/client' +import { DEVFRAME_EVENTS } from '../events' import { promiseWithResolver } from '../utils/promise' import { DevframeConnectionError } from './connection' @@ -65,7 +66,7 @@ export function createLiveRpcClientMode( return const previous = status status = next - events.emit('connection:status', next, previous) + events.emit(DEVFRAME_EVENTS.client.connectionStatus, next, previous) } // Pending calls we can settle proactively — a connection that drops (or a @@ -99,7 +100,7 @@ export function createLiveRpcClientMode( if (settled) return finish() - events.emit('rpc:error', error, method) + events.emit(DEVFRAME_EVENTS.client.error, error, method) reject(error) }, } @@ -127,7 +128,7 @@ export function createLiveRpcClientMode( return finish() const err = error instanceof Error ? error : new Error(String(error)) - events.emit('rpc:error', err, method) + events.emit(DEVFRAME_EVENTS.client.error, err, method) reject(err) }, ) @@ -148,7 +149,7 @@ export function createLiveRpcClientMode( definitions, onError(error) { setStatus('error', error) - events.emit('connection:error', error) + events.emit(DEVFRAME_EVENTS.client.connectionError, error) rejectAllPending(new DevframeConnectionError('connection', '[devframe] Connection to the devframe server failed', { cause: error })) }, onDisconnected() { @@ -169,15 +170,15 @@ export function createLiveRpcClientMode( // Handle server-initiated auth revocation clientRpc.register({ - name: 'devframe:auth:revoked', + name: DEVFRAME_EVENTS.broadcast.authRevoked, type: 'event', handler: () => { isTrusted = false const authError = new DevframeConnectionError('auth', '[devframe] The devframe server revoked this client\'s trust') setStatus('unauthorized', authError) - events.emit('connection:error', authError) + events.emit(DEVFRAME_EVENTS.client.connectionError, authError) rejectAllPending(authError) - events.emit('rpc:is-trusted:updated', false) + events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, false) }, }) @@ -209,9 +210,9 @@ export function createLiveRpcClientMode( // so it never lands here. const authError = new DevframeConnectionError('auth', '[devframe] The devframe server refused this client\'s credentials') setStatus('unauthorized', authError) - events.emit('connection:error', authError) + events.emit(DEVFRAME_EVENTS.client.connectionError, authError) } - events.emit('rpc:is-trusted:updated', isTrusted) + events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, isTrusted) return result.isTrusted } @@ -228,7 +229,7 @@ export function createLiveRpcClientMode( isTrusted = true trustedPromise.resolve(true) setStatus('connected') - events.emit('rpc:is-trusted:updated', true) + events.emit(DEVFRAME_EVENTS.client.isTrustedUpdated, true) } return token } @@ -284,7 +285,7 @@ export function createLiveRpcClientMode( const method = String(args[0]) const failFast = terminalError() if (failFast) { - events.emit('rpc:error', failFast, method) + events.emit(DEVFRAME_EVENTS.client.error, failFast, method) return Promise.reject(failFast) } return guardCall( @@ -300,7 +301,7 @@ export function createLiveRpcClientMode( // to send, so surface the failure and drop it instead of queuing forever. const failFast = terminalError() if (failFast) { - events.emit('rpc:error', failFast, String(args[0])) + events.emit(DEVFRAME_EVENTS.client.error, failFast, String(args[0])) return } return serverRpc.$callEvent( @@ -312,7 +313,7 @@ export function createLiveRpcClientMode( const method = String(args[0]) const failFast = terminalError() if (failFast) { - events.emit('rpc:error', failFast, method) + events.emit(DEVFRAME_EVENTS.client.error, failFast, method) return Promise.reject(failFast) } return guardCall( diff --git a/packages/devframe/src/client/rpc-shared-state.ts b/packages/devframe/src/client/rpc-shared-state.ts index 3e3ffb87..ae7c6b28 100644 --- a/packages/devframe/src/client/rpc-shared-state.ts +++ b/packages/devframe/src/client/rpc-shared-state.ts @@ -2,6 +2,7 @@ import type { RpcSharedStateGetOptions, RpcSharedStateHost } from 'devframe/type import type { SharedState, SharedStatePatch } from 'devframe/utils/shared-state' import type { DevframeRpcClient } from './rpc' import { createSharedState } from 'devframe/utils/shared-state' +import { DEVFRAME_EVENTS } from '../events' export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcSharedStateHost { const sharedState = new Map>() @@ -20,7 +21,7 @@ export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcShare } rpc.client.register({ - name: 'devframe:rpc:client-state:updated', + name: DEVFRAME_EVENTS.broadcast.clientStateUpdated, type: 'event', handler: (key: string, fullState: any, syncId: string) => { const state = sharedState.get(key) @@ -31,7 +32,7 @@ export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcShare }) rpc.client.register({ - name: 'devframe:rpc:client-state:patch', + name: DEVFRAME_EVENTS.broadcast.clientStatePatch, type: 'event', handler: (key: string, patches: SharedStatePatch[], syncId: string) => { const state = sharedState.get(key) @@ -124,7 +125,7 @@ export function createRpcSharedStateClientHost(rpc: DevframeRpcClient): RpcShare if (!rpc.isTrusted) { resolve(state) let initialized = false - rpc.events.on('rpc:is-trusted:updated', (isTrusted) => { + rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (isTrusted) => { if (isTrusted && !initialized) { initialized = true initSharedState() diff --git a/packages/devframe/src/client/rpc-streaming.ts b/packages/devframe/src/client/rpc-streaming.ts index 72646589..3f046a6f 100644 --- a/packages/devframe/src/client/rpc-streaming.ts +++ b/packages/devframe/src/client/rpc-streaming.ts @@ -1,6 +1,7 @@ import type { StreamErrorPayload, StreamReader, StreamSink } from 'devframe/utils/streaming-channel' import type { DevframeRpcClient } from './rpc' import { createStreamReader, createStreamSink } from 'devframe/utils/streaming-channel' +import { DEVFRAME_EVENTS } from '../events' const STREAM_KEY_SEPARATOR = '\x1F' @@ -46,7 +47,7 @@ export function createRpcStreamingClientHost(rpc: DevframeRpcClient): RpcStreami const uploads = new Map>() rpc.client.register({ - name: 'devframe:streaming:chunk', + name: DEVFRAME_EVENTS.broadcast.streamingChunk, type: 'event', handler(channel: string, id: string, seq: number, chunk: any) { const reader = readers.get(streamKey(channel, id)) @@ -55,7 +56,7 @@ export function createRpcStreamingClientHost(rpc: DevframeRpcClient): RpcStreami }) rpc.client.register({ - name: 'devframe:streaming:end', + name: DEVFRAME_EVENTS.broadcast.streamingEnd, type: 'event', handler(channel: string, id: string, error?: StreamErrorPayload) { const key = streamKey(channel, id) @@ -68,7 +69,7 @@ export function createRpcStreamingClientHost(rpc: DevframeRpcClient): RpcStreami }) rpc.client.register({ - name: 'devframe:streaming:upload-cancel', + name: DEVFRAME_EVENTS.broadcast.streamingUploadCancel, type: 'event', handler(channel: string, id: string) { const key = streamKey(channel, id) @@ -87,7 +88,7 @@ export function createRpcStreamingClientHost(rpc: DevframeRpcClient): RpcStreami // OR the WS dropped briefly (state intact). Either way, sending `subscribe` // with `afterSeq: lastSeenSeq` is the right thing: the server replays // missed chunks if it has them, otherwise starts fresh. - rpc.events.on('rpc:is-trusted:updated', (isTrusted) => { + rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (isTrusted) => { if (!isTrusted) return for (const [key, reader] of readers) { @@ -142,7 +143,7 @@ export function createRpcStreamingClientHost(rpc: DevframeRpcClient): RpcStreami }) } else { - const off = rpc.events.on('rpc:is-trusted:updated', (trusted) => { + const off = rpc.events.on(DEVFRAME_EVENTS.client.isTrustedUpdated, (trusted) => { if (trusted) { off() if (readers.has(key) && !reader.cancelled && !reader.done) { diff --git a/packages/devframe/src/constants.ts b/packages/devframe/src/constants.ts index b35bd57a..99583045 100644 --- a/packages/devframe/src/constants.ts +++ b/packages/devframe/src/constants.ts @@ -1,3 +1,7 @@ +import { DEVFRAME_EVENTS } from './events' + +export { DEVFRAME_EVENTS } from './events' + // Devframe runtime routes and static output conventions. export const DEVFRAME_CONNECTION_META_FILENAME = '__connection.json' @@ -88,7 +92,7 @@ export const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = 'devframe_viewer_origin_ * (`@devframes/hub-ui` does, in its iframe view). Payload shape: * `RemoteAssetsErrorMessage` (`devframe/types`). */ -export const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE = 'devframe:remote-assets-error' +export const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE: string = DEVFRAME_EVENTS.postMessage.remoteAssetsError /** * Prefix that marks an RPC method as callable before a connection is diff --git a/packages/devframe/src/events.ts b/packages/devframe/src/events.ts new file mode 100644 index 00000000..da8e6d57 --- /dev/null +++ b/packages/devframe/src/events.ts @@ -0,0 +1,64 @@ +/** + * Centralized registry of the core devframe event names — the node-side host + * bus events, the client RPC connection events, and the server→client + * broadcast notifications — so these names live in one place instead of + * scattered string literals. + * + * **Keep this in sync with [`docs/guide/events.md`](../../../docs/guide/events.md)** + * (the "Core devframe events" section): every name here appears in that page's + * tables, and every name there resolves to an entry here. Add, rename, or + * remove a name in both places in the same change, and reference + * `DEVFRAME_EVENTS.*` from call sites instead of re-typing a literal. + * + * This map covers **notifications** (events, broadcasts). The request/response + * RPC endpoints of the shared-state, streaming, and auth-handshake protocols + * (`devframe:rpc:server-state:*`, `devframe:streaming:subscribe`, + * `anonymous:devframe:auth`, …) are defined at their handlers and typed in + * `types/rpc-augments.ts`; they aren't events and stay out of this map. + * + * The `EventEmitter` maps (`RpcClientEvents`, `DevframeAgentHostEvents`) and the + * `DevframeRpcClientFunctions` augmentation declare these names as type-level + * keys (a literal is unavoidable in a type position); those declarations mirror + * this map and move with it. + */ +export const DEVFRAME_EVENTS = { + /** + * Node-side host `EventEmitter` events. The agent host (`ctx.agent.events`) + * emits these as its tool/resource surface changes; protocol adapters (e.g. + * MCP) subscribe to re-publish their manifest. + */ + bus: { + agentManifestChanged: 'agent:manifest:changed', + agentToolRegistered: 'agent:tool:registered', + agentToolUnregistered: 'agent:tool:unregistered', + agentResourceRegistered: 'agent:resource:registered', + agentResourceUnregistered: 'agent:resource:unregistered', + }, + /** + * Client-side RPC connection `EventEmitter` events (`rpc.events`) a UI + * subscribes to for connection lifecycle and error surfacing. + */ + client: { + isTrustedUpdated: 'rpc:is-trusted:updated', + error: 'rpc:error', + connectionStatus: 'connection:status', + connectionError: 'connection:error', + }, + /** + * Broadcast notifications the server pushes to clients (server → client), + * `devframe:` prefix. The paired request methods (subscribe/get/set/…) are + * RPC endpoints, not events, and are omitted deliberately. + */ + broadcast: { + authRevoked: 'devframe:auth:revoked', + clientStateUpdated: 'devframe:rpc:client-state:updated', + clientStatePatch: 'devframe:rpc:client-state:patch', + streamingChunk: 'devframe:streaming:chunk', + streamingEnd: 'devframe:streaming:end', + streamingUploadCancel: 'devframe:streaming:upload-cancel', + }, + /** `postMessage` channels the runtime posts across window boundaries. */ + postMessage: { + remoteAssetsError: 'devframe:remote-assets-error', + }, +} as const diff --git a/packages/devframe/src/node/auth/revoke.ts b/packages/devframe/src/node/auth/revoke.ts index 235e099d..ce742b2e 100644 --- a/packages/devframe/src/node/auth/revoke.ts +++ b/packages/devframe/src/node/auth/revoke.ts @@ -2,6 +2,7 @@ import type { DevframeNodeContext } from 'devframe/types' import type { SharedState } from 'devframe/utils/shared-state' import type { RpcFunctionsHostImpl } from '../host-functions' import type { InternalAnonymousAuthStorage } from '../hub-internals/context' +import { DEVFRAME_EVENTS } from '../../events' /** * Flip `isTrusted` to false on any live WS clients connected with `token` @@ -30,7 +31,7 @@ export async function revokeActiveConnectionsForToken( return await rpcHost.broadcast({ - method: 'devframe:auth:revoked', + method: DEVFRAME_EVENTS.broadcast.authRevoked, args: [], filter: client => affectedSessionIds.has(client.$meta.id), }) diff --git a/packages/devframe/src/node/host-agent.ts b/packages/devframe/src/node/host-agent.ts index c3e6bf90..fabd785b 100644 --- a/packages/devframe/src/node/host-agent.ts +++ b/packages/devframe/src/node/host-agent.ts @@ -16,6 +16,7 @@ import type { RpcFunctionAgentOptions, } from 'devframe/types' import { createEventEmitter } from 'devframe/utils/events' +import { DEVFRAME_EVENTS } from '../events' import { coerceAgentPositionalArgs } from './agent-args' import { diagnostics } from './diagnostics' @@ -48,7 +49,7 @@ export class DevframeAgentHost implements DevframeAgentHostType { ) { // Watch the RPC host for new `agent`-flagged definitions. this._rpcUnsubscribe = context.rpc.onChanged(() => { - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) }) } @@ -57,8 +58,8 @@ export class DevframeAgentHost implements DevframeAgentHostType { const tool = this._projectTool(input) this.tools.set(tool.id, { tool, handler: input.handler }) - this.events.emit('agent:tool:registered', tool) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentToolRegistered, tool) + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) return { unregister: () => this.unregisterTool(tool.id), @@ -68,25 +69,25 @@ export class DevframeAgentHost implements DevframeAgentHostType { unregisterTool(id: string): boolean { const existed = this.tools.delete(id) if (existed) { - this.events.emit('agent:tool:unregistered', id) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentToolUnregistered, id) + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) } return existed } registerToolProvider(provider: AgentToolProvider): AgentToolProviderHandle { this.providers.add(provider) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) const notifyChanged = (): void => { if (this.providers.has(provider)) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) } return { notifyChanged, unregister: () => { if (this.providers.delete(provider)) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) }, } } @@ -103,8 +104,8 @@ export class DevframeAgentHost implements DevframeAgentHostType { uri: input.uri ?? `devframe://resource/${encodeURIComponent(input.id)}`, } this.resources.set(resource.id, { resource, read: input.read }) - this.events.emit('agent:resource:registered', resource) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentResourceRegistered, resource) + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) return { unregister: () => this.unregisterResource(resource.id), @@ -114,8 +115,8 @@ export class DevframeAgentHost implements DevframeAgentHostType { unregisterResource(id: string): boolean { const existed = this.resources.delete(id) if (existed) { - this.events.emit('agent:resource:unregistered', id) - this.events.emit('agent:manifest:changed') + this.events.emit(DEVFRAME_EVENTS.bus.agentResourceUnregistered, id) + this.events.emit(DEVFRAME_EVENTS.bus.agentManifestChanged) } return existed } diff --git a/packages/devframe/src/node/rpc-shared-state.ts b/packages/devframe/src/node/rpc-shared-state.ts index 27bdc4d8..300ecff5 100644 --- a/packages/devframe/src/node/rpc-shared-state.ts +++ b/packages/devframe/src/node/rpc-shared-state.ts @@ -2,6 +2,7 @@ import type { RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost } f import type { SharedState, SharedStatePatch } from 'devframe/utils/shared-state' import { createSharedState } from 'devframe/utils/shared-state' import { createDebug } from 'obug' +import { DEVFRAME_EVENTS } from '../events' import { diagnostics } from './diagnostics' const debug = createDebug('devframe:rpc:state:changed') @@ -22,7 +23,7 @@ export function createRpcSharedStateServerHost( if (patches) { debug('patch', { key, syncId }) rpc.broadcast({ - method: 'devframe:rpc:client-state:patch', + method: DEVFRAME_EVENTS.broadcast.clientStatePatch, args: [key, patches, syncId], filter: client => client.$meta.subscribedStates.has(key), }) @@ -30,7 +31,7 @@ export function createRpcSharedStateServerHost( else { debug('updated', { key, syncId }) rpc.broadcast({ - method: 'devframe:rpc:client-state:updated', + method: DEVFRAME_EVENTS.broadcast.clientStateUpdated, args: [key, fullState, syncId], filter: client => client.$meta.subscribedStates.has(key), }) diff --git a/packages/devframe/src/node/rpc-streaming.ts b/packages/devframe/src/node/rpc-streaming.ts index 3e1d2d87..77ec909a 100644 --- a/packages/devframe/src/node/rpc-streaming.ts +++ b/packages/devframe/src/node/rpc-streaming.ts @@ -8,6 +8,7 @@ import type { import type { StreamErrorPayload, StreamReader, StreamSink } from 'devframe/utils/streaming-channel' import { createStreamReader, createStreamSink } from 'devframe/utils/streaming-channel' import { createDebug } from 'obug' +import { DEVFRAME_EVENTS } from '../events' import { diagnostics } from './diagnostics' const debug = createDebug('devframe:rpc:streaming') @@ -118,7 +119,7 @@ export function createRpcStreamingServerHost(rpc: RpcFunctionsHost): RpcStreamin for (const buffered of record.sink.buffer) { if (buffered.seq > afterSeq) { rpc.broadcast({ - method: 'devframe:streaming:chunk', + method: DEVFRAME_EVENTS.broadcast.streamingChunk, args: [channelName, id, buffered.seq, buffered.chunk], event: true, optional: true, @@ -128,7 +129,7 @@ export function createRpcStreamingServerHost(rpc: RpcFunctionsHost): RpcStreamin } if (record.sink.closed) { rpc.broadcast({ - method: 'devframe:streaming:end', + method: DEVFRAME_EVENTS.broadcast.streamingEnd, args: [channelName, id, undefined], event: true, optional: true, @@ -250,7 +251,7 @@ export function createRpcStreamingServerHost(rpc: RpcFunctionsHost): RpcStreamin record.unbinders.push( sink.events.on('chunk', (seq, chunk) => { rpc.broadcast({ - method: 'devframe:streaming:chunk', + method: DEVFRAME_EVENTS.broadcast.streamingChunk, args: [name, sink.id, seq, chunk], event: true, optional: true, @@ -261,7 +262,7 @@ export function createRpcStreamingServerHost(rpc: RpcFunctionsHost): RpcStreamin record.unbinders.push( sink.events.on('end', (error) => { rpc.broadcast({ - method: 'devframe:streaming:end', + method: DEVFRAME_EVENTS.broadcast.streamingEnd, args: [name, sink.id, error], event: true, optional: true, @@ -304,7 +305,7 @@ export function createRpcStreamingServerHost(rpc: RpcFunctionsHost): RpcStreamin if (!targetMeta) return rpc.broadcast({ - method: 'devframe:streaming:upload-cancel', + method: DEVFRAME_EVENTS.broadcast.streamingUploadCancel, args: [name, reader.id], event: true, optional: true, diff --git a/packages/devframe/src/utils/serve-static.ts b/packages/devframe/src/utils/serve-static.ts index 2dc50e95..c24e698b 100644 --- a/packages/devframe/src/utils/serve-static.ts +++ b/packages/devframe/src/utils/serve-static.ts @@ -8,7 +8,7 @@ import { Readable } from 'node:stream' import { defineHandler, H3 } from 'h3' import { lookup } from 'mrmime' import { extname, join, normalize, resolve, sep } from 'pathe' -import { DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE } from '../constants' +import { DEVFRAME_EVENTS } from '../events' /** * What the static-serving engine accepts: a local directory, or a resolved @@ -322,7 +322,7 @@ function remoteErrorPage(pkg: string, version: string, reason: string): string { const esc = (s: string): string => s.replace(/&/g, '&').replace(//g, '>') const name = esc(pkg) const ver = esc(version) - const message: RemoteAssetsErrorMessage = { type: DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, package: pkg, version, reason } + const message: RemoteAssetsErrorMessage = { type: DEVFRAME_EVENTS.postMessage.remoteAssetsError, package: pkg, version, reason } // `` inside the JSON would end the block early; `<` is the only // character that can do that, and escaping it keeps the literal valid JS. const payload = JSON.stringify(message).replace(/ props.entry.launcher.terminalSessionId) function viewInTerminal() { if (!terminalSessionId.value) return - props.context.rpc.call('hub:docks:activate', { + props.context.rpc.call(HUB_EVENTS.rpc.docksActivate, { dockId: TERMINALS_DOCK_ID, params: { sessionId: terminalSessionId.value }, }) diff --git a/packages/hub-ui/src/client/state/context.ts b/packages/hub-ui/src/client/state/context.ts index cf1574bb..6665e931 100644 --- a/packages/hub-ui/src/client/state/context.ts +++ b/packages/hub-ui/src/client/state/context.ts @@ -5,7 +5,7 @@ import type { WhenContext } from 'devframe/utils/when' import type { Ref } from 'vue' import type { DevframeDocksUserSettings } from './dock-settings' import { attachFrameNavClient, createDockRenderersContext } from '@devframes/hub/client' -import { DEFAULT_STATE_USER_SETTINGS, DOCK_RENDERERS_STATE_KEY } from '@devframes/hub/constants' +import { DEFAULT_STATE_USER_SETTINGS, DOCK_RENDERERS_STATE_KEY, HUB_EVENTS } from '@devframes/hub/constants' import { computed, markRaw, reactive, ref, toRefs, watch, watchEffect } from 'vue' import { BUILTIN_ENTRIES, BUILTIN_ENTRY_SETTINGS, DEFAULT_CATEGORIES_ORDER, HUB_UI_HIDE_EVENT } from '../constants' import { useBranding } from './branding' @@ -376,7 +376,7 @@ export async function createDocksContext( // ourselves. The target dock (e.g. Terminals) reads `activation.params` to // focus a specific session. rpc.client.register({ - name: 'devframe:docks:activate' satisfies keyof DevframeRpcClientFunctions, + name: HUB_EVENTS.broadcast.docksActivate satisfies keyof DevframeRpcClientFunctions, type: 'action', handler: (activation: { dockId: string, params?: Record }) => { if (activation?.dockId) diff --git a/packages/hub/src/client/frame-nav.ts b/packages/hub/src/client/frame-nav.ts index f44b7dc0..19e89d85 100644 --- a/packages/hub/src/client/frame-nav.ts +++ b/packages/hub/src/client/frame-nav.ts @@ -1,5 +1,6 @@ import type { DevframeDockEntryIcon, DevframeViewIframe, NavTarget } from '../types/docks' import type { DockRegistration, DocksEntriesContext } from './docks' +import { HUB_EVENTS } from '../events' /** * Shared-iframe soft navigation — the viewer-side half of a host↔iframe @@ -22,7 +23,7 @@ import type { DockRegistration, DocksEntriesContext } from './docks' */ /** `postMessage` channel tag shared by both halves of the protocol. */ -export const FRAME_NAV_CHANNEL = 'devframe:frame-nav' +export const FRAME_NAV_CHANNEL: string = HUB_EVENTS.postMessage.frameNav /** Protocol version. */ export const FRAME_NAV_VERSION = 1 diff --git a/packages/hub/src/client/host.ts b/packages/hub/src/client/host.ts index c16d3884..e8ba98fd 100644 --- a/packages/hub/src/client/host.ts +++ b/packages/hub/src/client/host.ts @@ -27,15 +27,16 @@ import type { DockRenderer, DockRendererManifest, DockRenderersContext } from '. import { connectDevframe } from 'devframe/client' import { createEventEmitter } from 'devframe/utils/events' import { DEFAULT_CATEGORIES_ORDER, DEFAULT_STATE_USER_SETTINGS, DOCK_RENDERERS_STATE_KEY } from '../constants' +import { HUB_EVENTS } from '../events' import { getDevframeClientContext, setDevframeClientContext } from './context' import { attachFrameNavClient } from './frame-nav' import { createMessagesClient } from './messages' import { createDockRenderersContext } from './renderers' -const DOCKS_STATE_KEY = 'devframe:docks' -const COMMANDS_STATE_KEY = 'devframe:commands' -const USER_SETTINGS_STATE_KEY = 'devframe:user-settings' -const DOCKS_ACTIVATE_EVENT = 'devframe:docks:activate' +const DOCKS_STATE_KEY = HUB_EVENTS.sharedState.docks +const COMMANDS_STATE_KEY = HUB_EVENTS.sharedState.commands +const USER_SETTINGS_STATE_KEY = HUB_EVENTS.sharedState.userSettings +const DOCKS_ACTIVATE_EVENT = HUB_EVENTS.broadcast.docksActivate export interface DevframeClientHostOptions { /** diff --git a/packages/hub/src/constants.ts b/packages/hub/src/constants.ts index f169e761..f1340ddb 100644 --- a/packages/hub/src/constants.ts +++ b/packages/hub/src/constants.ts @@ -1,6 +1,8 @@ import type { DevframeDocksUserSettings } from './types/settings' import { cleanDoubleSlashes, withLeadingSlash, withTrailingSlash } from 'ufo' +import { HUB_EVENTS } from './events' +export { HUB_EVENTS } from './events' export * from 'devframe/constants' /** Default mount base for a hub instance — one namespace, one catch-all. */ @@ -46,7 +48,7 @@ export const DEFAULT_CATEGORIES_ORDER: Record = { * `type`, published by `initHub({ renderers })` and consumed by every * hub-aware client (the headless client host and viewers alike). */ -export const DOCK_RENDERERS_STATE_KEY = 'devframe:dock-renderers' +export const DOCK_RENDERERS_STATE_KEY: string = HUB_EVENTS.sharedState.dockRenderers export const DEFAULT_STATE_USER_SETTINGS: () => DevframeDocksUserSettings = () => ({ docksHidden: [], diff --git a/packages/hub/src/events.ts b/packages/hub/src/events.ts new file mode 100644 index 00000000..0113e14c --- /dev/null +++ b/packages/hub/src/events.ts @@ -0,0 +1,70 @@ +/** + * Centralized registry of every event, broadcast, RPC method, shared-state + * key, and channel name the hub uses — the single source of truth that keeps + * these names out of scattered string literals. + * + * **Keep this in sync with [`docs/guide/events.md`](../../../docs/guide/events.md)** + * (the Hub Events Reference): every name here appears in that page's tables, and + * every name there resolves to an entry here. Add, rename, or remove a name in + * both places in the same change, and reference `HUB_EVENTS.*` from call sites + * instead of re-typing a literal. + * + * The `.events` EventEmitter maps in `types/{docks,terminals,messages,commands}.ts` + * and the RPC augmentation interfaces in `node/context.ts` declare these same + * names as type-level keys (a literal is unavoidable in a type position); those + * declarations mirror this map and move with it. + */ +export const HUB_EVENTS = { + /** + * Internal node `EventEmitter` events on `ctx..events`. Emitted + * and consumed inside the node process (chiefly by `createHubContext`, which + * fans them out onto the wire); they never cross to the browser. + */ + bus: { + docksEntryUpdated: 'docks:entry:updated', + docksActivate: 'docks:activate', + terminalsSessionUpdated: 'terminals:session:updated', + messagesAdded: 'messages:added', + messagesUpdated: 'messages:updated', + messagesRemoved: 'messages:removed', + messagesCleared: 'messages:cleared', + commandsRegistered: 'commands:registered', + commandsUnregistered: 'commands:unregistered', + }, + /** Server RPC methods a connected client calls (client → server), `hub:` prefix. */ + rpc: { + docksActivate: 'hub:docks:activate', + commandsExecute: 'hub:commands:execute', + messagesAdd: 'hub:messages:add', + messagesUpdate: 'hub:messages:update', + messagesRemove: 'hub:messages:remove', + messagesClear: 'hub:messages:clear', + terminalsWrite: 'hub:terminals:write', + terminalsResize: 'hub:terminals:resize', + terminalsTerminate: 'hub:terminals:terminate', + terminalsRestart: 'hub:terminals:restart', + terminalsRemove: 'hub:terminals:remove', + }, + /** Broadcast notifications the server pushes to clients (server → client), `devframe:` prefix. */ + broadcast: { + docksActivate: 'devframe:docks:activate', + terminalsUpdated: 'devframe:terminals:updated', + messagesUpdated: 'devframe:messages:updated', + }, + /** Shared-state slot keys a hub-aware client reads (server → client), `devframe:` prefix. */ + sharedState: { + docks: 'devframe:docks', + docksActive: 'devframe:docks:active', + commands: 'devframe:commands', + userSettings: 'devframe:user-settings', + dockRenderers: 'devframe:dock-renderers', + }, + /** Streaming channel ids (server → client), `devframe:` prefix. */ + stream: { + terminals: 'devframe:terminals', + }, + /** `postMessage` channels for host ↔ iframe protocols, `devframe:` prefix. */ + postMessage: { + frameNav: 'devframe:frame-nav', + }, +} as const diff --git a/packages/hub/src/node/__tests__/host-docks.test.ts b/packages/hub/src/node/__tests__/host-docks.test.ts index 392b6b6b..81313b42 100644 --- a/packages/hub/src/node/__tests__/host-docks.test.ts +++ b/packages/hub/src/node/__tests__/host-docks.test.ts @@ -85,7 +85,7 @@ describe('devframeDockHost grouping', () => { it('registers a group entry: stored, projected, and emitted', () => { const host = new DevframeDocksHost(createContext()) const emitted: string[] = [] - host.events.on('dock:entry:updated', entry => emitted.push(entry.id)) + host.events.on('docks:entry:updated', entry => emitted.push(entry.id)) host.register({ type: 'group', @@ -192,12 +192,12 @@ describe('devframeDockHost grouping', () => { }) describe('devframeDockHost activate', () => { - it('emits a dock:activate event carrying the id and params', () => { + it('emits a docks:activate event carrying the id and params', () => { const host = new DevframeDocksHost(createContext()) host.register({ type: 'iframe', id: 'terminals', title: 'Terminals', icon: 'ph:terminal-window-duotone', url: '/__terminals/' }) const activations: Array<{ dockId: string, params?: Record }> = [] - host.events.on('dock:activate', a => activations.push(a)) + host.events.on('docks:activate', a => activations.push(a)) host.activate('terminals', { sessionId: 'sess-1' }) expect(activations).toEqual([{ dockId: 'terminals', params: { sessionId: 'sess-1' } }]) @@ -208,7 +208,7 @@ describe('devframeDockHost activate', () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) try { const activations: string[] = [] - host.events.on('dock:activate', a => activations.push(a.dockId)) + host.events.on('docks:activate', a => activations.push(a.dockId)) host.activate('nope') expect(activations).toEqual(['nope']) diff --git a/packages/hub/src/node/__tests__/host-terminals.test.ts b/packages/hub/src/node/__tests__/host-terminals.test.ts index 26049d58..b3b2e056 100644 --- a/packages/hub/src/node/__tests__/host-terminals.test.ts +++ b/packages/hub/src/node/__tests__/host-terminals.test.ts @@ -261,7 +261,7 @@ describe('devframeTerminalHost child-process status lifecycle', () => { it('marks status stopped and emits an update on a clean exit', async () => { const { host } = createTerminalHost() const updates: string[] = [] - host.events.on('terminal:session:updated', s => updates.push(s.status)) + host.events.on('terminals:session:updated', s => updates.push(s.status)) const session = await host.startChildProcess({ command: process.execPath, @@ -441,7 +441,7 @@ describe('devframeTerminalHost PTY status lifecycle', () => { itPty('marks status stopped and emits an update on a clean exit', async () => { const { host } = createTerminalHost() const updates: string[] = [] - host.events.on('terminal:session:updated', s => updates.push(s.status)) + host.events.on('terminals:session:updated', s => updates.push(s.status)) const session = await host.startPtySession({ command: NODE, diff --git a/packages/hub/src/node/context.ts b/packages/hub/src/node/context.ts index 71d7c182..6aee0ffc 100644 --- a/packages/hub/src/node/context.ts +++ b/packages/hub/src/node/context.ts @@ -8,6 +8,7 @@ import type { InstallDevframeOptions } from './install-devframe' import { createHostContext } from 'devframe/node' import { getInternalContext } from 'devframe/node/hub-internals' import { debounce } from 'perfect-debounce' +import { HUB_EVENTS } from '../events' import { DevframeCommandsHost as CommandsHostImpl } from './host-commands' import { DevframeDocksHost as DocksHostImpl } from './host-docks' import { DevframeMessagesHost as MessagesHostImpl } from './host-messages' @@ -151,11 +152,11 @@ export async function createHubContext(options: CreateHubContextOptions): Promis const debounceMs = options.mode === 'build' ? 0 : 10 - const docksSharedState = await context.rpc.sharedState.get('devframe:docks', { initialValue: [] }) + const docksSharedState = await context.rpc.sharedState.get(HUB_EVENTS.sharedState.docks, { initialValue: [] }) const refreshDocks = debounce(() => { docksSharedState.mutate(() => docks.values()) }, debounceMs) - docks.events.on('dock:entry:updated', refreshDocks) + docks.events.on(HUB_EVENTS.bus.docksEntryUpdated, refreshDocks) // A remote iframe dock registered before the WS transport finishes binding // (the common case: `initHub` installs devframes — and their docks — before // resolving an async side-car/shared-server port) gets projected without a @@ -171,46 +172,46 @@ export async function createHubContext(options: CreateHubContextOptions): Promis // switches its active dock — and into a shared-state slot, so a dock that // only mounts *because* of the switch still converges on the request. const activeDockSharedState = await context.rpc.sharedState.get( - 'devframe:docks:active', + HUB_EVENTS.sharedState.docksActive, { initialValue: { activation: null } }, ) - docks.events.on('dock:activate', (activation) => { + docks.events.on(HUB_EVENTS.bus.docksActivate, (activation) => { activeDockSharedState.mutate((state) => { state.activation = activation }) context.rpc.broadcast({ - method: 'devframe:docks:activate', + method: HUB_EVENTS.broadcast.docksActivate, args: [activation], }) }) const broadcastTerminals = debounce(() => { context.rpc.broadcast({ - method: 'devframe:terminals:updated', + method: HUB_EVENTS.broadcast.terminalsUpdated, args: [], }) docksSharedState.mutate(() => docks.values()) }, debounceMs) - terminals.events.on('terminal:session:updated', broadcastTerminals) + terminals.events.on(HUB_EVENTS.bus.terminalsSessionUpdated, broadcastTerminals) const broadcastMessages = debounce(() => { context.rpc.broadcast({ - method: 'devframe:messages:updated', + method: HUB_EVENTS.broadcast.messagesUpdated, args: [], }) docksSharedState.mutate(() => docks.values()) }, debounceMs) - messages.events.on('message:added', broadcastMessages) - messages.events.on('message:updated', broadcastMessages) - messages.events.on('message:removed', broadcastMessages) - messages.events.on('message:cleared', broadcastMessages) + messages.events.on(HUB_EVENTS.bus.messagesAdded, broadcastMessages) + messages.events.on(HUB_EVENTS.bus.messagesUpdated, broadcastMessages) + messages.events.on(HUB_EVENTS.bus.messagesRemoved, broadcastMessages) + messages.events.on(HUB_EVENTS.bus.messagesCleared, broadcastMessages) - const commandsSharedState = await context.rpc.sharedState.get('devframe:commands', { initialValue: [] }) + const commandsSharedState = await context.rpc.sharedState.get(HUB_EVENTS.sharedState.commands, { initialValue: [] }) const syncCommands = debounce(() => { commandsSharedState.mutate(() => commands.list()) }, debounceMs) - commands.events.on('command:registered', syncCommands) - commands.events.on('command:unregistered', syncCommands) + commands.events.on(HUB_EVENTS.bus.commandsRegistered, syncCommands) + commands.events.on(HUB_EVENTS.bus.commandsUnregistered, syncCommands) commandsSharedState.mutate(() => commands.list()) diff --git a/packages/hub/src/node/host-commands.ts b/packages/hub/src/node/host-commands.ts index 82d6ceeb..2941d2f2 100644 --- a/packages/hub/src/node/host-commands.ts +++ b/packages/hub/src/node/host-commands.ts @@ -8,6 +8,7 @@ import type { import type { DevframeHubContext } from './context' import { coerceAgentPositionalArgs } from 'devframe/internal' import { createEventEmitter } from 'devframe/utils/events' +import { HUB_EVENTS } from '../events' import { diagnostics } from './diagnostics' function findChildCommand(command: DevframeServerCommandInput, id: string): DevframeServerCommandInput | undefined { @@ -76,7 +77,7 @@ export class DevframeCommandsHost implements DevframeCommandsHostType { validateCommandIds(this.commands, command) this.validateAgentExposure(command) this.commands.set(command.id, command) - this.events.emit('command:registered', this.toSerializable(command)) + this.events.emit(HUB_EVENTS.bus.commandsRegistered, this.toSerializable(command)) this.agentProvider?.notifyChanged() return { @@ -97,7 +98,7 @@ export class DevframeCommandsHost implements DevframeCommandsHostType { validateCommandIds(this.commands, next, existing.id) this.validateAgentExposure(next) Object.assign(existing, patch) - this.events.emit('command:registered', this.toSerializable(existing)) + this.events.emit(HUB_EVENTS.bus.commandsRegistered, this.toSerializable(existing)) this.agentProvider?.notifyChanged() }, unregister: () => this.unregister(command.id), @@ -107,7 +108,7 @@ export class DevframeCommandsHost implements DevframeCommandsHostType { unregister(id: string): boolean { const deleted = this.commands.delete(id) if (deleted) { - this.events.emit('command:unregistered', id) + this.events.emit(HUB_EVENTS.bus.commandsUnregistered, id) this.agentProvider?.notifyChanged() } return deleted diff --git a/packages/hub/src/node/host-docks.ts b/packages/hub/src/node/host-docks.ts index 4f1769a6..cd9b9b22 100644 --- a/packages/hub/src/node/host-docks.ts +++ b/packages/hub/src/node/host-docks.ts @@ -15,6 +15,7 @@ import { getInternalContext } from 'devframe/node/hub-internals' import { createEventEmitter } from 'devframe/utils/events' import { join } from 'pathe' import { DEFAULT_STATE_USER_SETTINGS } from '../constants' +import { HUB_EVENTS } from '../events' import { buildRemoteConnectionUrl } from '../remote-url' import { diagnostics } from './diagnostics' @@ -44,7 +45,7 @@ export class DevframeDocksHost implements DevframeDocksHostType { ) {} async init() { - this.userSettings = await this.context.rpc.sharedState.get('devframe:user-settings', { + this.userSettings = await this.context.rpc.sharedState.get(HUB_EVENTS.sharedState.userSettings, { sharedState: createStorage({ // Personal dock layout/preferences: per-checkout private state. filepath: join(this.context.host.getStorageDir('project'), 'settings.json'), @@ -91,7 +92,7 @@ export class DevframeDocksHost implements DevframeDocksHostType { this.validateGroupMembership(view) this.prepareRemoteRegistration(view) this.views.set(view.id, view) - this.events.emit('dock:entry:updated', view) + this.events.emit(HUB_EVENTS.bus.docksEntryUpdated, view) return { update: (patch) => { @@ -115,7 +116,7 @@ export class DevframeDocksHost implements DevframeDocksHostType { this.validateGroupMembership(view) this.prepareRemoteRegistration(view) this.views.set(view.id, view) - this.events.emit('dock:entry:updated', view) + this.events.emit(HUB_EVENTS.bus.docksEntryUpdated, view) } activate(dockId: string, params?: Record): void { @@ -125,7 +126,7 @@ export class DevframeDocksHost implements DevframeDocksHostType { // rather than fatal. if (!this.views.has(dockId)) diagnostics.DF8107({ id: dockId }) - this.events.emit('dock:activate', { dockId, params }) + this.events.emit(HUB_EVENTS.bus.docksActivate, { dockId, params }) } private validateGroupMembership(view: DevframeDockUserEntry): void { diff --git a/packages/hub/src/node/host-messages.ts b/packages/hub/src/node/host-messages.ts index 519018bb..14afed05 100644 --- a/packages/hub/src/node/host-messages.ts +++ b/packages/hub/src/node/host-messages.ts @@ -9,6 +9,7 @@ import type { import type { DevframeHubContext } from './context' import { createEventEmitter } from 'devframe/utils/events' import { nanoid } from 'devframe/utils/nanoid' +import { HUB_EVENTS } from '../events' const MAX_ENTRIES = 1000 const MAX_REMOVALS = 1000 @@ -69,7 +70,7 @@ export class DevframeMessagesHost implements DevframeMessagesHostType { this.entries.set(entry.id, entry) this.lastModified.set(entry.id, this._tick()) - this.events.emit('message:added', entry) + this.events.emit(HUB_EVENTS.bus.messagesAdded, entry) if (entry.autoDelete) { this._autoDeleteTimers.set(entry.id, setTimeout(() => { @@ -95,7 +96,7 @@ export class DevframeMessagesHost implements DevframeMessagesHostType { this.entries.set(id, updated) this.lastModified.set(id, this._tick()) - this.events.emit('message:updated', updated) + this.events.emit(HUB_EVENTS.bus.messagesUpdated, updated) // Reset autoDelete timer if changed if (patch.autoDelete !== undefined) { @@ -123,7 +124,7 @@ export class DevframeMessagesHost implements DevframeMessagesHostType { this.entries.delete(id) this.lastModified.delete(id) this._recordRemoval(id, this._tick()) - this.events.emit('message:removed', id) + this.events.emit(HUB_EVENTS.bus.messagesRemoved, id) } info(message: string, extra?: DevframeMessageShortcutInput): Promise { @@ -155,7 +156,7 @@ export class DevframeMessagesHost implements DevframeMessagesHostType { this._recordRemoval(id, tick) this.entries.clear() this.lastModified.clear() - this.events.emit('message:cleared') + this.events.emit(HUB_EVENTS.bus.messagesCleared) } listSince(since?: number | null): DevframeMessagesListDelta { diff --git a/packages/hub/src/node/host-terminals.ts b/packages/hub/src/node/host-terminals.ts index abd698d4..d1b3b542 100644 --- a/packages/hub/src/node/host-terminals.ts +++ b/packages/hub/src/node/host-terminals.ts @@ -16,6 +16,7 @@ import type { import type { DevframeHubContext } from './context' import process from 'node:process' import { createEventEmitter } from 'devframe/utils/events' +import { HUB_EVENTS } from '../events' import { diagnostics } from './diagnostics' type PartialWithoutId = Partial & { id: string } @@ -24,7 +25,7 @@ type PartialWithoutId = Partial & { id: string } * Channel name used for terminal stream output. Stable, well-known so * hub-aware clients can subscribe by name. */ -const TERMINAL_STREAM_CHANNEL = 'devframe:terminals' as const +const TERMINAL_STREAM_CHANNEL = HUB_EVENTS.stream.terminals const TERMINAL_REPLAY_WINDOW = 1000 /** Max chunks retained in the per-session scrollback buffer (bounded like the replay window). */ const TERMINAL_BUFFER_LIMIT = 1000 @@ -71,7 +72,7 @@ export class DevframeTerminalsHost implements DevframeTerminalsHostType { } this.sessions.set(session.id, session) this.bindStream(session) - this.events.emit('terminal:session:updated', session) + this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session) return session } @@ -83,13 +84,13 @@ export class DevframeTerminalsHost implements DevframeTerminalsHostType { Object.assign(session, patch) this.sessions.set(patch.id, session) this.bindStream(session) - this.events.emit('terminal:session:updated', session) + this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session) } remove(session: DevframeTerminalSession): void { this._boundStreams.get(session.id)?.dispose() this.sessions.delete(session.id) - this.events.emit('terminal:session:updated', session) + this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session) this._boundStreams.delete(session.id) } @@ -185,7 +186,7 @@ export class DevframeTerminalsHost implements DevframeTerminalsHostType { if (session.status === next) return session.status = next - this.events.emit('terminal:session:updated', session) + this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session) } const closeStream = () => { @@ -376,7 +377,7 @@ export class DevframeTerminalsHost implements DevframeTerminalsHostType { if (session.status === next) return session.status = next - this.events.emit('terminal:session:updated', session) + this.events.emit(HUB_EVENTS.bus.terminalsSessionUpdated, session) } const closeStream = () => { diff --git a/packages/hub/src/node/rpc-builtins.ts b/packages/hub/src/node/rpc-builtins.ts index 3d1f1eb0..03fa34de 100644 --- a/packages/hub/src/node/rpc-builtins.ts +++ b/packages/hub/src/node/rpc-builtins.ts @@ -5,6 +5,7 @@ import type { DevframePtyTerminalSession, } from '../types/terminals' import { defineHubRpcFunction } from '../define' +import { HUB_EVENTS } from '../events' import { diagnostics } from './diagnostics' /** @@ -52,7 +53,7 @@ function resolveControllableSession( * from the shared state and dispatch by id via this RPC. */ export const hubCommandsExecute = defineHubRpcFunction({ - name: 'hub:commands:execute', + name: HUB_EVENTS.rpc.commandsExecute, type: 'action', setup: context => ({ async handler(id: string, ...args: any[]) { @@ -71,7 +72,7 @@ export const hubCommandsExecute = defineHubRpcFunction({ * dock client script can report into the same feed the server writes to. */ export const hubMessagesAdd = defineHubRpcFunction({ - name: 'hub:messages:add', + name: HUB_EVENTS.rpc.messagesAdd, type: 'action', jsonSerializable: true, setup: context => ({ @@ -84,7 +85,7 @@ export const hubMessagesAdd = defineHubRpcFunction({ /** `hub:messages:update` — Patch a message by id; returns the updated entry (or `undefined`). */ export const hubMessagesUpdate = defineHubRpcFunction({ - name: 'hub:messages:update', + name: HUB_EVENTS.rpc.messagesUpdate, type: 'action', jsonSerializable: true, setup: context => ({ @@ -96,7 +97,7 @@ export const hubMessagesUpdate = defineHubRpcFunction({ /** `hub:messages:remove` — Remove a message by id. */ export const hubMessagesRemove = defineHubRpcFunction({ - name: 'hub:messages:remove', + name: HUB_EVENTS.rpc.messagesRemove, type: 'action', setup: context => ({ async handler(id: string): Promise { @@ -107,7 +108,7 @@ export const hubMessagesRemove = defineHubRpcFunction({ /** `hub:messages:clear` — Remove every message. */ export const hubMessagesClear = defineHubRpcFunction({ - name: 'hub:messages:clear', + name: HUB_EVENTS.rpc.messagesClear, type: 'action', setup: context => ({ async handler(): Promise { @@ -122,7 +123,7 @@ export const hubMessagesClear = defineHubRpcFunction({ * terminals plugin) drive a session owned by another plugin. */ export const hubTerminalsWrite = defineHubRpcFunction({ - name: 'hub:terminals:write', + name: HUB_EVENTS.rpc.terminalsWrite, type: 'action', setup: context => ({ async handler(id: string, data: string): Promise { @@ -133,7 +134,7 @@ export const hubTerminalsWrite = defineHubRpcFunction({ /** `hub:terminals:resize` — Resize an interactive PTY session by id. */ export const hubTerminalsResize = defineHubRpcFunction({ - name: 'hub:terminals:resize', + name: HUB_EVENTS.rpc.terminalsResize, type: 'action', setup: context => ({ async handler(id: string, cols: number, rows: number): Promise { @@ -149,7 +150,7 @@ export const hubTerminalsResize = defineHubRpcFunction({ * force-kill a session owned by another plugin. */ export const hubTerminalsTerminate = defineHubRpcFunction({ - name: 'hub:terminals:terminate', + name: HUB_EVENTS.rpc.terminalsTerminate, type: 'action', setup: context => ({ async handler(id: string): Promise { @@ -164,7 +165,7 @@ export const hubTerminalsTerminate = defineHubRpcFunction({ * elsewhere. */ export const hubTerminalsRestart = defineHubRpcFunction({ - name: 'hub:terminals:restart', + name: HUB_EVENTS.rpc.terminalsRestart, type: 'action', setup: context => ({ async handler(id: string): Promise { @@ -182,7 +183,7 @@ export const hubTerminalsRestart = defineHubRpcFunction({ * terminal UI discard a stopped aggregated session. */ export const hubTerminalsRemove = defineHubRpcFunction({ - name: 'hub:terminals:remove', + name: HUB_EVENTS.rpc.terminalsRemove, type: 'action', setup: context => ({ async handler(id: string): Promise { @@ -210,7 +211,7 @@ export const hubTerminalsRemove = defineHubRpcFunction({ * mounts in response still converges on it). */ export const hubDocksActivate = defineHubRpcFunction({ - name: 'hub:docks:activate', + name: HUB_EVENTS.rpc.docksActivate, type: 'action', setup: context => ({ async handler(input: { dockId: string, params?: Record }): Promise { diff --git a/packages/hub/src/types/commands.ts b/packages/hub/src/types/commands.ts index 0f9a79c1..ab4b4ee7 100644 --- a/packages/hub/src/types/commands.ts +++ b/packages/hub/src/types/commands.ts @@ -135,8 +135,8 @@ export interface DevframeCommandHandle { } export interface DevframeCommandsHostEvents { - 'command:registered': (command: DevframeServerCommandEntry) => void - 'command:unregistered': (id: string) => void + 'commands:registered': (command: DevframeServerCommandEntry) => void + 'commands:unregistered': (id: string) => void } export interface DevframeCommandsHost { diff --git a/packages/hub/src/types/docks.ts b/packages/hub/src/types/docks.ts index 65d4568f..14163e6d 100644 --- a/packages/hub/src/types/docks.ts +++ b/packages/hub/src/types/docks.ts @@ -3,8 +3,8 @@ import type { ConnectionMeta, EventEmitter } from 'devframe/types' export interface DevframeDocksHost { readonly views: Map readonly events: EventEmitter<{ - 'dock:entry:updated': (entry: DevframeDockUserEntry) => void - 'dock:activate': (activation: DevframeDockActivation) => void + 'docks:entry:updated': (entry: DevframeDockUserEntry) => void + 'docks:activate': (activation: DevframeDockActivation) => void }> register: (entry: T, force?: boolean) => { diff --git a/packages/hub/src/types/messages.ts b/packages/hub/src/types/messages.ts index d64c3dcb..28603377 100644 --- a/packages/hub/src/types/messages.ts +++ b/packages/hub/src/types/messages.ts @@ -206,10 +206,10 @@ export interface DevframeMessagesListDelta { export interface DevframeMessagesHost extends DevframeMessagesLevelShortcuts { readonly entries: Map readonly events: EventEmitter<{ - 'message:added': (entry: DevframeMessageEntry) => void - 'message:updated': (entry: DevframeMessageEntry) => void - 'message:removed': (id: string) => void - 'message:cleared': () => void + 'messages:added': (entry: DevframeMessageEntry) => void + 'messages:updated': (entry: DevframeMessageEntry) => void + 'messages:removed': (id: string) => void + 'messages:cleared': () => void }> /** diff --git a/packages/hub/src/types/terminals.ts b/packages/hub/src/types/terminals.ts index 3243485b..939634a9 100644 --- a/packages/hub/src/types/terminals.ts +++ b/packages/hub/src/types/terminals.ts @@ -5,7 +5,7 @@ import type { DevframeDockEntryIcon } from './docks' export interface DevframeTerminalsHost { readonly sessions: Map readonly events: EventEmitter<{ - 'terminal:session:updated': (session: DevframeTerminalSession) => void + 'terminals:session:updated': (session: DevframeTerminalSession) => void }> register: (session: DevframeTerminalSession) => DevframeTerminalSession diff --git a/plugins/messages/src/spa/dev-host.ts b/plugins/messages/src/spa/dev-host.ts index 570c11cf..9be658e3 100644 --- a/plugins/messages/src/spa/dev-host.ts +++ b/plugins/messages/src/spa/dev-host.ts @@ -1,5 +1,6 @@ import type { DevframeMessagesHost as DevframeMessagesHostType } from '@devframes/hub/types' import type { DevframeDefinition, DevframeNodeContext } from 'devframe' +import { HUB_EVENTS } from '@devframes/hub/constants' import { defineDevframe } from 'devframe' import { MESSAGES_UPDATED_EVENT } from '../constants' import { createMessagesDevframe } from '../index' @@ -31,10 +32,10 @@ function wireBroadcast(ctx: DevframeNodeContext, messages: DevframeMessagesHostT const broadcast = (): void => { ctx.rpc.broadcast({ method: MESSAGES_UPDATED_EVENT, args: [] }) } - messages.events.on('message:added', broadcast) - messages.events.on('message:updated', broadcast) - messages.events.on('message:removed', broadcast) - messages.events.on('message:cleared', broadcast) + messages.events.on(HUB_EVENTS.bus.messagesAdded, broadcast) + messages.events.on(HUB_EVENTS.bus.messagesUpdated, broadcast) + messages.events.on(HUB_EVENTS.bus.messagesRemoved, broadcast) + messages.events.on(HUB_EVENTS.bus.messagesCleared, broadcast) } function seedDemoMessages(messages: DevframeMessagesHostType): void { diff --git a/plugins/terminals/src/node/manager.ts b/plugins/terminals/src/node/manager.ts index 03cc00db..a4bbbd05 100644 --- a/plugins/terminals/src/node/manager.ts +++ b/plugins/terminals/src/node/manager.ts @@ -83,7 +83,7 @@ interface HubTerminalsBridge { update: (session: HubTerminalEntry) => void remove?: (session: { id: string }) => void events?: { - on: (event: 'terminal:session:updated', cb: (session: HubTerminalEntry) => void) => void + on: (event: 'terminals:session:updated', cb: (session: HubTerminalEntry) => void) => void } } @@ -176,7 +176,7 @@ export class TerminalManager { // aggregated sessions appear/update/disappear in this plugin's UI. Guarded // to foreign ids so mirroring our *own* sessions into the hub can't loop. const hub = this.hubTerminals() - hub?.events?.on('terminal:session:updated', (session) => { + hub?.events?.on('terminals:session:updated', (session) => { if (!this.sessions.has(session.id)) this.refreshSessionsState() }) diff --git a/plugins/terminals/test/_utils.ts b/plugins/terminals/test/_utils.ts index 6a69ae75..e1009012 100644 --- a/plugins/terminals/test/_utils.ts +++ b/plugins/terminals/test/_utils.ts @@ -39,7 +39,7 @@ export interface FakeHubTerminals { /** * Minimal stand-in for the hub's `ctx.terminals` aggregation host — a sessions - * map plus a `terminal:session:updated` emitter — so tests can exercise how the + * map plus a `terminals:session:updated` emitter — so tests can exercise how the * terminals plugin surfaces sessions contributed by *other* devframes. */ export function createFakeHubTerminals(): FakeHubTerminals { @@ -50,19 +50,19 @@ export function createFakeHubTerminals(): FakeHubTerminals { events, register(entry) { sessions.set(entry.id, entry) - events.emit('terminal:session:updated', entry) + events.emit('terminals:session:updated', entry) return entry }, update(patch) { const cur = sessions.get(patch.id) if (cur) Object.assign(cur, patch) - events.emit('terminal:session:updated', sessions.get(patch.id) ?? patch) + events.emit('terminals:session:updated', sessions.get(patch.id) ?? patch) }, remove(entry) { const cur = sessions.get(entry.id) sessions.delete(entry.id) - events.emit('terminal:session:updated', cur ?? entry) + events.emit('terminals:session:updated', cur ?? entry) }, } } diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts index 823b3b0e..4d2876e2 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.d.ts @@ -10,6 +10,50 @@ export declare const DEFAULT_CATEGORIES_ORDER: Record; export declare const DEFAULT_STATE_USER_SETTINGS: () => DevframeDocksUserSettings; export declare const DEVFRAMES_HUB_BASE: string; export declare const DOCK_RENDERERS_STATE_KEY: string; +export declare const HUB_EVENTS: { + readonly bus: { + readonly docksEntryUpdated: "docks:entry:updated"; + readonly docksActivate: "docks:activate"; + readonly terminalsSessionUpdated: "terminals:session:updated"; + readonly messagesAdded: "messages:added"; + readonly messagesUpdated: "messages:updated"; + readonly messagesRemoved: "messages:removed"; + readonly messagesCleared: "messages:cleared"; + readonly commandsRegistered: "commands:registered"; + readonly commandsUnregistered: "commands:unregistered"; + }; + readonly rpc: { + readonly docksActivate: "hub:docks:activate"; + readonly commandsExecute: "hub:commands:execute"; + readonly messagesAdd: "hub:messages:add"; + readonly messagesUpdate: "hub:messages:update"; + readonly messagesRemove: "hub:messages:remove"; + readonly messagesClear: "hub:messages:clear"; + readonly terminalsWrite: "hub:terminals:write"; + readonly terminalsResize: "hub:terminals:resize"; + readonly terminalsTerminate: "hub:terminals:terminate"; + readonly terminalsRestart: "hub:terminals:restart"; + readonly terminalsRemove: "hub:terminals:remove"; + }; + readonly broadcast: { + readonly docksActivate: "devframe:docks:activate"; + readonly terminalsUpdated: "devframe:terminals:updated"; + readonly messagesUpdated: "devframe:messages:updated"; + }; + readonly sharedState: { + readonly docks: "devframe:docks"; + readonly docksActive: "devframe:docks:active"; + readonly commands: "devframe:commands"; + readonly userSettings: "devframe:user-settings"; + readonly dockRenderers: "devframe:dock-renderers"; + }; + readonly stream: { + readonly terminals: "devframe:terminals"; + }; + readonly postMessage: { + readonly frameNav: "devframe:frame-nav"; + }; +}; // #endregion // #region Re-exports diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.js index 2990e6b6..e8477ff0 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.js +++ b/tests/__snapshots__/tsnapi/@devframes/hub/constants.snapshot.js @@ -10,6 +10,7 @@ export var DEFAULT_CATEGORIES_ORDER /* const */ export var DEFAULT_STATE_USER_SETTINGS /* const */ export var DEVFRAMES_HUB_BASE /* const */ export var DOCK_RENDERERS_STATE_KEY /* const */ +export var HUB_EVENTS /* const */ // #endregion // #region Re-exports diff --git a/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts index 9aff9c44..76e049e8 100644 --- a/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/@devframes/hub/index.snapshot.d.ts @@ -74,8 +74,8 @@ export interface DevframeCommandsHost { list: () => DevframeServerCommandEntry[]; } export interface DevframeCommandsHostEvents { - 'command:registered': (_: DevframeServerCommandEntry) => void; - 'command:unregistered': (_: string) => void; + 'commands:registered': (_: DevframeServerCommandEntry) => void; + 'commands:unregistered': (_: string) => void; } export interface DevframeDockActivation { dockId: string; @@ -107,8 +107,8 @@ export interface DevframeDocksActiveState { export interface DevframeDocksHost { readonly views: Map; readonly events: EventEmitter<{ - 'dock:entry:updated': (entry: DevframeDockUserEntry) => void; - 'dock:activate': (activation: DevframeDockActivation) => void; + 'docks:entry:updated': (entry: DevframeDockUserEntry) => void; + 'docks:activate': (activation: DevframeDockActivation) => void; }>; register: (_: T, _?: boolean) => { update: (_: Partial) => void; @@ -197,10 +197,10 @@ export interface DevframeMessagesClient extends DevframeMessagesLevelShortcuts { export interface DevframeMessagesHost extends DevframeMessagesLevelShortcuts { readonly entries: Map; readonly events: EventEmitter<{ - 'message:added': (entry: DevframeMessageEntry) => void; - 'message:updated': (entry: DevframeMessageEntry) => void; - 'message:removed': (id: string) => void; - 'message:cleared': () => void; + 'messages:added': (entry: DevframeMessageEntry) => void; + 'messages:updated': (entry: DevframeMessageEntry) => void; + 'messages:removed': (id: string) => void; + 'messages:cleared': () => void; }>; add: (_: DevframeMessageEntryInput) => Promise; update: (_: string, _: Partial) => Promise; @@ -264,7 +264,7 @@ export interface DevframeTerminalSessionBase { export interface DevframeTerminalsHost { readonly sessions: Map; readonly events: EventEmitter<{ - 'terminal:session:updated': (session: DevframeTerminalSession) => void; + 'terminals:session:updated': (session: DevframeTerminalSession) => void; }>; register: (_: DevframeTerminalSession) => DevframeTerminalSession; update: (_: DevframeTerminalSession) => void; diff --git a/tests/__snapshots__/tsnapi/devframe/constants.snapshot.d.ts b/tests/__snapshots__/tsnapi/devframe/constants.snapshot.d.ts index 9938da9f..24f03f7f 100644 --- a/tests/__snapshots__/tsnapi/devframe/constants.snapshot.d.ts +++ b/tests/__snapshots__/tsnapi/devframe/constants.snapshot.d.ts @@ -11,6 +11,32 @@ export declare const DEVFRAME_AUTH_TOKEN_QUERY_PARAM: string; export declare const DEVFRAME_CONNECTION_KEY: string; export declare const DEVFRAME_CONNECTION_META_FILENAME: string; export declare const DEVFRAME_DOCK_IMPORTS_FILENAME: string; +export declare const DEVFRAME_EVENTS: { + readonly bus: { + readonly agentManifestChanged: "agent:manifest:changed"; + readonly agentToolRegistered: "agent:tool:registered"; + readonly agentToolUnregistered: "agent:tool:unregistered"; + readonly agentResourceRegistered: "agent:resource:registered"; + readonly agentResourceUnregistered: "agent:resource:unregistered"; + }; + readonly client: { + readonly isTrustedUpdated: "rpc:is-trusted:updated"; + readonly error: "rpc:error"; + readonly connectionStatus: "connection:status"; + readonly connectionError: "connection:error"; + }; + readonly broadcast: { + readonly authRevoked: "devframe:auth:revoked"; + readonly clientStateUpdated: "devframe:rpc:client-state:updated"; + readonly clientStatePatch: "devframe:rpc:client-state:patch"; + readonly streamingChunk: "devframe:streaming:chunk"; + readonly streamingEnd: "devframe:streaming:end"; + readonly streamingUploadCancel: "devframe:streaming:upload-cancel"; + }; + readonly postMessage: { + readonly remoteAssetsError: "devframe:remote-assets-error"; + }; +}; export declare const DEVFRAME_MCP_ROUTE: string; export declare const DEVFRAME_OTP_URL_PARAM: string; export declare const DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE: string; diff --git a/tests/__snapshots__/tsnapi/devframe/constants.snapshot.js b/tests/__snapshots__/tsnapi/devframe/constants.snapshot.js index db97b2c1..e414f894 100644 --- a/tests/__snapshots__/tsnapi/devframe/constants.snapshot.js +++ b/tests/__snapshots__/tsnapi/devframe/constants.snapshot.js @@ -11,6 +11,7 @@ export var DEVFRAME_AUTH_TOKEN_QUERY_PARAM /* const */ export var DEVFRAME_CONNECTION_KEY /* const */ export var DEVFRAME_CONNECTION_META_FILENAME /* const */ export var DEVFRAME_DOCK_IMPORTS_FILENAME /* const */ +export var DEVFRAME_EVENTS /* const */ export var DEVFRAME_MCP_ROUTE /* const */ export var DEVFRAME_OTP_URL_PARAM /* const */ export var DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE /* const */