feat(cli): 1.34.7 → 1.34.13 — multi-session correctness train
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

Seven-ship sequence that took the daemon from "works for one session"
to "internally consistent for N sessions on one daemon." Architecture
invariant after 1.34.13: every shared store / channel scopes by
recipient (SSE demux at bind layer + token forwarding, inbox per-
recipient columns, outbox sender-session routing).

- 1.34.7  inbox flush + delete commands
- 1.34.8  seen_at column + TTL prune + first echo guard
- 1.34.9  broader echo guard + system-event polish + staleness warning
- 1.34.10 per-session SSE demux (SseFilterOptions) + universal daemon
          (--mesh / --name deprecated) + daemon_started version stamp
- 1.34.11 inbox per-recipient column (storage half of 1.34.10)
- 1.34.12 daemon up detaches by default (logs to ~/.claudemesh/daemon/
          daemon.log; service units explicitly pass --foreground)
- 1.34.13 MCP forwards session token on /v1/events — the actual fix
          that activates 1.34.10's demux. Without this header the
          daemon's session resolved null, filter was empty, every MCP
          received the unfiltered global stream.

Roadmap entry at docs/roadmap.md captures the timeline + the four
known gaps tracked for follow-ups (launch env-var leak, broker
listPeers mesh-filter, kick on control-plane no-op, session caps as
first-class concept).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-04 21:10:07 +01:00
parent cba4a938ec
commit 6780899185
24 changed files with 2568 additions and 143 deletions

View File

@@ -97,7 +97,15 @@ Message (resource form)
[--self] (allow targeting your own member/session pubkey;
fans out to every sibling session of your member)
[--json] (machine-readable result)
claudemesh message inbox drain pending (alias: inbox)
claudemesh message inbox read persisted inbox (alias: inbox)
flags: [--mesh <slug>] [--limit N] [--unread] [--json]
reads ~/.claudemesh/daemon/inbox.db via daemon
--unread → only rows never surfaced before (seen_at IS NULL);
listing stamps returned rows seen as a side effect
claudemesh inbox flush bulk-delete inbox rows
flags: [--mesh <slug>] [--before <iso-timestamp>] [--all]
--all required when neither --mesh nor --before is set
claudemesh inbox delete <id> delete one inbox row by id (alias: rm)
claudemesh message status <id> delivery status (alias: msg-status)
Memory (resource form)
@@ -190,16 +198,18 @@ Security
claudemesh backup [file] encrypt config → portable recovery file
claudemesh restore <file> restore config from a backup file
Daemon (long-lived peer mesh runtime, v0.9.0)
claudemesh daemon up start daemon (alias: start) [--mesh <slug>] [--no-tcp]
Daemon (long-lived peer mesh runtime — universal across every joined mesh)
claudemesh daemon up start daemon (alias: start) [--no-tcp]
claudemesh daemon status show running pid + IPC health [--json]
claudemesh daemon down stop daemon (alias: stop)
claudemesh daemon version ipc + schema version of running daemon
claudemesh daemon outbox list list local outbox rows [--failed|--pending|--inflight|--done]
claudemesh daemon outbox requeue <id> re-enqueue an aborted/dead row [--new-client-id <id>]
claudemesh daemon accept-host pin current host fingerprint
claudemesh daemon install-service --mesh <slug> write launchd / systemd-user unit
claudemesh daemon uninstall-service remove the unit
claudemesh daemon install-service write launchd / systemd-user unit
claudemesh daemon uninstall-service remove the unit
Note: the daemon attaches to every mesh in ~/.claudemesh/config.json
automatically; --mesh on up / install-service is deprecated and ignored.
Setup
claudemesh install register MCP server + hooks
@@ -394,7 +404,30 @@ async function main(): Promise<void> {
// Messaging
case "peers": { const { runPeers } = await import("~/commands/peers.js"); await runPeers({ mesh: flags.mesh as string, json: flags.json as boolean | string | undefined, all: !!flags.all }); break; }
case "send": { const { runSend } = await import("~/commands/send.js"); await runSend({ mesh: flags.mesh as string, priority: flags.priority as string, json: !!flags.json, self: !!flags.self }, positionals[0] ?? "", positionals.slice(1).join(" ")); break; }
case "inbox": { const { runInbox } = await import("~/commands/inbox.js"); await runInbox({ json: !!flags.json }); break; }
case "inbox": {
const sub = positionals[0];
if (sub === "flush") {
const { runInboxFlush } = await import("~/commands/inbox-actions.js");
await runInboxFlush({
mesh: flags.mesh as string | undefined,
before: flags.before as string | undefined,
all: !!flags.all,
json: !!flags.json,
});
} else if (sub === "delete" || sub === "rm") {
const { runInboxDelete } = await import("~/commands/inbox-actions.js");
await runInboxDelete(positionals[1] ?? "", { json: !!flags.json });
} else {
const { runInbox } = await import("~/commands/inbox.js");
await runInbox({
mesh: flags.mesh as string | undefined,
json: !!flags.json,
limit: typeof flags.limit === "number" ? flags.limit : (typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined),
unread: !!flags.unread,
});
}
break;
}
case "state": {
const sub = positionals[0];
if (sub === "set") { const { runStateSet } = await import("~/commands/state.js"); await runStateSet({}, positionals[1] ?? "", positionals[2] ?? ""); }
@@ -466,6 +499,11 @@ async function main(): Promise<void> {
publicHealth: !!flags["public-health"],
mesh: flags.mesh as string | undefined,
displayName: flags.name as string | undefined,
// 1.34.12: --foreground opts out of the new "detach by default"
// behavior. install-service and `claudemesh launch`'s auto-spawn
// path always run with --foreground so their parents (launchd /
// the launch helper) own lifecycle and stdio redirection.
foreground: !!flags.foreground,
outboxStatus,
newClientId: flags["new-client-id"] as string | undefined,
}, rest);
@@ -530,7 +568,29 @@ async function main(): Promise<void> {
case "message": {
const sub = positionals[0];
if (sub === "send") { const { runSend } = await import("~/commands/send.js"); await runSend({ mesh: flags.mesh as string, priority: flags.priority as string, json: !!flags.json, self: !!flags.self }, positionals[1] ?? "", positionals.slice(2).join(" ")); }
else if (sub === "inbox") { const { runInbox } = await import("~/commands/inbox.js"); await runInbox({ json: !!flags.json }); }
else if (sub === "inbox") {
const sub2 = positionals[1];
if (sub2 === "flush") {
const { runInboxFlush } = await import("~/commands/inbox-actions.js");
await runInboxFlush({
mesh: flags.mesh as string | undefined,
before: flags.before as string | undefined,
all: !!flags.all,
json: !!flags.json,
});
} else if (sub2 === "delete" || sub2 === "rm") {
const { runInboxDelete } = await import("~/commands/inbox-actions.js");
await runInboxDelete(positionals[2] ?? "", { json: !!flags.json });
} else {
const { runInbox } = await import("~/commands/inbox.js");
await runInbox({
mesh: flags.mesh as string | undefined,
json: !!flags.json,
limit: typeof flags.limit === "number" ? flags.limit : (typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined),
unread: !!flags.unread,
});
}
}
else if (sub === "status") { const { runMsgStatus } = await import("~/commands/broker-actions.js"); process.exit(await runMsgStatus(positionals[1], { mesh: flags.mesh as string, json: !!flags.json })); }
else { console.error("Usage: claudemesh message <send|inbox|status>"); process.exit(EXIT.INVALID_ARGS); }
break;