Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae378c2e3 | ||
|
|
7381738f0b | ||
|
|
8c6b0c0e07 | ||
|
|
ec9626503c |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudemesh-cli",
|
"name": "claudemesh-cli",
|
||||||
"version": "0.5.1",
|
"version": "0.5.3",
|
||||||
"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",
|
||||||
|
|||||||
@@ -722,54 +722,62 @@ 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();
|
||||||
|
if (buffered.length > 0) {
|
||||||
|
process.stderr.write(`[claudemesh] poll: ${buffered.length} message(s) to push\n`);
|
||||||
|
}
|
||||||
|
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: {
|
process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${content.slice(0, 60)}\n`);
|
||||||
content,
|
} catch (pushErr) {
|
||||||
meta: {
|
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}\n`);
|
||||||
from_id: fromPubkey,
|
}
|
||||||
from_name: fromName,
|
}
|
||||||
mesh_slug: client.meshSlug,
|
}, 1_000);
|
||||||
mesh_id: client.meshId,
|
pushPollTimer.unref();
|
||||||
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 {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ ENV NEXT_PUBLIC_URL=$NEXT_PUBLIC_URL
|
|||||||
ENV NEXT_PUBLIC_PRODUCT_NAME=$NEXT_PUBLIC_PRODUCT_NAME
|
ENV NEXT_PUBLIC_PRODUCT_NAME=$NEXT_PUBLIC_PRODUCT_NAME
|
||||||
ENV NEXT_PUBLIC_DEFAULT_LOCALE=$NEXT_PUBLIC_DEFAULT_LOCALE
|
ENV NEXT_PUBLIC_DEFAULT_LOCALE=$NEXT_PUBLIC_DEFAULT_LOCALE
|
||||||
|
|
||||||
|
# TURBOPACK=0 forces webpack for production build — Payload CMS's
|
||||||
|
# richtext-lexical CSS imports fail under Turbopack.
|
||||||
|
ENV TURBOPACK=0
|
||||||
RUN npx turbo run build --filter=web...
|
RUN npx turbo run build --filter=web...
|
||||||
|
|
||||||
# Stage 2: runtime — standalone output only
|
# Stage 2: runtime — standalone output only
|
||||||
|
|||||||
@@ -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) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user