@essentialai/cogent-bridge
Version:
Cogent Bridge — cross-agent comms for Claude Code, OpenAI Codex and Slack. Codex users: install with `curl -fsSL https://cogent.tools/install.sh | sh`, then start with `cogent-codex` for real-time peer wake.
90 lines • 3.81 kB
JavaScript
import { FileBackend } from "./file-backend.js";
import { HttpBackend } from "./http-backend.js";
import { HttpClient } from "../cloud/http-client.js";
/**
* Singleton accessor for the active storage backend.
*
* Follows the same pattern as config.ts: explicit init for production,
* lazy fallback for tests. getBackend() auto-creates a FileBackend when
* no backend has been set, ensuring existing tests work without modification.
*/
let _backend = null;
/** Set the singleton backend (called during server startup). */
export function initBackend(backend) {
_backend = backend;
}
/**
* Get the active storage backend.
*
* If no backend has been set via initBackend(), lazy-initializes a FileBackend.
* This makes existing tests pass without modification -- they never call
* initBackend(), so getBackend() auto-creates a FileBackend that delegates
* to the same service functions the tests already mock via vi.mock().
*/
export function getBackend() {
if (!_backend) {
_backend = new FileBackend();
}
return _backend;
}
/** Reset the singleton to null (for test isolation). */
export function resetBackend() {
_backend = null;
}
/** Check whether a value looks like a cloud endpoint (http:// or https://). */
export function isCloudEndpoint(value) {
return /^https?:\/\//i.test(value);
}
/**
* Check whether the active backend is cloud-mode (HttpBackend).
* Unlike checking config.COGENT_ENDPOINT, this reflects runtime state
* after reinitCloudBackend() may have swapped the backend at runtime.
*/
export function isCloudMode() {
return _backend instanceof HttpBackend;
}
/**
* SECURITY — cloud-configured but NOT yet joined ("deferred").
*
* True when the bridge is cloud-CONFIGURED (endpoint is an http(s) relay URL)
* yet the active backend is still the local FileBackend placeholder, i.e. the
* agent has not completed a join/register and belongs to NO channel yet
* (startup.ts deferred branch installs `createBackend(undefined)` until
* reinitCloudBackend swaps in the HttpBackend).
*
* In this state, channel-scoped reads (`cogent_get_history` / `cogent_list_peers`)
* MUST return empty rather than read the host-global `~/.cogent/cogent-state.json`,
* which can contain OTHER local/deferred agents' peers and message bodies on the
* same host — surfacing them would disclose another channel's content to an agent
* that is not on it. `endpoint` is passed in (this module deliberately does not
* import config; the caller supplies `getConfig().COGENT_ENDPOINT`).
*
* Genuine local mode (COGENT_LOCAL / non-http endpoint → `endpoint` undefined or
* non-URL) returns false: its single-host shared-file semantics are unchanged.
*/
export function isCloudDeferred(endpoint) {
return !!endpoint && isCloudEndpoint(endpoint) && !isCloudMode();
}
/**
* Factory: create a backend from an optional endpoint string.
*
* - If endpoint is an HTTP/HTTPS URL, creates an HttpBackend for cloud mode.
* Requires sessionId and token (from env vars or tool-based session setup).
* - Otherwise returns a FileBackend for local file-based state.
*
* Note: Does NOT import config.ts to avoid circular dependency risk
* (decision from 02B-01). Caller passes params explicitly.
*/
export function createBackend(endpoint, sessionId, token) {
if (endpoint && isCloudEndpoint(endpoint)) {
if (!sessionId || !token) {
throw new Error("Cloud mode requires COGENT_SESSION_ID and COGENT_SECRET " +
"(or use create-session/join-session tools). " +
`Got endpoint: ${endpoint}`);
}
const httpClient = new HttpClient(endpoint, token);
return new HttpBackend(httpClient, sessionId);
}
return new FileBackend();
}
//# sourceMappingURL=backend-provider.js.map