@delt/claude-alarm 1.0.1 → 1.1.1

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.0.1",
3
+ "version": "1.1.1",
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",
@@ -589,8 +589,23 @@
589
589
  background: linear-gradient(180deg, rgba(245,158,11,0.08) 0%, transparent 100%);
590
590
  padding: 0;
591
591
  display: none;
592
+ flex-shrink: 0;
593
+ max-height: 45vh;
594
+ overflow-y: auto;
595
+ scrollbar-width: thin;
592
596
  }
593
597
  .permission-bar.active { display: block; }
598
+ .perm-count {
599
+ position: sticky;
600
+ top: 0;
601
+ z-index: 1;
602
+ padding: 6px 20px;
603
+ font-size: 12px;
604
+ font-weight: 600;
605
+ color: var(--yellow);
606
+ background: var(--surface);
607
+ border-bottom: 1px solid var(--border);
608
+ }
594
609
  .perm-item {
595
610
  display: flex;
596
611
  align-items: center;
@@ -731,6 +746,18 @@
731
746
  margin-left: 2px;
732
747
  font-weight: 400;
733
748
  }
749
+ .perm-actions.choices {
750
+ flex-direction: column;
751
+ gap: 6px;
752
+ }
753
+ .perm-actions .perm-choice {
754
+ padding: 7px 16px;
755
+ font-size: 13px;
756
+ background: var(--surface);
757
+ color: var(--text);
758
+ border: 1px solid var(--border);
759
+ }
760
+ .perm-actions .perm-choice:hover { border-color: var(--yellow); }
734
761
  /* Permission in notification panel */
735
762
  .notif-perm-actions {
736
763
  display: flex;
@@ -1405,6 +1432,9 @@
1405
1432
  timestamp: msg.timestamp,
1406
1433
  resolved: false,
1407
1434
  behavior: null,
1435
+ choices: msg.choices || null,
1436
+ sent: null,
1437
+ outcome: null,
1408
1438
  });
1409
1439
  // Auto-select session if none selected
1410
1440
  if (!state.selectedSession && state.sessions[msg.sessionId]) selectSession(msg.sessionId);
@@ -1426,6 +1456,23 @@
1426
1456
  renderNotifications();
1427
1457
  }
1428
1458
  break;
1459
+ case 'permission_pending':
1460
+ for (const m of applyPendingChoices(state.permissionRequests, msg.requests || [])) {
1461
+ handleMessage({ type: 'permission_request', ...m });
1462
+ }
1463
+ renderPermissionBar();
1464
+ renderNotifications();
1465
+ break;
1466
+ case 'permission_resolved': {
1467
+ const req = (state.permissionRequests[msg.sessionId] || []).find(r => r.requestId === msg.requestId);
1468
+ if (req && !req.resolved) {
1469
+ req.resolved = true;
1470
+ req.outcome = msg.state;
1471
+ renderPermissionBar();
1472
+ renderNotifications();
1473
+ }
1474
+ break;
1475
+ }
1429
1476
  }
1430
1477
  }
1431
1478
 
