@delt/claude-alarm 1.2.0 → 1.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delt/claude-alarm",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Monitor and get notifications from multiple Claude Code sessions via MCP Channels",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "dev": "tsup --watch",
29
29
  "start:hub": "node dist/cli.js hub start",
30
30
  "start:channel": "node dist/channel/server.js",
31
- "test": "node --import tsx --import ./test/isolate-home.ts --test --test-timeout=30000 \"test/**/*.test.ts\""
31
+ "test": "node --import tsx --import ./test/isolate-home.ts --test --test-timeout=60000 \"test/**/*.test.ts\""
32
32
  },
33
33
  "keywords": [
34
34
  "claude",
@@ -1285,6 +1285,24 @@
1285
1285
  el.classList.toggle('show', !!msg);
1286
1286
  }
1287
1287
 
1288
+ function showUploadRejected(msg) {
1289
+ showNotDelivered(msg.sessionId, msg.withText ? 'Image and message not delivered' : 'Image not delivered', msg.reason);
1290
+ }
1291
+
1292
+ function showMessageRejected(msg) {
1293
+ showNotDelivered(msg.sessionId, 'Message not delivered', msg.reason);
1294
+ }
1295
+
1296
+ function showNotDelivered(sessionId, label, reason) {
1297
+ state.waitingReply[sessionId] = false;
1298
+ if (state.selectedSession === sessionId) {
1299
+ renderMessages();
1300
+ showMentionError(`${label}: ${reason}`);
1301
+ }
1302
+ state.notifications.unshift({ sessionId, title: label, message: reason, level: 'warning', time: Date.now() });
1303
+ renderNotifications();
1304
+ }
1305
+
1288
1306
  let mentionState = { open: false, items: [], index: 0, start: -1 };
1289
1307
 
1290
1308
  function currentMentionQuery(input) {
@@ -1448,32 +1466,18 @@
1448
1466
  state.notifications.unshift({ sessionId: msg.sessionId, title: 'Reply', message: msg.content.slice(0, 100), level: 'info', time: msg.timestamp });
1449
1467
  renderNotifications();
1450
1468
  break;
1469
+ case 'upload_rejected':
1470
+ showUploadRejected(msg);
1471
+ break;
1472
+ case 'message_rejected':
1473
+ showMessageRejected(msg);
1474
+ break;
1451
1475
  case 'notification':
1452
1476
  state.notifications.unshift({ sessionId: msg.sessionId, title: msg.title, message: msg.message, level: msg.level || 'info', time: msg.timestamp });
1453
1477
  renderNotifications();
1454
1478
  break;
1455
1479
  case 'permission_request':
1456
- if (!state.permissionRequests[msg.sessionId]) state.permissionRequests[msg.sessionId] = [];
1457
- state.permissionRequests[msg.sessionId].unshift({
1458
- requestId: msg.requestId,
1459
- toolName: msg.toolName,
1460
- description: msg.description,
1461
- inputPreview: msg.inputPreview,
1462
- timestamp: msg.timestamp,
1463
- resolved: false,
1464
- behavior: null,
1465
- choices: msg.choices || null,
1466
- sent: null,
1467
- outcome: null,
1468
- });
1469
- // Auto-select session if none selected
1470
- if (!state.selectedSession && state.sessions[msg.sessionId]) selectSession(msg.sessionId);
1471
- if (state.selectedSession === msg.sessionId) { renderMessages(); renderPermissionBar(); }
1472
- // Also show in notifications with permission info
1473
- state.notifications.unshift({ sessionId: msg.sessionId, title: 'Permission Request', message: `${msg.toolName}: ${msg.description}`, level: 'warning', time: msg.timestamp, permRequestId: msg.requestId });
1474
- renderNotifications();
1475
- // Play notification sound effect via title flash
1476
- flashTitle('Permission Request');
1480
+ showPermissionRequest(msg);
1477
1481
  break;
1478
1482
  case 'permission_response':
1479
1483
  // Another source (Telegram, other dashboard) resolved a permission request
@@ -1487,11 +1491,7 @@
1487
1491
  }
1488
1492
  break;
1489
1493
  case 'permission_pending':
1490
- for (const m of applyPendingChoices(state.permissionRequests, msg.requests || [])) {
1491
- handleMessage({ type: 'permission_request', ...m });
1492
- }
1493
- renderPermissionBar();
1494
- renderNotifications();
1494
+ restorePendingRequests(msg.requests || []);
1495
1495
  break;
