omni-notify-mcp 1.3.11 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +27 -1
- package/dist/ui/server.js +26 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,7 +33,33 @@ import { fileURLToPath } from "url";
|
|
|
33
33
|
import { z } from "zod";
|
|
34
34
|
const PORT = process.env.NOTIFY_MCP_PORT ? parseInt(process.env.NOTIFY_MCP_PORT) : 3737;
|
|
35
35
|
const BASE = `http://localhost:${PORT}`;
|
|
36
|
-
const
|
|
36
|
+
const CLEAN_ID = (s) => s.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
37
|
+
// NOTIFY_MCP_TAG is the explicit per-window name; otherwise use the nearest
|
|
38
|
+
// meaningful working-dir folder, skipping generic launcher/tool/system dirs so a
|
|
39
|
+
// bridge spawned from an editor's launch dir names itself after the project
|
|
40
|
+
// (e.g. "bullseyenotify"), never the host ("claude-code").
|
|
41
|
+
function deriveVscId() {
|
|
42
|
+
const explicit = CLEAN_ID(process.env.NOTIFY_MCP_TAG ?? "");
|
|
43
|
+
if (explicit)
|
|
44
|
+
return explicit;
|
|
45
|
+
const generic = new Set([
|
|
46
|
+
"claude-code", "claude", "code", "cursor", "vscode", "windsurf",
|
|
47
|
+
"bin", "dist", "build", "src", "out", "node_modules",
|
|
48
|
+
"windows", "system32", "users", "appdata", "roaming", "local", "programs", "temp", "tmp",
|
|
49
|
+
]);
|
|
50
|
+
let dir = process.cwd();
|
|
51
|
+
for (let i = 0; i < 5; i++) {
|
|
52
|
+
const name = CLEAN_ID(basename(dir));
|
|
53
|
+
if (name && !generic.has(name))
|
|
54
|
+
return name;
|
|
55
|
+
const parent = dirname(dir);
|
|
56
|
+
if (!parent || parent === dir)
|
|
57
|
+
break;
|
|
58
|
+
dir = parent;
|
|
59
|
+
}
|
|
60
|
+
return CLEAN_ID(basename(process.cwd())) || "agent";
|
|
61
|
+
}
|
|
62
|
+
const VSC_ID = deriveVscId();
|
|
37
63
|
const SESSION_TAG = `${hostname().toLowerCase().replace(/[^a-z0-9_-]/g, "")}-${VSC_ID}`;
|
|
38
64
|
const CLIENT_NAME = "claude-channel-bridge";
|
|
39
65
|
// ── 1. Ensure the HTTP server is up ───────────────────────────────────────────
|
package/dist/ui/server.js
CHANGED
|
@@ -1659,18 +1659,34 @@ async function slackPost(text) {
|
|
|
1659
1659
|
catch { /* webhook post is best-effort */ }
|
|
1660
1660
|
}
|
|
1661
1661
|
function slackClientTags() {
|
|
1662
|
+
pruneDeadSessions();
|
|
1662
1663
|
const tags = new Set();
|
|
1663
1664
|
for (const sess of listActiveSessions())
|
|
1664
1665
|
if (sess.tag)
|
|
1665
1666
|
tags.add(sess.tag);
|
|
1666
|
-
for (const c of inboxStreamClients)
|
|
1667
|
+
for (const c of inboxStreamClients) {
|
|
1668
|
+
if (c.res.destroyed || c.res.writableEnded || !c.res.writable)
|
|
1669
|
+
continue;
|
|
1667
1670
|
if (c.tag)
|
|
1668
1671
|
tags.add(c.tag);
|
|
1672
|
+
}
|
|
1669
1673
|
for (const [, w] of inboxWaiters)
|
|
1670
1674
|
if (w.tag)
|
|
1671
1675
|
tags.add(w.tag);
|
|
1672
1676
|
return [...tags].sort();
|
|
1673
1677
|
}
|
|
1678
|
+
// How many live agents would actually receive a message with this tag (undefined
|
|
1679
|
+
// = broadcast). Counts live SSE subscribers, parked long-poll waiters, and MCP
|
|
1680
|
+
// sessions. Used to gate the Slack ack: a "ack" posted when nobody is connected
|
|
1681
|
+
// is a lie — the message only sits queued for the next connector.
|
|
1682
|
+
function liveListenerCount(tag) {
|
|
1683
|
+
pruneDeadSessions();
|
|
1684
|
+
let waiters = 0;
|
|
1685
|
+
for (const [, w] of inboxWaiters)
|
|
1686
|
+
if (!tag || w.tag === tag)
|
|
1687
|
+
waiters++;
|
|
1688
|
+
return sseSubscribersForTag(tag) + waiters + sessionsMatchingTag(tag).length;
|
|
1689
|
+
}
|
|
1674
1690
|
function slackClientsNumbered() {
|
|
1675
1691
|
const tags = slackClientTags();
|
|
1676
1692
|
return tags.length ? tags.map((t, i) => `${i + 1}. ${t}`).join("\n") : "(none connected)";
|
|
@@ -1724,11 +1740,13 @@ async function pollSlackOnce(token, channel) {
|
|
|
1724
1740
|
continue;
|
|
1725
1741
|
}
|
|
1726
1742
|
ingestInboxEntry({ text: msg, ts: new Date().toISOString(), tag, origin: "slack" }, "slack");
|
|
1727
|
-
|
|
1743
|
+
if (liveListenerCount(tag) > 0)
|
|
1744
|
+
await slackPost(sessionBusyNote(tag) || "ack");
|
|
1728
1745
|
}
|
|
1729
1746
|
else {
|
|
1730
1747
|
ingestInboxEntry({ text, ts: new Date().toISOString(), origin: "slack" }, "slack");
|
|
1731
|
-
|
|
1748
|
+
if (liveListenerCount(undefined) > 0)
|
|
1749
|
+
await slackPost("ack");
|
|
1732
1750
|
}
|
|
1733
1751
|
}
|
|
1734
1752
|
const newest = all.reduce((acc, m) => (Number(m.ts) > Number(acc || 0) ? String(m.ts) : acc), slackCursor);
|
|
@@ -2433,8 +2451,11 @@ const httpServer = app.listen(PORT, "0.0.0.0", () => {
|
|
|
2433
2451
|
else {
|
|
2434
2452
|
console.log(" MCP endpoint (remote) → disabled (set ENABLE_MCP=1 to enable)\n");
|
|
2435
2453
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2454
|
+
// Live Slack/Telegram pollers hit real external channels — never under test.
|
|
2455
|
+
if (process.env.NOTIFY_MCP_TEST_ENDPOINTS !== "1") {
|
|
2456
|
+
startTelegramListener();
|
|
2457
|
+
startSlackListener();
|
|
2458
|
+
}
|
|
2438
2459
|
open(`http://localhost:${PORT}`).catch(() => { });
|
|
2439
2460
|
});
|
|
2440
2461
|
// TCP-level keepalive on every incoming socket. Without this, a client that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omni-notify-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"description": "An MCP server that lets AI agents (Claude, Cursor, etc.) reach you on any channel — desktop, Telegram, SMS, email — with two-way ask/reply, real-time inbox push, Do Not Disturb, idle gating, multi-session routing, and a one-page web UI for setup. Zero config code; configure once, agents call notify/ask.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|