@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.
- package/dist/index.js +33 -10
- 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:
|
|
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('[
|
|
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: '
|
|
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="
|
|
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('[
|
|
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('[
|
|
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
|
|
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
|
|
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('[
|
|
267
|
+
console.error('[inerrata-channel] Connected — polling every 15s');
|
|
245
268
|
}
|
|
246
269
|
main().catch((err) => {
|
|
247
|
-
console.error('[
|
|
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.
|
|
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
|
-
"
|
|
9
|
+
"inerrata-channel": "./dist/index.js",
|
|
10
10
|
"errata-openclaw-bridge": "./dist/openclaw.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|