1496
1496
  case 'codex_adapters': {
1497
1497
  state.codexAdapters = msg.adapters || [];
@@ -2114,6 +2114,46 @@
2114
2114
  return missing;
2115
2115
  }
2116
2116
 
2117
+ function addPermissionRequest(msg) {
2118
+ if (!state.permissionRequests[msg.sessionId]) state.permissionRequests[msg.sessionId] = [];
2119
+ state.permissionRequests[msg.sessionId].unshift({
2120
+ requestId: msg.requestId,
2121
+ toolName: msg.toolName,
2122
+ description: msg.description,
2123
+ inputPreview: msg.inputPreview,
2124
+ timestamp: msg.timestamp,
2125
+ resolved: false,
2126
+ behavior: null,
2127
+ choices: msg.choices || null,
2128
+ sent: null,
2129
+ outcome: null,
2130
+ });
2131
+ if (!state.selectedSession && state.sessions[msg.sessionId]) selectSession(msg.sessionId);
2132
+ if (state.selectedSession === msg.sessionId) { renderMessages(); renderPermissionBar(); }
2133
+ }
2134
+
2135
+ function showPermissionRequest(msg) {
2136
+ addPermissionRequest(msg);
2137
+ state.notifications.unshift({ sessionId: msg.sessionId, title: 'Permission Request', message: `${msg.toolName}: ${msg.description}`, level: 'warning', time: msg.timestamp, permRequestId: msg.requestId });
2138
+ renderNotifications();
2139
+ flashTitle('Permission Request');
2140
+ }
2141
+
2142
+ // A reconnect can bring back many requests at once; one row and one flash keep them from burying the notifications.
2143
+ function restorePendingRequests(requests) {
2144
+ const restored = applyPendingChoices(state.permissionRequests, requests);
2145
+ for (const m of restored) addPermissionRequest(m);
2146
+ if (restored.length) {
2147
+ const label = `${restored.length} approval request(s) waiting`;
2148
+ const listed = restored.slice(0, 3).map(m => `${m.toolName}: ${m.description}`).join(' · ');
2149
+ const message = restored.length > 3 ? `${listed} …and ${restored.length - 3} more` : listed;
2150
+ state.notifications.unshift({ sessionId: restored[0].sessionId, title: label, message, level: 'warning', time: Date.now() });
2151
+ flashTitle(label);
2152
+ }
2153
+ renderPermissionBar();
2154
+ renderNotifications();
2155
+ }
2156
+
2117
2157
  function sendPermissionChoice(sessionId, requestId, choiceId) {
2118
2158
  if (!state.ws || state.ws.readyState !== WebSocket.OPEN) return;
2119
2159
  state.ws.send(JSON.stringify({ type: 'permission_response', sessionId, requestId, choiceId }));
@@ -2197,23 +2237,28 @@
2197
2237
  }, true);
2198
2238
 
2199
2239
  // Flash title for attention
2200
- let flashInterval = null;
2240
+ let flash = null;
2201
2241
  function flashTitle(msg) {
2202
- const original = document.title;
2203
- let on = true;
2204
- clearInterval(flashInterval);
2205
- flashInterval = setInterval(() => {
2206
- document.title = on ? `** ${msg} **` : original;
2207
- on = !on;
2208
- }, 500);
2209
- const stopFlash = () => {
2210
- clearInterval(flashInterval);
2211
- flashInterval = null;
2212
- document.title = original;
2213
- window.removeEventListener('focus', stopFlash);
2242
+ // While flashing, document.title may hold the flash text, so only a flash that starts from rest reads the real title.
2243
+ if (flash) {
2244
+ flash.msg = msg;
2245
+ return;
2246
+ }
2247
+ const current = { msg, original: document.title, on: true, interval: null, timeout: null };
2248
+ const stop = () => {
2249
+ clearInterval(current.interval);
2250
+ clearTimeout(current.timeout);
2251
+ window.removeEventListener('focus', stop);
2252
+ document.title = current.original;
2253
+ flash = null;
2214
2254
  };
2215
- window.addEventListener('focus', stopFlash);
2216
- setTimeout(stopFlash, 30000);
2255
+ current.interval = setInterval(() => {
2256
+ document.title = current.on ? `** ${current.msg} **` : current.original;
2257
+ current.on = !current.on;
2258
+ }, 500);
2259
+ current.timeout = setTimeout(stop, 30000);
2260
+ window.addEventListener('focus', stop);
2261
+ flash = current;
2217
2262
  }
2218
2263
 
2219
2264
  // Clear all notifications