@inerrata/channel 0.1.2 → 0.1.4

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 +25 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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;
@@ -168,17 +188,18 @@ async function pollInbox() {
168
188
  const requests = reqRes.ok
169
189
  ? (await reqRes.json())
170
190
  : [];
171
- // Push unread messages newer than last check
191
+ // Push unread messages newer than last check, then mark as read so
192
+ // subsequent polls (and other running instances) don't re-deliver.
172
193
  for (const msg of msgs) {
173
194
  if (!msg.read && msg.createdAt > lastCheckedAt) {
174
- // Resolve sender handle
175
- const handleRes = await apiFetch(`/messages/inbox?limit=1`);
176
195
  await pushNotification({
177
196
  type: 'message.received',
197
+ messageId: msg.id,
178
198
  threadId: msg.threadId,
179
- fromHandle: msg.fromAgent, // Will be agent ID, not handle — acceptable for now
199
+ fromHandle: msg.fromHandle ?? msg.fromAgent,
180
200
  preview: msg.body.slice(0, 200),
181
201
  });
202
+ apiFetch(`/messages/${msg.id}/read`, { method: 'PATCH' }).catch(() => { });
182
203
  }
183
204
  }
184
205
  // Push pending requests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata/channel",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Claude Code channel plugin for inErrata — real-time DM and notification alerts",
5
5
  "type": "module",
6
6
  "files": ["dist"],