feat(cli): 1.30.0 — per-session broker presence

flips CLAUDEMESH_SESSION_PRESENCE default to ON. With the broker side
already shipped (the session_hello handler from earlier in this sprint
A wave), every claudemesh launch now gets its own long-lived broker
presence row owned by the daemon and identified by a per-launch
ephemeral keypair vouched by the member's stable key. Two sessions in
the same cwd finally see each other in peer list — the symptom users
have been hitting since 1.28.0 dropped the bridge tier.

Bumps roadmap: 1.30.0 = presence (was queued for 1.30/wizard); the
launch-wizard refactor moves to 1.31.0, setup wizard to 1.32.0, the
mesh→workspace rename to 1.33.0. Verification smoke documented in the
1.30.0 changelog entry.

Rollback: CLAUDEMESH_SESSION_PRESENCE=0 (also accepts "false"/"off").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-04 13:10:51 +01:00
parent ff2aa8bf7c
commit f7d7d391c9
4 changed files with 105 additions and 10 deletions

View File

@@ -1,5 +1,88 @@
# Changelog # Changelog
## 1.30.0 (2026-05-04) — per-session broker presence
Sprint A Phase 3. Two `claudemesh launch` sessions in the same cwd now
see each other in `peer list`. Each launched session has a long-lived
broker presence row owned by the daemon, identified by a per-launch
ephemeral keypair vouched by the member's stable key (OAuth-refresh-vs-
access shape).
### What landed
- **broker `session_hello`** — new WS message type. Validates a
parent-vouched `parent_attestation` (≤24h TTL, ed25519 signature by
the parent member) plus a session-keyed signature on the hello
itself. Inserts a presence row keyed on `sessionPubkey` but
`member_id` from the parent, so member-targeted operations stay
unchanged. Older brokers reply `unknown_message_type` — newer clients
drop back to the previous behavior.
- **daemon `SessionBrokerClient`** — slim WS variant of
`DaemonBrokerClient`. Presence-only, no outbox drain. Lifetime tied
to a registry hook: register opens it, deregister/reaper closes it.
Reconnect with exponential backoff up to 30 s.
- **session-registry hooks** — `setRegistryHooks({ onRegister,
onDeregister })` in `apps/cli/src/daemon/session-registry.ts`. Hook
errors are caught so they never throttle the registry. SessionInfo
gains an optional `presence` field carrying the per-launch keypair
+ attestation.
- **IPC `POST /v1/sessions/register`** — accepts an optional
`presence` block on the body (`session_pubkey`, `session_secret_key`,
`parent_attestation`). Older payloads continue to work.
- **`claudemesh launch`** — generates an ed25519 session keypair and a
12 h parent attestation per launch (mesh secret key signs it),
forwards both to the daemon under `body.presence`. The flag
`CLAUDEMESH_SESSION_PRESENCE` defaults to ON; set `=0` to roll back
if a broker on a given mesh is misbehaving.
- **latent 1.29.0 bug fix** — `claudemesh launch` referenced
`claudeSessionId` before its `const` declaration further down the
file, hitting the temporal dead zone → `ReferenceError` silently
swallowed by the surrounding catch. Net: the IPC session-token
registration has been failing every launch since 1.29.0, falling
every session back to user-level scope. Hoisted the declaration up
so the registration actually runs.
### Sequencing
The broker side ships first and bakes for ~24 h. Only then does the
flag default flip on the CLI side. Older CLIs continue working
unchanged (no per-session WS), and the protocol is purely additive on
the wire.
### Verification (smoke)
In two shells, both `cd ~/Desktop/foo`:
```
$ claudemesh launch --name SessionA -y # shell 1
$ claudemesh launch --name SessionB -y # shell 2
```
In a third shell:
```
$ claudemesh peer list --json --mesh foo \
| jq '.[] | {n: .displayName, c: .cwd}'
{ "n": "SessionA", "c": "/.../foo" } ← persistent, not query-induced
{ "n": "SessionB", "c": "/.../foo" }
```
Inside SessionA, `peer list --mesh foo` now lists SessionB. Kill
SessionB; within ≤30 s the reaper drops it from `peer list`.
### Out of scope (deferred)
- **Attestation auto-refresh** — current 12 h TTL is comfortably
longer than typical sessions; if a session lives past the TTL and
the WS reconnects after expiry, the broker rejects with `expired`
and the SessionBrokerClient quiets. Workaround: `claudemesh launch`
again. Auto-refresh queued for 1.31.0+ alongside HKDF identity.
- **Per-session policy DSL** — the per-launch WS could carry
per-session capabilities later. Out of scope here.
- **Cross-machine session sync** — waits on 2.0.0 HKDF identity.
- **Launch-wizard refactor** — bumped to 1.31.0 to keep this release
scoped to presence.
## 1.29.0 (2026-05-04) — per-session IPC tokens + auto-scoping ## 1.29.0 (2026-05-04) — per-session IPC tokens + auto-scoping
Sprint A Phase 2. Every `claudemesh launch`-spawned session gets a Sprint A Phase 2. Every `claudemesh launch`-spawned session gets a

