From 8697c1c032539cb2310a09b0454af0dc891883da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sat, 2 May 2026 22:10:13 +0100 Subject: [PATCH] fix(api+cli): topic post messageId is the durable historyId (v1.9.2) Previously POST /v1/messages returned the message_queue row id as `messageId`. Topic posts ARE durable (in topic_message); the queue entry drains on delivery. Pasting that id into `--reply-to` failed because the broker validates parents against topic_message, not the queue. Now `messageId` aliases `historyId` for topic posts; both `historyId` and `queueId` remain available as explicit fields. Roadmap and CLI README updated with v0.3.1 reply-to + v0.3.2 multi-session entries. --- apps/cli/README.md | 4 +++- apps/cli/package.json | 2 +- docs/roadmap.md | 22 ++++++++++++++++++++++ packages/api/src/modules/mesh/v1-router.ts | 8 +++++++- 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/cli/README.md b/apps/cli/README.md index 9a0df0e..a05143b 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -2,7 +2,9 @@ Peer mesh for Claude Code sessions. Connect multiple Claude Code instances into a shared mesh with real-time messaging, shared state, memory, file sharing, vector store, scheduled jobs, and more — all driven from the `claudemesh` CLI. The MCP server is a tool-less push-pipe that delivers inbound peer messages to Claude as `` interrupts; everything else lives behind CLI verbs that Claude learns from the auto-installed `claudemesh` skill. -> **What's new in 1.8.0:** per-topic end-to-end encryption (v0.3.0 phase 3, CLI side). `claudemesh topic post ` encrypts the body with `crypto_secretbox` under the topic's symmetric key — broker stores ciphertext only. `claudemesh topic tail` now decrypts v2 messages on render and runs a background re-seal loop every 30s, so new topic joiners get their sealed keys without manual action. `topic-key` cache is process-only — kill the CLI, the key forgets. Web dashboard reads v1 plaintext for now (phase 3.5 brings browser-side identity). +> **What's new in 1.9.x:** topic threading + multi-session reliability fixes. `claudemesh topic post --reply-to ` threads a reply onto a previous topic message (full id or 8+ char prefix); `topic tail` renders `↳ in reply to : ""` above replies and shows a copyable `#xxxxxxxx` short id on every row. `` MCP attrs now carry `from_member_id`, `from_pubkey` (stable), `from_session_pubkey` (ephemeral), `message_id`, `topic`, `reply_to_id` — everything the recipient needs to reply directly. Broker fixes (v0.3.2): replies to a stale session pubkey now resolve to the owning member's live session instead of bouncing with "not online", and broadcast `*` no longer loopbacks decrypt-fail warnings to the sender's sibling sessions. +> +> **What was new in 1.8.0:** per-topic end-to-end encryption (v0.3.0 phase 3, CLI side). `claudemesh topic post ` encrypts the body with `crypto_secretbox` under the topic's symmetric key — broker stores ciphertext only. `claudemesh topic tail` now decrypts v2 messages on render and runs a background re-seal loop every 30s, so new topic joiners get their sealed keys without manual action. `topic-key` cache is process-only — kill the CLI, the key forgets. Web dashboard reads v1 plaintext for now (phase 3.5 brings browser-side identity). > > **What was new in 1.7.0:** terminal parity for the v1.6.x server features. New verbs: `claudemesh topic tail` (live SSE message stream — Ctrl-C to exit), `claudemesh notification list` (recent `@you` mentions across topics), `claudemesh member list` (mesh roster with online dots, distinct from `peer list`'s live-session view). Each command auto-mints a 5-minute read-only apikey via the WebSocket and revokes it on exit, so no token plumbing is needed. > diff --git a/apps/cli/package.json b/apps/cli/package.json index e3768fa..d5138a0 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.9.1", + "version": "1.9.2", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/docs/roadmap.md b/docs/roadmap.md index bf7936b..c53755d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -223,6 +223,28 @@ level, or wire claudemesh to messaging surfaces beyond Claude Code. web chat encrypt-on-send / decrypt-on-render. Web stays on v1 plaintext until this lands; the existing CLI re-seal loop will pick up web members the moment they have a real pubkey. +- **v0.3.1 — topic message threading (reply-to)** — `topic_message` + gains a self-FK `reply_to_id` column (migration 0027); REST `POST + /v1/messages` and the WS `send` envelope accept `replyToId`; broker + validates same-topic membership. CLI: `topic post --reply-to ` + (full id or 8+ char prefix), `topic tail` renders `↳ in reply to + : ""` above replies and emits `#xxxxxxxx` short ids + per row for copy-paste. WS push envelope + MCP `` channel + attributes now carry `senderMemberId`, `senderName`, `topic`, + `message_id`, `reply_to_id` so the recipient has everything needed + to thread a reply without a follow-up query. *Shipped 2026-05-02 in + CLI v1.9.0.* +- **v0.3.2 — multi-session DM routing + broadcast self-loopback** — + fixes two production bugs: (1) replies via `claudemesh send + ` rejected with "no connected peer" when the sender's + session had rotated — `from_id` now exposes the *member* pubkey + (stable) and the broker pre-flight resolves stale session pubkeys + to the owning member's live session; (2) broadcast / `*` / + `@group` looped back to the sender's sibling sessions, surfacing a + spurious decrypt-fail warning — fan-out now skips by member + pubkey, not just per-presence. Push envelope adds + `senderMemberPubkey` alongside `senderPubkey`. *Shipped 2026-05-02 + in CLI v1.9.1.* - **Self-hosted broker packaging** — one-command Docker compose, Postgres included. The new migration runner (v1.6.x) makes this practical. diff --git a/packages/api/src/modules/mesh/v1-router.ts b/packages/api/src/modules/mesh/v1-router.ts index de424fb..78ca138 100644 --- a/packages/api/src/modules/mesh/v1-router.ts +++ b/packages/api/src/modules/mesh/v1-router.ts @@ -254,9 +254,15 @@ export const v1Router = new Hono() } } + // For topic posts the durable identity is the topic_message row; + // the message_queue row is ephemeral (drains on delivery). Return + // historyRow.id as `messageId` so callers that paste the response + // back into `--reply-to` actually find the parent in history. + // `historyId` and `queueId` are kept as explicit aliases. return c.json({ - messageId: queueRow?.id ?? null, + messageId: historyRow?.id ?? queueRow?.id ?? null, historyId: historyRow?.id ?? null, + queueId: queueRow?.id ?? null, topic: body.topic, topicId: topic.id, notifications,