Shared core runtime
createCoreRuntime() is the common client composition behind the browser, console, web, and mobile packages. Use it directly when a new platform can supply the boundaries that a specialized factory would normally own: persistent identity, WebSocket construction, HTTP return, lifecycle integration, platform handlers, and any local policy or state reporting.
import { createCoreRuntime } from '@cheetah/core';
const runtime = await createCoreRuntime({
serverUrl: 'wss://api.example.com/cheetah/ws',
authToken: await acquireHostCredential(),
identityProvider,
webSocketFactory,
responseSink,
clientType: 'warehouse-terminal',
localPolicySource,
stateReporter,
});
runtime.registerHandler(handler, descriptor);
const helloAck = await runtime.start();
Construction is asynchronous because identity may come from platform storage. It does not connect. Register the complete action set after construction and before start().
Configuration boundaries
| Concern | Core option | Default or requirement |
|---|---|---|
| WebSocket edge | serverUrl | required; a strict remote edge requires wss:; local:// is reserved for an explicit local transport composition |
| HTTP return | restEndpoint or negotiated endpoint | optional initially; a normal remote runtime needs one before it can publish outcomes |
| server credential | authToken | required; sent as the hello wire field auth_token |
| protocol | protocolVersion | package version by default; an explicit value must be a non-empty trimmed exact server-supported version |
| identity | identityProvider | generated in memory when absent, so it does not survive reconstruction |
| WebSocket adapter | webSocketFactory | browser-style WebSocket factory |
| command timeout | defaultTimeoutMs | 20 seconds when neither command nor runtime overrides it |
| lease lifetime | defaultLeaseTtlMs | 30 seconds |
| telemetry | telemetrySink | console sink unless enableLogging is false, then a null sink |
| capture | captureProvider | null provider |
| local authority | localPolicySource, approvalProvider | no local policy source or approval provider |
| payload references | payloadResolver, autoResolvePayloads | disabled unless both the provider and opt-in are supplied |
| encrypted envelopes | payloadEncryptor, autoDecryptPayloads | disabled unless both the provider and opt-in are supplied |
| local caching | cacheProvider | absent; cache_data returns no_cache |
The REST endpoint, payload base URL, heartbeat interval, telemetry endpoint, and selected client configuration can be learned or updated from an accepted handshake when the supplied providers support those capabilities. Each accepted reconnect handshake is applied as a new generation. Commands remain buffered until required connection preparation succeeds, and a superseded preparation cannot overwrite newer negotiated state.
Startup is a one-attempt boundary
One runtime object permits one call to start(). Starting it twice, restarting it after stop(), or retrying a failed start raises RuntimeNotStartableError; construct a fresh runtime instead. A startup failure rolls back the dispatcher, transport, state subscription, and other work already started by core.
Handler registration also closes as soon as startup begins. A later call to registerHandler() raises RuntimeHandlerRegistrationClosedError. This keeps executable handlers, the action registry, and every reconnect advertisement aligned. To change the advertised action set, build a replacement runtime.
stop() is idempotent. It unsubscribes and disposes the state reporter, stops command dispatch, disconnects transport, records the final runtime event, and flushes the telemetry sink. It does not own arbitrary platform objects that were never passed through one of these interfaces.
Reconnection repeats preparation, not construction
The transport sends one hello per connection and retains the runtime's client ID, instance ID, credential, protocol version, and action advertisement across reconnects. Its local defaults are a 10-second handshake timeout, 1-second initial reconnect backoff, and 30-second maximum backoff. The server may replace the 15-second local heartbeat interval for the current accepted connection.
Authentication and unsupported-protocol rejections are fatal for that configured runtime; retrying the same credential or exact version cannot repair them. Ordinary transport loss uses exponential reconnection. An accepted reconnect reapplies server configuration, resets negotiated endpoints when appropriate, and triggers a best-effort connection state report.
This is transport reconnection by one runtime instance. A destroyed process or service worker must construct a new runtime, restore its stable client identity, and use a new instance identity.
The dispatcher owns the common execution pipeline
Core registers ping, echo, get_actions, get_policy, get_config, and cache_data, then adds platform or application handlers. For a remote command it acknowledges a valid frame before running the handler. The execution path can then:
- resolve opted-in payload references and parser definitions;
- decrypt opted-in envelopes;
- validate descriptor-declared argument presence and basic types;
- combine central authorization with local policy and approval;
- acquire a lease for a targeted tab or window;
- execute with progress, an abort signal, and lease-validity checks;
- perform supported post-command capture;
- publish a terminal result or error and release the lease.
A handler returning null, losing its lease, or observing cancellation can suppress a late terminal response. Cancellation is cooperative: core can signal and fence, but it cannot undo an external effect already performed by platform code.
Local command ingress uses the same handlers
runtime.localCommands submits work directly to the same dispatcher without a remote WebSocket ACK. It is useful for client-local applications and tests that need the real validation, policy, lease, handler, progress, and result path. A supplied local response or history sink decides where those outcomes go. Local ingress is not a second network protocol and does not impersonate server authentication.