fix(broker): echo _reqId in all WS responses for correlation ID routing

Extract _reqId from incoming WS messages and include it in every direct
response sendToPeer call and sendError call. Clients can now match
responses to requests by ID instead of relying on FIFO ordering.
Old clients without _reqId are unaffected (field simply omitted).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-07 14:28:30 +01:00
parent 8f925d9a9e
commit d0fa1c028f
2 changed files with 80 additions and 16 deletions

View File

@@ -547,8 +547,9 @@ function sendError(
code: string, code: string,
message: string, message: string,
id?: string, id?: string,
reqId?: string,
): void { ): void {
const err: WSServerMessage = { type: "error", code, message, id }; const err: WSServerMessage = { type: "error", code, message, id, ...(reqId ? { _reqId: reqId } : {}) };
try { try {
ws.send(JSON.stringify(err)); ws.send(JSON.stringify(err));
} catch { } catch {
@@ -727,6 +728,7 @@ function handleConnection(ws: WebSocket): void {
ws.on("message", async (raw) => { ws.on("message", async (raw) => {
try { try {
const msg = JSON.parse(raw.toString()) as WSClientMessage; const msg = JSON.parse(raw.toString()) as WSClientMessage;
const _reqId = (msg as any)._reqId as string | undefined;
if (msg.type === "hello") { if (msg.type === "hello") {
const result = await handleHello(ws, msg); const result = await handleHello(ws, msg);
if (!result) return; if (!result) return;
@@ -776,6 +778,7 @@ function handleConnection(ws: WebSocket): void {
sessionId: p.sessionId, sessionId: p.sessionId,
connectedAt: p.connectedAt.toISOString(), connectedAt: p.connectedAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}; };
conn.ws.send(JSON.stringify(resp)); conn.ws.send(JSON.stringify(resp));
log.info("ws list_peers", { log.info("ws list_peers", {
@@ -862,6 +865,7 @@ function handleConnection(ws: WebSocket): void {
value: stateEntry.value, value: stateEntry.value,
updatedBy: stateEntry.updatedBy, updatedBy: stateEntry.updatedBy,
updatedAt: stateEntry.updatedAt.toISOString(), updatedAt: stateEntry.updatedAt.toISOString(),
...(_reqId ? { _reqId } : {}),
}); });
} else { } else {
sendToPeer(presenceId, { sendToPeer(presenceId, {
@@ -870,6 +874,7 @@ function handleConnection(ws: WebSocket): void {
value: null, value: null,
updatedBy: "", updatedBy: "",
updatedAt: "", updatedAt: "",
...(_reqId ? { _reqId } : {}),
}); });
} }
log.info("ws get_state", { log.info("ws get_state", {
@@ -889,6 +894,7 @@ function handleConnection(ws: WebSocket): void {
updatedBy: e.updatedBy, updatedBy: e.updatedBy,
updatedAt: e.updatedAt.toISOString(), updatedAt: e.updatedAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws list_state", { log.info("ws list_state", {
presence_id: presenceId, presence_id: presenceId,
@@ -911,6 +917,7 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "memory_stored", type: "memory_stored",
id: memoryId, id: memoryId,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws remember", { log.info("ws remember", {
presence_id: presenceId, presence_id: presenceId,
@@ -930,6 +937,7 @@ function handleConnection(ws: WebSocket): void {
rememberedBy: m.rememberedBy, rememberedBy: m.rememberedBy,
rememberedAt: m.rememberedAt.toISOString(), rememberedAt: m.rememberedAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws recall", { log.info("ws recall", {
presence_id: presenceId, presence_id: presenceId,
@@ -946,6 +954,7 @@ function handleConnection(ws: WebSocket): void {
id: fg.memoryId, id: fg.memoryId,
messageId: fg.memoryId, messageId: fg.memoryId,
queued: false, queued: false,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws forget", { log.info("ws forget", {
presence_id: presenceId, presence_id: presenceId,
@@ -957,7 +966,7 @@ function handleConnection(ws: WebSocket): void {
const gf = msg as Extract<WSClientMessage, { type: "get_file" }>; const gf = msg as Extract<WSClientMessage, { type: "get_file" }>;
const file = await getFile(conn.meshId, gf.fileId); const file = await getFile(conn.meshId, gf.fileId);
if (!file) { if (!file) {
sendError(conn.ws, "not_found", "file not found"); sendError(conn.ws, "not_found", "file not found", undefined, _reqId);
break; break;
} }
// Access control: if targetSpec is set, verify peer matches // Access control: if targetSpec is set, verify peer matches
@@ -967,7 +976,7 @@ function handleConnection(ws: WebSocket): void {
file.targetSpec === conn.sessionPubkey || file.targetSpec === conn.sessionPubkey ||
file.targetSpec === "*"; file.targetSpec === "*";
if (!matches) { if (!matches) {
sendError(conn.ws, "forbidden", "file not targeted at you"); sendError(conn.ws, "forbidden", "file not targeted at you", undefined, _reqId);
break; break;
} }
} }
@@ -980,7 +989,7 @@ function handleConnection(ws: WebSocket): void {
const isOwner = !!(file.ownerPubkey && peerPubkey === file.ownerPubkey); const isOwner = !!(file.ownerPubkey && peerPubkey === file.ownerPubkey);
sealedKey = peerPubkey ? await getFileKey(gf.fileId, peerPubkey) : null; sealedKey = peerPubkey ? await getFileKey(gf.fileId, peerPubkey) : null;
if (!sealedKey && !isOwner) { if (!sealedKey && !isOwner) {
sendError(conn.ws, "forbidden", "no decryption key for this file"); sendError(conn.ws, "forbidden", "no decryption key for this file", undefined, _reqId);
break; break;
} }
} }
@@ -1007,6 +1016,7 @@ function handleConnection(ws: WebSocket): void {
name: file.name, name: file.name,
encrypted: file.encrypted, encrypted: file.encrypted,
sealedKey: sealedKey ?? undefined, sealedKey: sealedKey ?? undefined,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws get_file", { log.info("ws get_file", {
presence_id: presenceId, presence_id: presenceId,
@@ -1029,6 +1039,7 @@ function handleConnection(ws: WebSocket): void {
persistent: f.persistent, persistent: f.persistent,
encrypted: f.encrypted, encrypted: f.encrypted,
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws list_files", { log.info("ws list_files", {
presence_id: presenceId, presence_id: presenceId,
@@ -1047,6 +1058,7 @@ function handleConnection(ws: WebSocket): void {
peerName: a.peerName, peerName: a.peerName,
accessedAt: a.accessedAt.toISOString(), accessedAt: a.accessedAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws file_status", { log.info("ws file_status", {
presence_id: presenceId, presence_id: presenceId,
@@ -1058,16 +1070,16 @@ function handleConnection(ws: WebSocket): void {
const gfa = msg as { type: "grant_file_access"; fileId: string; peerPubkey: string; sealedKey: string }; const gfa = msg as { type: "grant_file_access"; fileId: string; peerPubkey: string; sealedKey: string };
const file = await getFile(conn.meshId, gfa.fileId); const file = await getFile(conn.meshId, gfa.fileId);
if (!file) { if (!file) {
sendError(conn.ws, "not_found", "file not found"); sendError(conn.ws, "not_found", "file not found", undefined, _reqId);
break; break;
} }
const requestorPubkey = conn.sessionPubkey ?? conn.memberPubkey; const requestorPubkey = conn.sessionPubkey ?? conn.memberPubkey;
if (file.ownerPubkey && file.ownerPubkey !== requestorPubkey) { if (file.ownerPubkey && file.ownerPubkey !== requestorPubkey) {
sendError(conn.ws, "forbidden", "only the file owner can grant access"); sendError(conn.ws, "forbidden", "only the file owner can grant access", undefined, _reqId);
break; break;
} }
await grantFileKey(gfa.fileId, gfa.peerPubkey, gfa.sealedKey, requestorPubkey ?? undefined); await grantFileKey(gfa.fileId, gfa.peerPubkey, gfa.sealedKey, requestorPubkey ?? undefined);
sendToPeer(presenceId, { type: "grant_file_access_ok", fileId: gfa.fileId, peerPubkey: gfa.peerPubkey }); sendToPeer(presenceId, { type: "grant_file_access_ok", fileId: gfa.fileId, peerPubkey: gfa.peerPubkey, ...(_reqId ? { _reqId } : {}) });
log.info("ws grant_file_access", { presence_id: presenceId, file_id: gfa.fileId, peer: gfa.peerPubkey }); log.info("ws grant_file_access", { presence_id: presenceId, file_id: gfa.fileId, peer: gfa.peerPubkey });
break; break;
} }
@@ -1079,6 +1091,7 @@ function handleConnection(ws: WebSocket): void {
id: df.fileId, id: df.fileId,
messageId: df.fileId, messageId: df.fileId,
queued: false, queued: false,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws delete_file", { log.info("ws delete_file", {
presence_id: presenceId, presence_id: presenceId,
@@ -1099,7 +1112,7 @@ function handleConnection(ws: WebSocket): void {
.from(messageQueue) .from(messageQueue)
.where(eq(messageQueue.id, ms.messageId)); .where(eq(messageQueue.id, ms.messageId));
if (!mqRow || mqRow.meshId !== conn.meshId) { if (!mqRow || mqRow.meshId !== conn.meshId) {
sendError(conn.ws, "not_found", "message not found"); sendError(conn.ws, "not_found", "message not found", undefined, _reqId);
break; break;
} }
// Build per-recipient status from connected peers. // Build per-recipient status from connected peers.
@@ -1143,6 +1156,7 @@ function handleConnection(ws: WebSocket): void {
delivered: !!mqRow.deliveredAt, delivered: !!mqRow.deliveredAt,
deliveredAt: mqRow.deliveredAt?.toISOString() ?? null, deliveredAt: mqRow.deliveredAt?.toISOString() ?? null,
recipients, recipients,
...(_reqId ? { _reqId } : {}),
}; };
sendToPeer(presenceId, resp); sendToPeer(presenceId, resp);
log.info("ws message_status", { log.info("ws message_status", {
@@ -1201,6 +1215,7 @@ function handleConnection(ws: WebSocket): void {
tags: c.tags, tags: c.tags,
updatedAt: c.updatedAt.toISOString(), updatedAt: c.updatedAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws get_context", { log.info("ws get_context", {
presence_id: presenceId, presence_id: presenceId,
@@ -1219,6 +1234,7 @@ function handleConnection(ws: WebSocket): void {
tags: c.tags, tags: c.tags,
updatedAt: c.updatedAt.toISOString(), updatedAt: c.updatedAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws list_contexts", { log.info("ws list_contexts", {
presence_id: presenceId, presence_id: presenceId,
@@ -1243,6 +1259,7 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "task_created", type: "task_created",
id: taskId, id: taskId,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws create_task", { log.info("ws create_task", {
presence_id: presenceId, presence_id: presenceId,
@@ -1263,7 +1280,7 @@ function handleConnection(ws: WebSocket): void {
memberInfo?.displayName, memberInfo?.displayName,
); );
if (!claimed) { if (!claimed) {
sendError(conn.ws, "task_not_claimable", "task is not open or does not exist"); sendError(conn.ws, "task_not_claimable", "task is not open or does not exist", undefined, _reqId);
break; break;
} }
// Return updated task list so caller sees the change. // Return updated task list so caller sees the change.
@@ -1281,6 +1298,7 @@ function handleConnection(ws: WebSocket): void {
tags: t.tags, tags: t.tags,
createdAt: t.createdAt.toISOString(), createdAt: t.createdAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws claim_task", { log.info("ws claim_task", {
presence_id: presenceId, presence_id: presenceId,
@@ -1296,7 +1314,7 @@ function handleConnection(ws: WebSocket): void {
cpt.result, cpt.result,
); );
if (!completed) { if (!completed) {
sendError(conn.ws, "task_not_found", "task not found in this mesh"); sendError(conn.ws, "task_not_found", "task not found in this mesh", undefined, _reqId);
break; break;
} }
// Return updated task list. // Return updated task list.
@@ -1314,6 +1332,7 @@ function handleConnection(ws: WebSocket): void {
tags: t.tags, tags: t.tags,
createdAt: t.createdAt.toISOString(), createdAt: t.createdAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws complete_task", { log.info("ws complete_task", {
presence_id: presenceId, presence_id: presenceId,
@@ -1337,6 +1356,7 @@ function handleConnection(ws: WebSocket): void {
tags: t.tags, tags: t.tags,
createdAt: t.createdAt.toISOString(), createdAt: t.createdAt.toISOString(),
})), })),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws list_tasks", { log.info("ws list_tasks", {
presence_id: presenceId, presence_id: presenceId,
@@ -1362,6 +1382,7 @@ function handleConnection(ws: WebSocket): void {
type: "stream_created", type: "stream_created",
id: streamId, id: streamId,
name: cs.name, name: cs.name,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws create_stream", { log.info("ws create_stream", {
presence_id: presenceId, presence_id: presenceId,
@@ -1376,7 +1397,7 @@ function handleConnection(ws: WebSocket): void {
if (!streamSubscriptions.has(key)) if (!streamSubscriptions.has(key))
streamSubscriptions.set(key, new Set()); streamSubscriptions.set(key, new Set());
streamSubscriptions.get(key)!.add(presenceId); streamSubscriptions.get(key)!.add(presenceId);
sendToPeer(presenceId, { type: "subscribed", stream: sub.stream }); sendToPeer(presenceId, { type: "subscribed", stream: sub.stream, ...(_reqId ? { _reqId } : {}) });
log.info("ws subscribe", { log.info("ws subscribe", {
presence_id: presenceId, presence_id: presenceId,
stream: sub.stream, stream: sub.stream,
@@ -1435,6 +1456,7 @@ function handleConnection(ws: WebSocket): void {
subscriberCount: streamSubscriptions.get(key)?.size ?? 0, subscriberCount: streamSubscriptions.get(key)?.size ?? 0,
}; };
}), }),
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws list_streams", { log.info("ws list_streams", {
presence_id: presenceId, presence_id: presenceId,
@@ -1476,6 +1498,7 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "vector_stored", type: "vector_stored",
id: pointId, id: pointId,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws vector_store", { log.info("ws vector_store", {
presence_id: presenceId, presence_id: presenceId,
@@ -1483,7 +1506,7 @@ function handleConnection(ws: WebSocket): void {
point_id: pointId, point_id: pointId,
}); });
} catch (e) { } catch (e) {
sendError(conn.ws, "vector_error", e instanceof Error ? e.message : String(e)); sendError(conn.ws, "vector_error", e instanceof Error ? e.message : String(e), undefined, _reqId);
} }
break; break;
} }
@@ -1518,12 +1541,14 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "vector_results", type: "vector_results",
results: matches, results: matches,
...(_reqId ? { _reqId } : {}),
}); });
} catch { } catch {
// Collection may not exist yet — return empty results. // Collection may not exist yet — return empty results.
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "vector_results", type: "vector_results",
results: [], results: [],
...(_reqId ? { _reqId } : {}),
}); });
} }
log.info("ws vector_search", { log.info("ws vector_search", {
@@ -1549,6 +1574,7 @@ function handleConnection(ws: WebSocket): void {
id: vd.id, id: vd.id,
messageId: vd.id, messageId: vd.id,
queued: false, queued: false,
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws vector_delete", { log.info("ws vector_delete", {
presence_id: presenceId, presence_id: presenceId,
@@ -1568,11 +1594,13 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "collection_list", type: "collection_list",
collections: meshCollections, collections: meshCollections,
...(_reqId ? { _reqId } : {}),
}); });
} catch { } catch {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "collection_list", type: "collection_list",
collections: [], collections: [],
...(_reqId ? { _reqId } : {}),
}); });
} }
log.info("ws list_collections", { log.info("ws list_collections", {
@@ -1607,9 +1635,10 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "graph_result", type: "graph_result",
records: gqRecords, records: gqRecords,
...(_reqId ? { _reqId } : {}),
}); });
} catch (gqErr) { } catch (gqErr) {
sendError(conn.ws, "graph_error", gqErr instanceof Error ? gqErr.message : String(gqErr)); sendError(conn.ws, "graph_error", gqErr instanceof Error ? gqErr.message : String(gqErr), undefined, _reqId);
} finally { } finally {
await gqSession.close(); await gqSession.close();
} }
@@ -1641,9 +1670,10 @@ function handleConnection(ws: WebSocket): void {
sendToPeer(presenceId, { sendToPeer(presenceId, {
type: "graph_result", type: "graph_result",
records: geRecords, records: geRecords,
...(_reqId ? { _reqId } : {}),
}); });
} catch (geErr) { } catch (geErr) {
sendError(conn.ws, "graph_error", geErr instanceof Error ? geErr.message : String(geErr)); sendError(conn.ws, "graph_error", geErr instanceof Error ? geErr.message : String(geErr), undefined, _reqId);
} finally { } finally {
await geSession.close(); await geSession.close();
} }
@@ -1660,12 +1690,14 @@ function handleConnection(ws: WebSocket): void {
const mq = msg as Extract<WSClientMessage, { type: "mesh_query" }>; const mq = msg as Extract<WSClientMessage, { type: "mesh_query" }>;
try { try {
const result = await meshQuery(conn.meshId, mq.sql); const result = await meshQuery(conn.meshId, mq.sql);
sendToPeer(presenceId, { type: "mesh_query_result", ...result }); sendToPeer(presenceId, { type: "mesh_query_result", ...result, ...(_reqId ? { _reqId } : {}) });
} catch (e) { } catch (e) {
sendError( sendError(
conn.ws, conn.ws,
"query_error", "query_error",
e instanceof Error ? e.message : String(e), e instanceof Error ? e.message : String(e),
undefined,
_reqId,
); );
} }
log.info("ws mesh_query", { log.info("ws mesh_query", {
@@ -1683,12 +1715,15 @@ function handleConnection(ws: WebSocket): void {
columns: [], columns: [],
rows: [], rows: [],
rowCount: result.rowCount, rowCount: result.rowCount,
...(_reqId ? { _reqId } : {}),
}); });
} catch (e) { } catch (e) {
sendError( sendError(
conn.ws, conn.ws,
"execute_error", "execute_error",
e instanceof Error ? e.message : String(e), e instanceof Error ? e.message : String(e),
undefined,
_reqId,
); );
} }
log.info("ws mesh_execute", { log.info("ws mesh_execute", {
@@ -1700,12 +1735,14 @@ function handleConnection(ws: WebSocket): void {
case "mesh_schema": { case "mesh_schema": {
try { try {
const tables = await meshSchema(conn.meshId); const tables = await meshSchema(conn.meshId);
sendToPeer(presenceId, { type: "mesh_schema_result", tables }); sendToPeer(presenceId, { type: "mesh_schema_result", tables, ...(_reqId ? { _reqId } : {}) });
} catch (e) { } catch (e) {
sendError( sendError(
conn.ws, conn.ws,
"schema_error", "schema_error",
e instanceof Error ? e.message : String(e), e instanceof Error ? e.message : String(e),
undefined,
_reqId,
); );
} }
log.info("ws mesh_schema", { presence_id: presenceId }); log.info("ws mesh_schema", { presence_id: presenceId });
@@ -1746,6 +1783,7 @@ function handleConnection(ws: WebSocket): void {
collections: [], collections: [],
yourName: selfPeer?.displayName ?? "unknown", yourName: selfPeer?.displayName ?? "unknown",
yourGroups: peerConn?.groups ?? [], yourGroups: peerConn?.groups ?? [],
...(_reqId ? { _reqId } : {}),
}); });
log.info("ws mesh_info", { presence_id: presenceId }); log.info("ws mesh_info", { presence_id: presenceId });
break; break;

View File

@@ -161,6 +161,7 @@ export interface WSAckMessage {
id: string; // echoes client-side correlation id id: string; // echoes client-side correlation id
messageId: string; messageId: string;
queued: boolean; queued: boolean;
_reqId?: string;
} }
/** Broker → client: hello handshake acknowledgement. */ /** Broker → client: hello handshake acknowledgement. */
@@ -182,6 +183,7 @@ export interface WSPeersListMessage {
sessionId: string; sessionId: string;
connectedAt: string; connectedAt: string;
}>; }>;
_reqId?: string;
} }
/** Broker → client: a state key was changed by another peer. */ /** Broker → client: a state key was changed by another peer. */
@@ -199,6 +201,7 @@ export interface WSStateResultMessage {
value: unknown; value: unknown;
updatedAt: string; updatedAt: string;
updatedBy: string; updatedBy: string;
_reqId?: string;
} }
/** Broker → client: response to list_state. */ /** Broker → client: response to list_state. */
@@ -210,12 +213,14 @@ export interface WSStateListMessage {
updatedBy: string; updatedBy: string;
updatedAt: string; updatedAt: string;
}>; }>;
_reqId?: string;
} }
/** Broker → client: acknowledgement for a remember. */ /** Broker → client: acknowledgement for a remember. */
export interface WSMemoryStoredMessage { export interface WSMemoryStoredMessage {
type: "memory_stored"; type: "memory_stored";
id: string; id: string;
_reqId?: string;
} }
/** Broker → client: response to recall. */ /** Broker → client: response to recall. */
@@ -228,6 +233,7 @@ export interface WSMemoryResultsMessage {
rememberedBy: string; rememberedBy: string;
rememberedAt: string; rememberedAt: string;
}>; }>;
_reqId?: string;
} }
// --- Vector storage messages --- // --- Vector storage messages ---
@@ -299,6 +305,7 @@ export interface WSMeshSchemaMessage {
export interface WSVectorStoredMessage { export interface WSVectorStoredMessage {
type: "vector_stored"; type: "vector_stored";
id: string; id: string;
_reqId?: string;
} }
/** Broker → client: vector search results. */ /** Broker → client: vector search results. */
@@ -310,18 +317,21 @@ export interface WSVectorResultsMessage {
score: number; score: number;
metadata?: Record<string, unknown>; metadata?: Record<string, unknown>;
}>; }>;
_reqId?: string;
} }
/** Broker → client: list of vector collections. */ /** Broker → client: list of vector collections. */
export interface WSCollectionListMessage { export interface WSCollectionListMessage {
type: "collection_list"; type: "collection_list";
collections: string[]; collections: string[];
_reqId?: string;
} }
/** Broker → client: graph query results. */ /** Broker → client: graph query results. */
export interface WSGraphResultMessage { export interface WSGraphResultMessage {
type: "graph_result"; type: "graph_result";
records: Array<Record<string, unknown>>; records: Array<Record<string, unknown>>;
_reqId?: string;
} }
/** Broker → client: mesh SQL query results. */ /** Broker → client: mesh SQL query results. */
@@ -330,6 +340,7 @@ export interface WSMeshQueryResultMessage {
columns: string[]; columns: string[];
rows: Array<Record<string, unknown>>; rows: Array<Record<string, unknown>>;
rowCount: number; rowCount: number;
_reqId?: string;
} }
/** Broker → client: mesh schema introspection results. */ /** Broker → client: mesh schema introspection results. */
@@ -339,6 +350,7 @@ export interface WSMeshSchemaResultMessage {
name: string; name: string;
columns: Array<{ name: string; type: string; nullable: boolean }>; columns: Array<{ name: string; type: string; nullable: boolean }>;
}>; }>;
_reqId?: string;
} }
/** Client → broker: get full mesh overview. */ /** Client → broker: get full mesh overview. */
@@ -361,6 +373,7 @@ export interface WSMeshInfoResultMessage {
collections: string[]; collections: string[];
yourName: string; yourName: string;
yourGroups: Array<{ name: string; role?: string }>; yourGroups: Array<{ name: string; role?: string }>;
_reqId?: string;
} }
/** Client → broker: check delivery status of a message. */ /** Client → broker: check delivery status of a message. */
@@ -381,6 +394,7 @@ export interface WSMessageStatusResultMessage {
pubkey: string; pubkey: string;
status: "delivered" | "held" | "disconnected"; status: "delivered" | "held" | "disconnected";
}>; }>;
_reqId?: string;
} }
// --- File sharing messages --- // --- File sharing messages ---
@@ -426,6 +440,7 @@ export interface WSFileUrlMessage {
name: string; name: string;
encrypted?: boolean; encrypted?: boolean;
sealedKey?: string; sealedKey?: string;
_reqId?: string;
} }
/** Broker → client: list of files in the mesh. */ /** Broker → client: list of files in the mesh. */
@@ -441,6 +456,7 @@ export interface WSFileListMessage {
persistent: boolean; persistent: boolean;
encrypted: boolean; encrypted: boolean;
}>; }>;
_reqId?: string;
} }
/** Broker → client: acknowledgement for grant_file_access. */ /** Broker → client: acknowledgement for grant_file_access. */
@@ -448,6 +464,7 @@ export interface WSGrantFileAccessOkMessage {
type: "grant_file_access_ok"; type: "grant_file_access_ok";
fileId: string; fileId: string;
peerPubkey: string; peerPubkey: string;
_reqId?: string;
} }
/** Broker → client: access log for a file. */ /** Broker → client: access log for a file. */
@@ -458,6 +475,7 @@ export interface WSFileStatusResultMessage {
peerName: string; peerName: string;
accessedAt: string; accessedAt: string;
}>; }>;
_reqId?: string;
} }
// --- Context sharing messages --- // --- Context sharing messages ---
@@ -499,6 +517,7 @@ export interface WSContextResultsMessage {
tags: string[]; tags: string[];
updatedAt: string; updatedAt: string;
}>; }>;
_reqId?: string;
} }
/** Broker → client: response to list_contexts. */ /** Broker → client: response to list_contexts. */
@@ -510,6 +529,7 @@ export interface WSContextListMessage {
tags: string[]; tags: string[];
updatedAt: string; updatedAt: string;
}>; }>;
_reqId?: string;
} }
// --- Task messages --- // --- Task messages ---
@@ -547,6 +567,7 @@ export interface WSListTasksMessage {
export interface WSTaskCreatedMessage { export interface WSTaskCreatedMessage {
type: "task_created"; type: "task_created";
id: string; id: string;
_reqId?: string;
} }
/** Broker → client: response to list_tasks, claim_task, complete_task. */ /** Broker → client: response to list_tasks, claim_task, complete_task. */
@@ -563,6 +584,7 @@ export interface WSTaskListMessage {
tags: string[]; tags: string[];
createdAt: string; createdAt: string;
}>; }>;
_reqId?: string;
} }
// --- Stream messages --- // --- Stream messages ---
@@ -602,6 +624,7 @@ export interface WSStreamCreatedMessage {
type: "stream_created"; type: "stream_created";
id: string; id: string;
name: string; name: string;
_reqId?: string;
} }
/** Broker → client: real-time data pushed from a stream. */ /** Broker → client: real-time data pushed from a stream. */
@@ -616,6 +639,7 @@ export interface WSStreamDataMessage {
export interface WSSubscribedMessage { export interface WSSubscribedMessage {
type: "subscribed"; type: "subscribed";
stream: string; stream: string;
_reqId?: string;
} }
/** Broker → client: response to list_streams. */ /** Broker → client: response to list_streams. */
@@ -628,6 +652,7 @@ export interface WSStreamListMessage {
createdAt: string; createdAt: string;
subscriberCount: number; subscriberCount: number;
}>; }>;
_reqId?: string;
} }
/** Broker → client: structured error. */ /** Broker → client: structured error. */
@@ -636,6 +661,7 @@ export interface WSErrorMessage {
code: string; code: string;
message: string; message: string;
id?: string; id?: string;
_reqId?: string;
} }
export type WSClientMessage = export type WSClientMessage =