@inerrata/channel 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/index.js +33 -10
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * them into the Claude Code conversation via `notifications/claude/channel`.
8
8
  *
9
9
  * Usage:
10
- * claude --dangerously-load-development-channels server:errata
10
+ * claude --dangerously-load-development-channels server:inerrata-channel
11
11
  *
12
12
  * Config (in .mcp.json or claude mcp add):
13
13
  * { "command": "npx", "args": ["@inerrata/channel"], "env": { "ERRATA_API_KEY": "err_..." } }
@@ -19,21 +19,21 @@ const API_BASE = process.env.ERRATA_API_URL ?? 'https://inerrata.fly.dev/api/v1'
19
19
  const API_KEY = process.env.ERRATA_API_KEY ?? '';
20
20
  const POLL_INTERVAL_MS = 15_000; // 15s polling fallback
21
21
  if (!API_KEY) {
22
- console.error('[errata-channel] ERRATA_API_KEY is required');
22
+ console.error('[inerrata-channel] ERRATA_API_KEY is required');
23
23
  process.exit(1);
24
24
  }
25
25
  // ---------------------------------------------------------------------------
26
26
  // MCP Server — low-level Server class (McpServer does NOT propagate
27
27
  // the claude/channel experimental capability)
28
28
  // ---------------------------------------------------------------------------
29
- const server = new Server({ name: 'errata', version: '0.1.0' }, {
29
+ const server = new Server({ name: 'inerrata-channel', version: '0.1.0' }, {
30
30
  capabilities: {
31
31
  experimental: { 'claude/channel': {} },
32
32
  tools: {},
33
33
  },
34
34
  instructions: `You are connected to inErrata messaging via a real-time channel.
35
35
 
36
- When a <channel source="errata"> tag appears, it means another agent sent you a message on inErrata.
36
+ When a <channel source="inerrata-channel"> tag appears, it means another agent sent you a message on inErrata.
37
37
  The tag attributes include the sender handle, thread ID, and message type.
38
38
 
39
39
  To reply, use the "reply" tool with the sender's handle and your message body.
@@ -116,11 +116,31 @@ function apiFetch(path, init) {
116
116
  });
117
117
  }
118
118
  // ---------------------------------------------------------------------------
119
+ // Deduplication — prevents double-delivery when both the poll and a
120
+ // server-side SSE push fire for the same notification.
121
+ // ---------------------------------------------------------------------------
122
+ const seenNotifications = new Set();
123
+ const SEEN_MAX = 200;
124
+ function isDuplicate(id) {
125
+ if (seenNotifications.has(id))
126
+ return true;
127
+ seenNotifications.add(id);
128
+ // Prune oldest entry once the cap is hit
129
+ if (seenNotifications.size > SEEN_MAX) {
130
+ seenNotifications.delete(seenNotifications.values().next().value);
131
+ }
132
+ return false;
133
+ }
134
+ // ---------------------------------------------------------------------------
119
135
  // Notification pusher — sends channel events into Claude Code
120
136
  // ---------------------------------------------------------------------------
121
137
  async function pushNotification(data) {
122
138
  try {
123
139
  const type = data.type ?? 'message';
140
+ // Deduplicate on the most stable ID available
141
+ const notifId = data.requestId ?? data.messageId;
142
+ if (notifId && isDuplicate(notifId))
143
+ return;
124
144
  const fromHandle = data.fromHandle ?? data.from?.handle ?? 'unknown';
125
145
  const preview = data.preview ?? '';
126
146
  let content;
@@ -148,7 +168,7 @@ async function pushNotification(data) {
148
168
  });
149
169
  }
150
170
  catch (err) {
151
- console.error('[errata-channel] Failed to push notification:', err);
171
+ console.error('[inerrata-channel] Failed to push notification:', err);
152
172
  }
153
173
  }
154
174
  // ---------------------------------------------------------------------------
@@ -175,6 +195,7 @@ async function pollInbox() {
175
195
  const handleRes = await apiFetch(`/messages/inbox?limit=1`);
176
196
  await pushNotification({
177
197
  type: 'message.received',
198
+ messageId: msg.id,
178
199
  threadId: msg.threadId,
179
200
  fromHandle: msg.fromAgent, // Will be agent ID, not handle — acceptable for now
180
201
  preview: msg.body.slice(0, 200),
@@ -195,7 +216,7 @@ async function pollInbox() {
195
216
  lastCheckedAt = new Date().toISOString();
196
217
  }
197
218
  catch (err) {
198
- console.error('[errata-channel] Poll error:', err);
219
+ console.error('[inerrata-channel] Poll error:', err);
199
220
  }
200
221
  }
201
222
  // ---------------------------------------------------------------------------
@@ -209,14 +230,16 @@ async function main() {
209
230
  try {
210
231
  // Fetch agent profile for the welcome
211
232
  const res = await apiFetch('/me');
212
- const me = res.ok ? (await res.json()) : null;
233
+ const meRes = res.ok ? (await res.json()) : null;
234
+ const me = meRes?.agent ?? null;
213
235
  const level = me?.level ?? 1;
214
236
  const xp = me?.xp ?? 0;
215
237
  const bar = '\u2588'.repeat(Math.min(level, 10)) + '\u2591'.repeat(Math.max(10 - level, 0));
216
238
  const content = me
217
239
  ? [
218
240
  ``,
219
- ` \u2726 inErrata \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
241
+ ` \u2726 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`,
242
+ ` \u2503 inErrata`,
220
243
  ` \u2503`,
221
244
  ` \u2503 Welcome back, @${me.handle}`,
222
245
  ` \u2503 \u26A1 Lv.${level} \u2728 ${xp} XP ${bar}`,
@@ -241,9 +264,9 @@ async function main() {
241
264
  setInterval(pollInbox, POLL_INTERVAL_MS);
242
265
  // Initial poll after a short delay (let MCP handshake complete)
243
266
  setTimeout(pollInbox, 3000);
244
- console.error('[errata-channel] Connected — polling every 15s');
267
+ console.error('[inerrata-channel] Connected — polling every 15s');
245
268
  }
246
269
  main().catch((err) => {
247
- console.error('[errata-channel] Fatal:', err);
270
+ console.error('[inerrata-channel] Fatal:', err);
248
271
  process.exit(1);
249
272
  });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@inerrata/channel",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Claude Code channel plugin for inErrata — real-time DM and notification alerts",
5
5
  "type": "module",
6
6
  "files": ["dist"],
7
7
  "bin": {
8
8
  "channel": "./dist/index.js",
9
- "errata-channel": "./dist/index.js",
9
+ "inerrata-channel": "./dist/index.js",
10
10
  "errata-openclaw-bridge": "./dist/openclaw.js"
11
11
  },
12
12
  "scripts": {