2 Commits

Author SHA1 Message Date
Alejandro Gutiérrez
8c6b0c0e07 fix(cli): v0.5.2 — poll-based push delivery (1s interval)
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
Release / Publish multi-arch images (push) Has been cancelled
Replace WS onPush→notification with timer-based buffer drain.
The old claude-intercom used 1s polling and worked reliably.
WS async callbacks may not flush stdio properly for MCP
notifications. Polling on a timer ensures consistent delivery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:33:26 +01:00
Alejandro Gutiérrez
ec9626503c fix(web): force-dynamic on payload admin page (build CSS error)
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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:16:21 +01:00
3 changed files with 51 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "claudemesh-cli", "name": "claudemesh-cli",
"version": "0.5.1", "version": "0.5.2",
"description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.", "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
"keywords": [ "keywords": [
"claude-code", "claude-code",

View File

@@ -722,54 +722,56 @@ Your message mode is "${messageMode}".
// any mesh's broker connection becomes a <channel source="claudemesh"> // any mesh's broker connection becomes a <channel source="claudemesh">
// system reminder injected into Claude Code's context. // system reminder injected into Claude Code's context.
for (const client of allClients()) { for (const client of allClients()) {
client.onPush(async (msg) => { // Poll-based push: drain pushBuffer every 1s and emit channel notifications.
// In "off" mode, silently skip notification — messages are still // This is the proven approach from claude-intercom. The WS onPush handler
// buffered in pushBuffer and accessible via check_messages. // fires instantly but server.notification() may not flush stdio reliably
if (messageMode === "off") return; // from an async WS callback. Polling on a timer ensures consistent delivery.
if (messageMode !== "off") {
const pushPollTimer = setInterval(async () => {
const buffered = client.drainPushBuffer();
for (const msg of buffered) {
const fromPubkey = msg.senderPubkey || "";
const fromName = fromPubkey
? await resolvePeerName(client, fromPubkey)
: "unknown";
const fromPubkey = msg.senderPubkey || ""; if (messageMode === "inbox") {
// Resolve sender's display name from the cached peer list. try {
const fromName = fromPubkey await server.notification({
? await resolvePeerName(client, fromPubkey) method: "notifications/claude/channel",
: "unknown"; params: {
content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
meta: { kind: "inbox_notification", from_name: fromName },
},
});
} catch { /* best effort */ }
continue;
}
if (messageMode === "inbox") { // push mode — full content
// Count-only notification, no content const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
try { try {
await server.notification({ await server.notification({
method: "notifications/claude/channel", method: "notifications/claude/channel",
params: { params: {
content: `[inbox] New message from ${fromName}. Use check_messages to read.`, content,
meta: { kind: "inbox_notification", from_name: fromName }, meta: {
}, from_id: fromPubkey,
}); from_name: fromName,
} catch { /* best effort */ } mesh_slug: client.meshSlug,
return; mesh_id: client.meshId,
} priority: msg.priority,
sent_at: msg.createdAt,
// push mode — full content notification delivered_at: msg.receivedAt,
const content = msg.plaintext ?? decryptFailedWarning(fromPubkey); kind: msg.kind,
try { },
await server.notification({ },
method: "notifications/claude/channel", });
params: { } catch { /* best effort */ }
content, }
meta: { }, 1_000);
from_id: fromPubkey, pushPollTimer.unref();
from_name: fromName, }
mesh_slug: client.meshSlug,
mesh_id: client.meshId,
priority: msg.priority,
sent_at: msg.createdAt,
delivered_at: msg.receivedAt,
kind: msg.kind,
},
},
});
} catch {
/* channel push is best-effort; check_messages is the fallback */
}
});
client.onStreamData(async (evt) => { client.onStreamData(async (evt) => {
try { try {

View File

@@ -4,6 +4,8 @@ import { RootPage, generatePageMetadata } from "@payloadcms/next/views";
import { importMap } from "../importMap"; import { importMap } from "../importMap";
import config from "@payload-config"; import config from "@payload-config";
export const dynamic = "force-dynamic";
type Args = { params: Promise<{ segments: string[] }> }; type Args = { params: Promise<{ segments: string[] }> };
export const generateMetadata = ({ params }: Args) => export const generateMetadata = ({ params }: Args) =>