Files
claudemesh/apps/cli/src/ws/manager.ts
Alejandro Gutiérrez d451fc296e
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
feat: hierarchical group routing + role wiring
broker: expand member groups to ancestor paths at drain time (pull model)
- @flexicar message reaches peers in @flexicar/core, @flexicar/output, etc.
- Resolved at drainForMember — no DB changes, fully backward-compatible
- Any depth: flexicar/team/backend also matches @flexicar and @flexicar/team

cli: wire --role all the way through to session config + env
- Config.role field added
- launch.ts stores role in sessionConfig, passes CLAUDEMESH_ROLE env var
- mcp/server.ts includes role in identity string
- manager.ts auto-joins groups from config on WS connect (--groups flag now works)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 12:09:37 +01:00

64 lines
2.1 KiB
TypeScript

/**
* Process-wide registry of BrokerClient connections, keyed by meshId.
*
* The MCP server lazily starts a client per joined mesh on startup,
* keeps them alive for the life of the process, and uses them to
* service MCP tool calls.
*/
import { BrokerClient } from "./client";
import type { Config, JoinedMesh } from "../state/config";
import { env } from "../env";
const clients = new Map<string, BrokerClient>();
let configDisplayName: string | undefined;
let configGroups: Config["groups"] = [];
/** Ensure a BrokerClient exists + is connecting/open for this mesh. */
export async function ensureClient(mesh: JoinedMesh): Promise<BrokerClient> {
const existing = clients.get(mesh.meshId);
if (existing) return existing;
const client = new BrokerClient(mesh, { debug: env.CLAUDEMESH_DEBUG, displayName: configDisplayName });
clients.set(mesh.meshId, client);
try {
await client.connect();
// Auto-join groups declared at launch time (--groups flag or config).
for (const g of configGroups ?? []) {
try { await client.joinGroup(g.name, g.role); } catch { /* best effort */ }
}
} catch {
// Connect failed → client is in "reconnecting" state, leave it
// wired so tool calls can surface the status.
}
return client;
}
/** Start clients for every joined mesh. Called once on MCP server start. */
export async function startClients(config: Config): Promise<void> {
configDisplayName = config.displayName;
configGroups = config.groups ?? [];
await Promise.allSettled(config.meshes.map(ensureClient));
}
/** Look up a client by mesh slug (human-friendly) or meshId. */
export function findClient(needle: string): BrokerClient | null {
// Try meshId first, then slug.
const byId = clients.get(needle);
if (byId) return byId;
for (const c of clients.values()) {
if (c.meshSlug === needle) return c;
}
return null;
}
/** All clients across all meshes. */
export function allClients(): BrokerClient[] {
return [...clients.values()];
}
/** Close every client (shutdown hook). */
export function stopAll(): void {
for (const c of clients.values()) c.close();
clients.clear();
}