@@ -1664,7 +1711,10 @@
1664
1711
  if (n.permRequestId) {
1665
1712
  const reqs = state.permissionRequests[n.sessionId] || [];
1666
1713
  const req = reqs.find(r => r.requestId === n.permRequestId);
1667
- if (req && !req.resolved) {
1714
+ if (req && req.choices) {
1715
+ if (req.resolved) permButtons = `<div class="perm-resolved-label">${req.outcome === 'expired' ? 'Expired' : 'Resolved'}</div>`;
1716
+ else if (req.sent != null) permButtons = '<div class="perm-resolved-label">Sent</div>';
1717
+ } else if (req && !req.resolved) {
1668
1718
  permButtons = `<div class="notif-perm-actions">
1669
1719
  <button class="notif-perm-allow" data-req-id="${esc(n.permRequestId)}" data-session-id="${esc(n.sessionId)}">Allow</button>
1670
1720
  <button class="notif-perm-deny" data-req-id="${esc(n.permRequestId)}" data-session-id="${esc(n.sessionId)}">Deny</button>
@@ -1951,6 +2001,7 @@
1951
2001
  let kind = 'code';
1952
2002
  let lang = '';
1953
2003
  switch (toolName) {
2004
+ case 'Command':
1954
2005
  case 'Bash':
1955
2006
  text = p.command ? '$ ' + p.command : raw; lang = 'bash'; break;
1956
2007
  case 'Write':
@@ -2001,6 +2052,47 @@
2001
2052
  renderNotifications();
2002
2053
  }
2003
2054
 
2055
+ // After a reconnect the hub lists the choice requests still waiting; any other unresolved one died with the old connection.
2056
+ function applyPendingChoices(permissionRequests, pending) {
2057
+ const missing = pending.filter(p => !(permissionRequests[p.sessionId] || []).some(r => r.requestId === p.requestId));
2058
+ const live = new Set(pending.map(p => p.sessionId + '\n' + p.requestId));
2059
+ for (const [sid, reqs] of Object.entries(permissionRequests)) {
2060
+ for (const r of reqs) {
2061
+ if (!r.choices || r.resolved) continue;
2062
+ if (!live.has(sid + '\n' + r.requestId)) { r.resolved = true; r.outcome = 'expired'; }
2063
+ else r.sent = null;
2064
+ }
2065
+ }
2066
+ return missing;
2067
+ }
2068
+
2069
+ function sendPermissionChoice(sessionId, requestId, choiceId) {
2070
+ if (!state.ws || state.ws.readyState !== WebSocket.OPEN) return;
2071
+ state.ws.send(JSON.stringify({ type: 'permission_response', sessionId, requestId, choiceId }));
2072
+ const req = (state.permissionRequests[sessionId] || []).find(r => r.requestId === requestId);
2073
+ if (req) req.sent = choiceId;
2074
+ renderPermissionBar();
2075
+ renderNotifications();
2076
+ }
2077
+
2078
+ function permissionActions(r, sid) {
2079
+ const ids = `data-req-id="${esc(r.requestId)}" data-session-id="${esc(sid)}"`;
2080
+ if (!r.choices) {
2081
+ return `<button class="perm-allow" ${ids}>Allow <span class="perm-kbd">(Enter)</span></button>
2082
+ <button class="perm-deny" ${ids}>Deny <span class="perm-kbd">(Esc)</span></button>`;
2083
+ }
2084
+ if (r.sent != null) {
2085
+ const chosen = r.choices.find(c => c.id === r.sent);
2086
+ return `<div class="perm-resolved-label">Sent: ${esc(chosen ? chosen.label : r.sent)}</div>`;
2087
+ }
2088
+ return r.choices.map(c => `<button class="perm-choice" ${ids} data-choice-id="${esc(c.id)}">${esc(c.label)}</button>`).join('');
2089
+ }
2090
+
2091
+ // Codex choices have no safe default, so Enter/Esc stay with the message box for them.
2092
+ function shortcutRequest(reqs) {
2093
+ return reqs.find(r => !r.resolved && !r.choices);
2094
+ }
2095
+
2004
2096
  function renderPermissionBar() {
2005
2097
  const bar = $('#permissionBar');
2006
2098
  const sid = state.selectedSession;
@@ -2008,7 +2100,8 @@
2008
2100
  const reqs = (state.permissionRequests[sid] || []).filter(r => !r.resolved);
2009
2101
  if (!reqs.length) { bar.classList.remove('active'); bar.innerHTML = ''; return; }
2010
2102
  bar.classList.add('active');
2011
- bar.innerHTML = reqs.map(r => {
2103
+ const count = reqs.length > 1 ? `<div class="perm-count">${reqs.length} permission requests waiting</div>` : '';
2104
+ bar.innerHTML = count + reqs.map(r => {
2012
2105
  const { text: previewText, kind: previewKind, lang: previewLang } = formatPermPreview(r.toolName, r.inputPreview);
2013
2106
  const displayName = formatToolName(r.toolName, r.inputPreview);
2014
2107
  return `<div class="perm-item">
@@ -2017,12 +2110,10 @@
2017
2110
  <div class="perm-header">
2018
2111
  <span class="perm-tool">${esc(displayName)}</span>
2019
2112
  </div>
2113
+ ${r.choices && r.description ? `<div class="perm-desc">${esc(r.description)}</div>` : ''}
2020
2114
  ${previewText ? renderPermPreview(previewText, previewKind, previewLang) : ''}
2021
2115
  </div>
2022
- <div class="perm-actions">
2023
- <button class="perm-allow" data-req-id="${esc(r.requestId)}" data-session-id="${esc(sid)}">Allow <span class="perm-kbd">(Enter)</span></button>
2024
- <button class="perm-deny" data-req-id="${esc(r.requestId)}" data-session-id="${esc(sid)}">Deny <span class="perm-kbd">(Esc)</span></button>
2025
- </div>
2116
+ <div class="perm-actions${r.choices ? ' choices' : ''}">${permissionActions(r, sid)}</div>
2026
2117
  </div>`;
2027
2118
  }).join('');
2028
2119
  bindPermissionButtons(bar);
@@ -2035,6 +2126,9 @@
2035
2126
  (container || document).querySelectorAll('.perm-deny, .notif-perm-deny').forEach(btn => {
2036
2127
  btn.addEventListener('click', (e) => { e.stopPropagation(); sendPermissionVerdict(btn.dataset.sessionId, btn.dataset.reqId, 'deny'); });
2037
2128
  });
2129
+ (container || document).querySelectorAll('.perm-choice').forEach(btn => {
2130
+ btn.addEventListener('click', (e) => { e.stopPropagation(); sendPermissionChoice(btn.dataset.sessionId, btn.dataset.reqId, btn.dataset.choiceId); });
2131
+ });
2038
2132
  }
2039
2133
 
2040
2134
  // Keyboard shortcuts: Enter=allow, Esc=deny — works even in textarea when permission is pending
@@ -2044,7 +2138,7 @@
2044
2138
  const sid = state.selectedSession;
2045
2139
  if (!sid) return;
2046
2140
  const reqs = state.permissionRequests[sid] || [];
2047
- const pending = reqs.find(r => !r.resolved);
2141
+ const pending = shortcutRequest(reqs);
2048
2142
  if (!pending) return;
2049
2143
  // Permission pending: intercept Enter/Esc everywhere (even in textarea)
2050
2144
  // Shift+Enter in textarea still inserts newline