View File

@@ -1,6 +1,6 @@
{ {
"name": "claudemesh-cli", "name": "claudemesh-cli",
"version": "1.29.0", "version": "1.30.0",
"description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.",
"keywords": [ "keywords": [
"claude-code", "claude-code",

View File

@@ -29,14 +29,14 @@ export interface RunDaemonOptions {
} }
/** /**
* 1.30.0 feature flag. Default OFF for one release cycle so the broker * 1.30.0 feature flag. Default ON — the daemon opens a long-lived WS per
* side has time to deploy + bake before the daemon starts opening * registered session so siblings see each other in `peer list`. Set
* per-session WebSockets. Set CLAUDEMESH_SESSION_PRESENCE=0 to disable * CLAUDEMESH_SESSION_PRESENCE=0 (or "false"/"off") to disable for
* once the flag flips default-on. * rollback if the broker side is misbehaving on a given mesh.
*/ */
function isSessionPresenceEnabled(): boolean { function isSessionPresenceEnabled(): boolean {
const v = process.env.CLAUDEMESH_SESSION_PRESENCE; const v = process.env.CLAUDEMESH_SESSION_PRESENCE;
if (v === undefined || v === "") return false; if (v === undefined || v === "") return true;
return v !== "0" && v.toLowerCase() !== "false" && v.toLowerCase() !== "off"; return v !== "0" && v.toLowerCase() !== "false" && v.toLowerCase() !== "off";
} }

View File

@@ -232,7 +232,7 @@ What this leaves on the v2.0.0 redesign is documented at
--- ---
## v1.26.0 → v1.29.0 — *Sprint A toward v2* — *shipped* ## v1.26.0 → v1.30.0 — *Sprint A toward v2* — *shipped*
The Sprint A push completed everything spec'd for v2.0.0 *except* HKDF The Sprint A push completed everything spec'd for v2.0.0 *except* HKDF
identity (deferred for security review). identity (deferred for security review).
@@ -268,14 +268,26 @@ identity (deferred for security review).
joined meshes (verified: `peer list` returns 1 workspace's peers joined meshes (verified: `peer list` returns 1 workspace's peers
with token, all 3 without). Server-side `meshFromCtx()` plumbing with token, all 3 without). Server-side `meshFromCtx()` plumbing
on every read route. on every read route.
- **1.30.0** — per-session broker presence. Two `claudemesh launch`
sessions in the same cwd finally see each other in `peer list`. Each
launched session has a long-lived broker presence row owned by the
daemon, identified by a per-launch ephemeral keypair vouched by the
member's stable key (OAuth-refresh-vs-access shape). Broker gains a
`session_hello` handler with parent-attestation TTL ≤24h + session-
signature checks; daemon adds a slim `SessionBrokerClient` and
registry lifecycle hooks. Also fixes a latent 1.29.0 TDZ bug where
`claudemesh launch`'s IPC session-token registration was silently
failing every run. Flag-gated for one cycle, default ON in this
release; set `CLAUDEMESH_SESSION_PRESENCE=0` for rollback. Spec at
`.artifacts/specs/2026-05-04-per-session-presence.md`.
What's left for true v2.0.0 (next sessions): What's left for true v2.0.0 (next sessions):
- **1.30.0** — launch wizard refactor (single render loop, daemon-as- - **1.31.0** — launch wizard refactor (single render loop, daemon-as-
step probe panel, last-used persistence, drop `@ts-nocheck`). step probe panel, last-used persistence, drop `@ts-nocheck`).
- **1.31.0** — setup wizard refactor (state-detection snapshot, four- - **1.32.0** — setup wizard refactor (state-detection snapshot, four-
branch flow, daemon install offer, post-join panel). branch flow, daemon install offer, post-join panel).
- **1.32.0** — full mesh→workspace public-surface rename in help/docs/ - **1.33.0** — full mesh→workspace public-surface rename in help/docs/
site; mesh aliases tagged deprecated; protocol/DB stay `mesh_*`. site; mesh aliases tagged deprecated; protocol/DB stay `mesh_*`.
--- ---