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>
64 lines
2.1 KiB
TypeScript
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();
|
|
}
|