@gonzih/cc-discord 0.2.23 → 0.2.24

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/notifier.js +22 -15
  2. package/package.json +1 -1
package/dist/notifier.js CHANGED
@@ -101,6 +101,9 @@ function shortenModelName(model, driver) {
101
101
  * Appends " cost: $X.XXX" if a numeric cost field is present.
102
102
  */
103
103
  export function parseNotification(raw) {
104
+ // Filter cron-fire noise before any parsing — catches plain-text and JSON-wrapped ⏰ cron notifications
105
+ if (raw.startsWith("⏰") || raw.includes('"⏰'))
106
+ return null;
104
107
  let text = raw;
105
108
  let driver;
106
109
  let model;
@@ -127,10 +130,8 @@ export function parseNotification(raw) {
127
130
  isCron = parsed.is_cron;
128
131
  }
129
132
  catch {
130
- // non-JSON: fall through to text-based check below
133
+ // non-JSON: fall through
131
134
  }
132
- if (text.startsWith("⏰"))
133
- return null;
134
135
  // Parse eval_report if present — this field is non-standard and not in NotificationPayload type
135
136
  const evalReport = parseEvalReport(raw);
136
137
  if (!driver)
@@ -356,23 +357,24 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
356
357
  const notification = parseNotification(raw);
357
358
  if (notification === null)
358
359
  continue; // routing excludes discord
359
- // Dedup: skip if this notification was already forwarded recently
360
- // Uses Redis when available; falls back to in-memory dedup on Redis errors.
360
+ // Dedup: skip if this notification was already forwarded recently.
361
+ // Check in-memory first (catches pub/sub-delivered notifications in the same process).
362
+ if (checkAndMarkSentSync(ns, raw)) {
363
+ log("info", `dedup: skipping already-sent notification (ns=${ns})`);
364
+ continue;
365
+ }
366
+ // Also check Redis for cross-restart/cross-process dedup.
361
367
  let isDup = false;
362
368
  try {
363
369
  if (typeof redis.sadd === "function") {
364
370
  isDup = await checkAndMarkSent(redis, ns, raw);
365
371
  }
366
- else {
367
- isDup = checkAndMarkSentSync(ns, raw);
368
- }
369
372
  }
370
373
  catch (err) {
371
- log("warn", `dedup check failed (ns=${ns}), falling back to in-memory:`, err.message);
372
- isDup = checkAndMarkSentSync(ns, raw);
374
+ log("warn", `dedup Redis check failed (ns=${ns}):`, err.message);
373
375
  }
374
376
  if (isDup) {
375
- log("info", `dedup: skipping already-sent notification (ns=${ns})`);
377
+ log("info", `dedup: skipping already-sent notification (ns=${ns}) [redis]`);
376
378
  continue;
377
379
  }
378
380
  // Primary namespace: honour chatId-based per-channel routing via reverseSnowflakeLookup,
@@ -436,6 +438,8 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
436
438
  log("info", `dedup: skipping already-sent pub/sub notification (ns=${ns})`);
437
439
  return;
438
440
  }
441
+ // Also mark in Redis so list-poller dedup sees it (prevents cross-path duplicates)
442
+ checkAndMarkSent(redis, ns, message).catch(() => { });
439
443
  let mainChannelId;
440
444
  if (isPrimary) {
441
445
  mainChannelId = resolveNotifyChannel(notification.chatId, notifyChannelId, getActiveChannelId, reverseSnowflakeLookup, ns, getChannelIdForNamespace);
@@ -483,10 +487,13 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
483
487
  ? (notifyChannelId ?? getActiveChannelId?.())
484
488
  : routedChannelIds.get(ns);
485
489
  if (targetChannelId !== undefined) {
486
- // Echo to Discord so the user sees UI messages
487
- bot.sendToChannelById(targetChannelId, `[from UI]: ${content}`).catch((err) => {
488
- log("warn", `sendToChannelById (UI echo) failed (ns=${ns}):`, err.message);
489
- });
490
+ // Echo to Discord so the user sees UI messages (suppress cron-fire noise)
491
+ const isCronMessage = content.startsWith("⏰") || content.includes("[cron]");
492
+ if (!isCronMessage) {
493
+ bot.sendToChannelById(targetChannelId, `[from UI]: ${content}`).catch((err) => {
494
+ log("warn", `sendToChannelById (UI echo) failed (ns=${ns}):`, err.message);
495
+ });
496
+ }
490
497
  // Log the incoming message
491
498
  const inMsg = {
492
499
  id: crypto.randomUUID(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonzih/cc-discord",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "Claude Code Discord bot — chat with Claude Code via Discord",
5
5
  "type": "module",
6
6
  "bin": {