feat: broadcast system notifications on peer join/leave
When a peer connects or disconnects, the broker now broadcasts a system push (subtype: "system") to all other peers in the same mesh. The CLI formats these as [system] channel notifications so AI sessions can react to topology changes without polling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -990,6 +990,39 @@ Your message mode is "${messageMode}".
|
||||
client.onPush(async (msg) => {
|
||||
if (messageMode === "off") return;
|
||||
|
||||
// System events (peer join/leave) — always push, regardless of mode.
|
||||
if (msg.subtype === "system" && msg.event) {
|
||||
const eventName = msg.event;
|
||||
const data = msg.eventData ?? {};
|
||||
let content: string;
|
||||
if (eventName === "peer_joined") {
|
||||
content = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
|
||||
} else if (eventName === "peer_left") {
|
||||
content = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
|
||||
} else {
|
||||
content = `[system] ${eventName}: ${JSON.stringify(data)}`;
|
||||
}
|
||||
try {
|
||||
await server.notification({
|
||||
method: "notifications/claude/channel",
|
||||
params: {
|
||||
content,
|
||||
meta: {
|
||||
kind: "system",
|
||||
event: eventName,
|
||||
mesh_slug: client.meshSlug,
|
||||
mesh_id: client.meshId,
|
||||
...(Object.keys(data).length > 0 ? { eventData: data } : {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
process.stderr.write(`[claudemesh] system: ${content}\n`);
|
||||
} catch (pushErr) {
|
||||
process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}\n`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const fromPubkey = msg.senderPubkey || "";
|
||||
const fromName = fromPubkey
|
||||
? await resolvePeerName(client, fromPubkey)
|
||||
|
||||
@@ -51,8 +51,13 @@ export interface InboundPush {
|
||||
/** Hint for UI: "direct" (crypto_box), "channel"/"broadcast"
|
||||
* (plaintext for now). */
|
||||
kind: "direct" | "broadcast" | "channel" | "unknown";
|
||||
/** Optional semantic tag — "reminder" when fired by the scheduler. */
|
||||
subtype?: "reminder";
|
||||
/** Optional semantic tag — "reminder" when fired by the scheduler,
|
||||
* "system" for broker-originated topology events. */
|
||||
subtype?: "reminder" | "system";
|
||||
/** Machine-readable event name (e.g. "peer_joined", "peer_left"). */
|
||||
event?: string;
|
||||
/** Structured payload for the event. */
|
||||
eventData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
type PushHandler = (msg: InboundPush) => void;
|
||||
@@ -937,7 +942,9 @@ export class BrokerClient {
|
||||
receivedAt: new Date().toISOString(),
|
||||
plaintext,
|
||||
kind,
|
||||
...(msg.subtype ? { subtype: msg.subtype as "reminder" } : {}),
|
||||
...(msg.subtype ? { subtype: msg.subtype as "reminder" | "system" } : {}),
|
||||
...(msg.event ? { event: String(msg.event) } : {}),
|
||||
...(msg.eventData ? { eventData: msg.eventData as Record<string, unknown> } : {}),
|
||||
};
|
||||
this.pushBuffer.push(push);
|
||||
if (this.pushBuffer.length > 500) this.pushBuffer.shift();
|
||||
|
||||
Reference in New Issue
Block a user