@luckydraw/cumulus 0.27.18 → 0.27.19

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 (58) hide show
  1. package/dist/cli/cumulus.js +0 -0
  2. package/dist/gateway/adapters/webchat.d.ts +1 -3
  3. package/dist/gateway/adapters/webchat.d.ts.map +1 -1
  4. package/dist/gateway/adapters/webchat.js +7 -150
  5. package/dist/gateway/adapters/webchat.js.map +1 -1
  6. package/dist/gateway/config.d.ts +7 -0
  7. package/dist/gateway/config.d.ts.map +1 -1
  8. package/dist/gateway/config.js +13 -0
  9. package/dist/gateway/config.js.map +1 -1
  10. package/dist/gateway/daemon.js +26 -8
  11. package/dist/gateway/daemon.js.map +1 -1
  12. package/dist/gateway/gateway-agents-mcp.js +49 -0
  13. package/dist/gateway/gateway-agents-mcp.js.map +1 -1
  14. package/dist/gateway/push.d.ts +66 -0
  15. package/dist/gateway/push.d.ts.map +1 -0
  16. package/dist/gateway/push.js +131 -0
  17. package/dist/gateway/push.js.map +1 -0
  18. package/dist/gateway/server.d.ts +3 -9
  19. package/dist/gateway/server.d.ts.map +1 -1
  20. package/dist/gateway/server.js +112 -243
  21. package/dist/gateway/server.js.map +1 -1
  22. package/dist/gateway/static/sw.js +73 -0
  23. package/dist/gateway/static/widget.js +126 -755
  24. package/dist/index.d.ts +3 -5
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/lib/config.d.ts +0 -8
  29. package/dist/lib/config.d.ts.map +1 -1
  30. package/dist/lib/config.js +1 -61
  31. package/dist/lib/config.js.map +1 -1
  32. package/dist/lib/content-detector.d.ts.map +1 -1
  33. package/dist/lib/content-detector.js +2 -9
  34. package/dist/lib/content-detector.js.map +1 -1
  35. package/dist/lib/gateway.d.ts +2 -14
  36. package/dist/lib/gateway.d.ts.map +1 -1
  37. package/dist/lib/gateway.js +103 -234
  38. package/dist/lib/gateway.js.map +1 -1
  39. package/dist/lib/summaries.d.ts.map +1 -1
  40. package/dist/lib/summaries.js +2 -9
  41. package/dist/lib/summaries.js.map +1 -1
  42. package/dist/mcp/index.js +0 -0
  43. package/dist/mcp/tool-handler.d.ts.map +1 -1
  44. package/dist/mcp/tool-handler.js +3 -10
  45. package/dist/mcp/tool-handler.js.map +1 -1
  46. package/dist/tui/hooks/useClaudeProcess.d.ts.map +1 -1
  47. package/dist/tui/hooks/useClaudeProcess.js +23 -18
  48. package/dist/tui/hooks/useClaudeProcess.js.map +1 -1
  49. package/package.json +3 -1
  50. package/dist/gateway/device-mcp.d.ts +0 -21
  51. package/dist/gateway/device-mcp.d.ts.map +0 -1
  52. package/dist/gateway/device-mcp.js +0 -165
  53. package/dist/gateway/device-mcp.js.map +0 -1
  54. package/dist/lib/executor.d.ts +0 -168
  55. package/dist/lib/executor.d.ts.map +0 -1
  56. package/dist/lib/executor.js +0 -284
  57. package/dist/lib/executor.js.map +0 -1
  58. package/dist/lib/projects.d.ts.map +0 -1
@@ -2,7 +2,6 @@
2
2
  * Cumulus Web Chat Widget — self-contained, no external dependencies.
3
3
  * Embeddable via <script src="/widget.js" data-api-key="..."></script>
4
4
  * or usable standalone at /chat.
5
- * @version 0.27.15
6
5
  *
7
6
  * < 80KB unminified.
8
7
  */
@@ -155,38 +154,6 @@
155
154
  ' word-wrap: break-word;',
156
155
  '}',
157
156
 
158
- /* ── Timestamps ── */
159
- '.cumulus-msg-time {',
160
- ' font-size: 0.75em; color: #555; margin-top: 0.3em; padding: 0 0.3em;',
161
- '}',
162
- '.cumulus-msg-row:has(.user) .cumulus-msg-time { align-self: flex-end; }',
163
- '.cumulus-msg-row:has(.assistant) .cumulus-msg-time { align-self: flex-start; }',
164
-
165
- /* ── Agent messages ── */
166
- '.cumulus-msg.agent {',
167
- ' align-self: flex-start;',
168
- ' background: transparent;',
169
- ' color: #e0e0e0;',
170
- ' padding: 0.4em 0 0.4em 0.75em;',
171
- ' width: 100%;',
172
- ' word-wrap: break-word;',
173
- ' border-left: 2px solid var(--agent-color, #9966cc);',
174
- '}',
175
- '.cumulus-agent-header {',
176
- ' display: flex; align-items: center; gap: 0.4em;',
177
- ' margin-bottom: 0.3em; font-size: 0.82em;',
178
- '}',
179
- '.cumulus-agent-name {',
180
- ' font-family: "SF Mono", "Fira Code", Consolas, monospace;',
181
- ' font-weight: 600; color: var(--agent-color, #9966cc);',
182
- '}',
183
- '.cumulus-agent-name::before { content: "["; opacity: 0.5; }',
184
- '.cumulus-agent-name::after { content: "]"; opacity: 0.5; }',
185
- '.cumulus-agent-context {',
186
- ' font-size: 0.9em; color: #666;',
187
- ' font-family: "SF Mono", "Fira Code", Consolas, monospace;',
188
- '}',
189
-
190
157
  /* ── Markdown rendered inside assistant messages ── */
191
158
  '.cumulus-msg.assistant p { margin: 0.4em 0; }',
192
159
  '.cumulus-msg.assistant p:first-child { margin-top: 0; }',
@@ -718,97 +685,6 @@
718
685
  ' border-radius: 3px; font-family: inherit; white-space: nowrap;',
719
686
  '}',
720
687
  '.cumulus-panel-action-btn:hover { color: #ddd; border-color: #555; }',
721
- '.cumulus-panel-action-btn.active { color: #e0e0e0; border-color: #0066cc; background: rgba(0,102,204,0.12); }',
722
-
723
- /* ── Context menu (right-click on thread) ── */
724
- '.cumulus-context-menu {',
725
- ' position: fixed; z-index: 300; background: #2a2a2a; border: 1px solid #444;',
726
- ' border-radius: 6px; padding: 4px 0; min-width: 140px;',
727
- ' box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-size: 13px;',
728
- '}',
729
- '.cumulus-context-menu-item {',
730
- ' padding: 6px 14px; color: #ccc; cursor: pointer; display: block;',
731
- ' width: 100%; border: none; background: none; text-align: left;',
732
- ' font-family: inherit; font-size: inherit;',
733
- '}',
734
- '.cumulus-context-menu-item:hover { background: #3a3a3a; color: #fff; }',
735
- '.cumulus-context-menu-item.danger { color: #ef4444; }',
736
- '.cumulus-context-menu-item.danger:hover { background: rgba(239,68,68,0.15); color: #ff6b6b; }',
737
-
738
- /* ── Unread dot (glowing) ── */
739
- '.cumulus-thread-unread {',
740
- ' width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;',
741
- ' background: #3b82f6;',
742
- ' box-shadow: 0 0 4px #3b82f6, 0 0 8px rgba(59,130,246,0.4);',
743
- ' animation: cumulus-unread-pulse 2s ease-in-out infinite;',
744
- '}',
745
- '@keyframes cumulus-unread-pulse {',
746
- ' 0%, 100% { box-shadow: 0 0 4px #3b82f6, 0 0 8px rgba(59,130,246,0.4); }',
747
- ' 50% { box-shadow: 0 0 6px #3b82f6, 0 0 14px rgba(59,130,246,0.6); }',
748
- '}',
749
-
750
- /* ── Message checkbox (selection mode) ── */
751
- '.cumulus-msg-checkbox {',
752
- ' display: none; flex-shrink: 0; align-self: flex-start; margin-top: 0.4em;',
753
- ' width: 16px; height: 16px; cursor: pointer; accent-color: #0066cc;',
754
- '}',
755
- '.cumulus-selection-mode .cumulus-msg-checkbox { display: block; }',
756
- '.cumulus-selection-mode .cumulus-msg-row {',
757
- ' flex-direction: row; align-items: flex-start; gap: 8px;',
758
- '}',
759
- '.cumulus-selection-mode .cumulus-msg-row > *:not(.cumulus-msg-checkbox) { flex: 1; min-width: 0; }',
760
-
761
- /* ── Selection action bar (floats at bottom of messages area) ── */
762
- '.cumulus-selection-bar {',
763
- ' display: flex; align-items: center; gap: 8px;',
764
- ' padding: 8px 12px;',
765
- ' background: #2d2d2d; border-top: 1px solid #3a3a3a;',
766
- ' flex-shrink: 0;',
767
- '}',
768
- '.cumulus-selection-count { font-size: 13px; color: #aaa; flex: 1; }',
769
- '.cumulus-selection-select-all-btn {',
770
- ' background: none; border: 1px solid #555; color: #ccc;',
771
- ' padding: 4px 10px; border-radius: 4px; font-size: 12px;',
772
- ' cursor: pointer; font-family: inherit;',
773
- '}',
774
- '.cumulus-selection-select-all-btn:hover { border-color: #888; color: #e0e0e0; }',
775
- '.cumulus-selection-delete-btn {',
776
- ' background: #8b2222; border: none; color: white;',
777
- ' padding: 4px 12px; border-radius: 4px; font-size: 12px;',
778
- ' cursor: pointer; font-family: inherit; font-weight: 600;',
779
- '}',
780
- '.cumulus-selection-delete-btn:hover:not(:disabled) { background: #a33; }',
781
- '.cumulus-selection-delete-btn:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
782
-
783
- /* ── Confirm dialog overlay ── */
784
- '.cumulus-confirm-dialog {',
785
- ' position: absolute; top: 0; left: 0; right: 0; bottom: 0;',
786
- ' background: rgba(0,0,0,0.6); z-index: 20;',
787
- ' display: flex; align-items: center; justify-content: center;',
788
- '}',
789
- '.cumulus-confirm-dialog-box {',
790
- ' background: #2d2d2d; border: 1px solid #4a4a4a; border-radius: 8px;',
791
- ' padding: 1.5em; max-width: 320px; width: 90%;',
792
- ' display: flex; flex-direction: column; gap: 1em; text-align: center;',
793
- ' box-shadow: 0 8px 32px rgba(0,0,0,0.5);',
794
- '}',
795
- '.cumulus-confirm-dialog-title { font-weight: 600; font-size: 15px; color: #e0e0e0; }',
796
- '.cumulus-confirm-dialog-text { font-size: 13px; color: #aaa; }',
797
- '.cumulus-confirm-dialog-warn { font-size: 12px; color: #f59e0b; }',
798
- '.cumulus-confirm-dialog-actions { display: flex; gap: 8px; justify-content: center; }',
799
- '.cumulus-confirm-dialog-cancel {',
800
- ' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
801
- ' font-size: 13px; border: 1px solid #3a3a3a; background: #2a2a2a; color: #ccc;',
802
- ' font-family: inherit;',
803
- '}',
804
- '.cumulus-confirm-dialog-cancel:hover { background: #3a3a3a; }',
805
- '.cumulus-confirm-dialog-confirm {',
806
- ' padding: 0.4em 1.2em; border-radius: 4px; cursor: pointer;',
807
- ' font-size: 13px; border: none; background: #8b2222; color: white;',
808
- ' font-family: inherit; font-weight: 600;',
809
- '}',
810
- '.cumulus-confirm-dialog-confirm:hover { background: #a33; }',
811
- '.cumulus-confirm-dialog-confirm:disabled { background: #3a2a2a; color: #666; cursor: not-allowed; }',
812
688
 
813
689
  /* ── Overlay (covers the thread panel) ── */
814
690
  '.cumulus-overlay {',
@@ -1044,67 +920,6 @@
1044
920
  // else, then reinsert code blocks. This prevents markdown patterns inside
1045
921
  // code from being processed.
1046
922
 
1047
- // ── Agent message detection + timestamps ──────────────────────────────────
1048
- var AGENT_MSG_RE = /^\[([^\]]+)\s*\u2192\s*([^\]]+)\]:\s*/; // [sender → recipient]:
1049
- var AGENT_COLORS = ['#9966cc', '#2aa198', '#e69500', '#d33682', '#85c025', '#ff6b6b'];
1050
-
1051
- function getAgentColor(name) {
1052
- var hash = 0;
1053
- for (var i = 0; i < name.length; i++) hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;
1054
- return AGENT_COLORS[Math.abs(hash) % AGENT_COLORS.length];
1055
- }
1056
-
1057
- function parseAgentMessage(content) {
1058
- var match = content.match(AGENT_MSG_RE);
1059
- if (!match) return null;
1060
- var body = content.slice(match[0].length).replace(/\n\n\(Reply using send_to_agent\([^)]*\)[^)]*\)\s*$/, '').trim();
1061
- // Also strip CC/broadcast reply hints
1062
- body = body.replace(/\n\n\(You are CC'd on this message[^)]*\)\s*$/, '').trim();
1063
- body = body.replace(/\n\n\(Review the above messages[^)]*\)\s*$/, '').trim();
1064
- return { sender: match[1].trim(), recipient: match[2].trim(), body: body };
1065
- }
1066
-
1067
- function formatTime(timestamp) {
1068
- if (!timestamp) return null;
1069
- var d = new Date(timestamp);
1070
- return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1071
- }
1072
-
1073
- function appendTimestamp(parent, timestamp) {
1074
- var time = formatTime(timestamp);
1075
- if (time) {
1076
- var timeEl = document.createElement('div');
1077
- timeEl.className = 'cumulus-msg-time';
1078
- timeEl.textContent = time;
1079
- parent.appendChild(timeEl);
1080
- }
1081
- }
1082
-
1083
- function buildAgentMsgEl(agent) {
1084
- var el = document.createElement('div');
1085
- el.className = 'cumulus-msg agent';
1086
- el.style.setProperty('--agent-color', getAgentColor(agent.sender));
1087
-
1088
- var header = document.createElement('div');
1089
- header.className = 'cumulus-agent-header';
1090
- var nameEl = document.createElement('span');
1091
- nameEl.className = 'cumulus-agent-name';
1092
- nameEl.textContent = agent.sender;
1093
- header.appendChild(nameEl);
1094
- var ctx = document.createElement('span');
1095
- ctx.className = 'cumulus-agent-context';
1096
- ctx.textContent = '\u2192 ' + agent.recipient;
1097
- header.appendChild(ctx);
1098
- el.appendChild(header);
1099
-
1100
- var body = document.createElement('div');
1101
- body.innerHTML = renderMarkdown(agent.body);
1102
- el.appendChild(body);
1103
- return el;
1104
- }
1105
-
1106
- // ── Markdown ──────────────────────────────────────────────────────────────
1107
-
1108
923
  function renderMarkdown(text) {
1109
924
  // Phase 1: Extract fenced code blocks — replace with unique tokens
1110
925
  var codeBlocks = [];
@@ -1520,8 +1335,6 @@
1520
1335
  var connectionStatus = 'disconnected';
1521
1336
  var authenticated = false;
1522
1337
  var pendingAttachments = [];
1523
- var selectionMode = false;
1524
- var selectedIds = {};
1525
1338
 
1526
1339
  // ── Panel ──
1527
1340
  var panel = document.createElement('div');
@@ -1538,9 +1351,6 @@
1538
1351
  '<span class="cumulus-status-dot" data-testid="webchat-status-dot"></span>' +
1539
1352
  '<span data-testid="webchat-status-text">Disconnected</span>' +
1540
1353
  '</span>' +
1541
- '<div class="cumulus-panel-actions">' +
1542
- '<button class="cumulus-panel-action-btn" data-testid="webchat-select-btn" title="Select messages to delete">Select</button>' +
1543
- '</div>' +
1544
1354
  '<button class="cumulus-close-btn" data-testid="webchat-close">&times;</button>';
1545
1355
  panel.appendChild(header);
1546
1356
 
@@ -1639,9 +1449,7 @@
1639
1449
 
1640
1450
  // ── Rendering ──
1641
1451
  function scrollToBottom() {
1642
- requestAnimationFrame(function () {
1643
- messagesEl.scrollTop = messagesEl.scrollHeight;
1644
- });
1452
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1645
1453
  }
1646
1454
 
1647
1455
  function isWideUserMessage(content) {
@@ -1710,51 +1518,20 @@
1710
1518
 
1711
1519
  function renderMessages() {
1712
1520
  messagesEl.innerHTML = '';
1713
- // Apply selection mode class
1714
- if (selectionMode) {
1715
- messagesEl.classList.add('cumulus-selection-mode');
1716
- } else {
1717
- messagesEl.classList.remove('cumulus-selection-mode');
1718
- }
1719
1521
  if (messages.length === 0 && !streaming) {
1720
1522
  messagesEl.innerHTML =
1721
1523
  '<div class="cumulus-empty">Send a message to start chatting</div>';
1722
- updateEmbeddedSelectionBar();
1723
1524
  return;
1724
1525
  }
1725
1526
  for (var i = 0; i < messages.length; i++) {
1726
1527
  var msg = messages[i];
1727
1528
  var row = document.createElement('div');
1728
1529
  row.className = 'cumulus-msg-row';
1729
- // Add checkbox in selection mode
1730
- if (selectionMode && msg.id) {
1731
- var cb = document.createElement('input');
1732
- cb.type = 'checkbox';
1733
- cb.className = 'cumulus-msg-checkbox';
1734
- cb.checked = !!selectedIds[msg.id];
1735
- (function (msgId) {
1736
- cb.addEventListener('change', function () {
1737
- if (cb.checked) {
1738
- selectedIds[msgId] = true;
1739
- } else {
1740
- delete selectedIds[msgId];
1741
- }
1742
- updateEmbeddedSelectionBar();
1743
- });
1744
- })(msg.id);
1745
- row.appendChild(cb);
1746
- }
1747
1530
  if (msg.role === 'user') {
1748
- var agent = parseAgentMessage(msg.content);
1749
- if (agent) {
1750
- row.appendChild(buildAgentMsgEl(agent));
1751
- } else {
1752
- row.appendChild(buildUserMsgEl(msg));
1753
- }
1531
+ row.appendChild(buildUserMsgEl(msg));
1754
1532
  } else {
1755
1533
  row.appendChild(buildAssistantMsgEl(msg.content, false));
1756
1534
  }
1757
- appendTimestamp(row, msg.timestamp);
1758
1535
  messagesEl.appendChild(row);
1759
1536
  }
1760
1537
  if (streaming) {
@@ -1764,109 +1541,6 @@
1764
1541
  messagesEl.appendChild(row);
1765
1542
  }
1766
1543
  scrollToBottom();
1767
- updateEmbeddedSelectionBar();
1768
- }
1769
-
1770
- function updateEmbeddedSelectionBar() {
1771
- var existingBar = panel.querySelector('.cumulus-selection-bar');
1772
- if (existingBar) existingBar.remove();
1773
- if (!selectionMode) return;
1774
-
1775
- var selectedCount = Object.keys(selectedIds).length;
1776
- var selectableCount = messages.filter(function (m) { return !!m.id; }).length;
1777
-
1778
- var bar = document.createElement('div');
1779
- bar.className = 'cumulus-selection-bar';
1780
-
1781
- var countEl = document.createElement('span');
1782
- countEl.className = 'cumulus-selection-count';
1783
- countEl.textContent = selectedCount + ' selected';
1784
- bar.appendChild(countEl);
1785
-
1786
- var selectAllBtn = document.createElement('button');
1787
- selectAllBtn.className = 'cumulus-selection-select-all-btn';
1788
- selectAllBtn.textContent = selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
1789
- selectAllBtn.addEventListener('click', function () {
1790
- if (selectedCount === selectableCount && selectableCount > 0) {
1791
- selectedIds = {};
1792
- } else {
1793
- messages.forEach(function (m) {
1794
- if (m.id) selectedIds[m.id] = true;
1795
- });
1796
- }
1797
- renderMessages();
1798
- });
1799
- bar.appendChild(selectAllBtn);
1800
-
1801
- var deleteBtn = document.createElement('button');
1802
- deleteBtn.className = 'cumulus-selection-delete-btn';
1803
- deleteBtn.textContent = 'Delete';
1804
- deleteBtn.disabled = selectedCount === 0;
1805
- deleteBtn.addEventListener('click', function () {
1806
- if (selectedCount === 0) return;
1807
- showEmbeddedDeleteMessagesConfirm(Object.keys(selectedIds));
1808
- });
1809
- bar.appendChild(deleteBtn);
1810
-
1811
- var inputAreaEl = panel.querySelector('.cumulus-input-area');
1812
- if (inputAreaEl) {
1813
- panel.insertBefore(bar, inputAreaEl);
1814
- } else {
1815
- panel.appendChild(bar);
1816
- }
1817
- }
1818
-
1819
- function showEmbeddedDeleteMessagesConfirm(ids) {
1820
- var existing = panel.querySelector('.cumulus-confirm-dialog');
1821
- if (existing) existing.remove();
1822
-
1823
- var dialog = document.createElement('div');
1824
- dialog.className = 'cumulus-confirm-dialog';
1825
-
1826
- var box = document.createElement('div');
1827
- box.className = 'cumulus-confirm-dialog-box';
1828
-
1829
- var title = document.createElement('div');
1830
- title.className = 'cumulus-confirm-dialog-title';
1831
- title.textContent = 'Delete Messages';
1832
- box.appendChild(title);
1833
-
1834
- var text = document.createElement('div');
1835
- text.className = 'cumulus-confirm-dialog-text';
1836
- text.textContent = ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
1837
- box.appendChild(text);
1838
-
1839
- var warn = document.createElement('div');
1840
- warn.className = 'cumulus-confirm-dialog-warn';
1841
- warn.textContent = 'This cannot be undone.';
1842
- box.appendChild(warn);
1843
-
1844
- var actions = document.createElement('div');
1845
- actions.className = 'cumulus-confirm-dialog-actions';
1846
-
1847
- var cancelBtn = document.createElement('button');
1848
- cancelBtn.className = 'cumulus-confirm-dialog-cancel';
1849
- cancelBtn.textContent = 'Cancel';
1850
- cancelBtn.addEventListener('click', function () { dialog.remove(); });
1851
- actions.appendChild(cancelBtn);
1852
-
1853
- var confirmBtn = document.createElement('button');
1854
- confirmBtn.className = 'cumulus-confirm-dialog-confirm';
1855
- confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
1856
- confirmBtn.textContent = 'Delete';
1857
- confirmBtn.addEventListener('click', function () {
1858
- confirmBtn.disabled = true;
1859
- confirmBtn.textContent = 'Deleting\u2026';
1860
- if (connection) {
1861
- connection.send({ type: 'delete_messages', threadName: sessionId, messageIds: ids });
1862
- }
1863
- dialog.remove();
1864
- });
1865
- actions.appendChild(confirmBtn);
1866
-
1867
- box.appendChild(actions);
1868
- dialog.appendChild(box);
1869
- panel.appendChild(dialog);
1870
1544
  }
1871
1545
 
1872
1546
  function updateSendBtn() {
@@ -1963,7 +1637,7 @@
1963
1637
  case 'history':
1964
1638
  if (data.messages && data.messages.length > 0) {
1965
1639
  messages = data.messages.map(function (m) {
1966
- return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
1640
+ return { role: m.role, content: m.content };
1967
1641
  });
1968
1642
  renderMessages();
1969
1643
  }
@@ -2019,30 +1693,6 @@
2019
1693
  updateSendBtn();
2020
1694
  renderMessages();
2021
1695
  break;
2022
- case 'new_messages':
2023
- // Agent inject completed — append new messages and re-render
2024
- if (data.messages && data.messages.length > 0) {
2025
- for (var nm = 0; nm < data.messages.length; nm++) {
2026
- messages.push({ role: data.messages[nm].role, content: data.messages[nm].content });
2027
- }
2028
- renderMessages();
2029
- }
2030
- break;
2031
- case 'messages_deleted':
2032
- // Server confirmed deletion — reload history and exit selection mode
2033
- selectionMode = false;
2034
- selectedIds = {};
2035
- var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
2036
- if (embSelectBtn) {
2037
- embSelectBtn.textContent = 'Select';
2038
- embSelectBtn.classList.remove('active');
2039
- }
2040
- messages = [];
2041
- // Re-request history
2042
- if (connection) {
2043
- connection.send({ type: 'history', threadName: sessionId, limit: 200 });
2044
- }
2045
- break;
2046
1696
  }
2047
1697
  }
2048
1698
 
@@ -2158,22 +1808,6 @@
2158
1808
  });
2159
1809
  }
2160
1810
 
2161
- // Select button for embedded mode
2162
- var embSelectBtn = panel.querySelector('[data-testid="webchat-select-btn"]');
2163
- if (embSelectBtn) {
2164
- embSelectBtn.addEventListener('click', function () {
2165
- selectionMode = !selectionMode;
2166
- selectedIds = {};
2167
- embSelectBtn.textContent = selectionMode ? 'Done' : 'Select';
2168
- if (selectionMode) {
2169
- embSelectBtn.classList.add('active');
2170
- } else {
2171
- embSelectBtn.classList.remove('active');
2172
- }
2173
- renderMessages();
2174
- });
2175
- }
2176
-
2177
1811
  // Embedded mode: no auth panel handling needed (API key from attribute)
2178
1812
  function startConnection() {
2179
1813
  if (connection) {
@@ -2217,9 +1851,6 @@
2217
1851
  // Thread list from server: [{ name, messageCount, lastActivity }]
2218
1852
  var allThreads = [];
2219
1853
 
2220
- // Unread threads: threadName -> true
2221
- var unreadThreads = {};
2222
-
2223
1854
  // Currently visible panel names (ordered, max 3)
2224
1855
  var visibleThreads = [];
2225
1856
 
@@ -2237,8 +1868,6 @@
2237
1868
  interjecting: false,
2238
1869
  pendingAttachments: [],
2239
1870
  historyLoaded: false,
2240
- selectionMode: false,
2241
- selectedIds: {},
2242
1871
  };
2243
1872
  }
2244
1873
  return threadStates[threadName];
@@ -2503,9 +2132,7 @@
2503
2132
  item.setAttribute('data-thread-name', name);
2504
2133
 
2505
2134
  var dot = document.createElement('span');
2506
- dot.className = unreadThreads[name]
2507
- ? 'cumulus-thread-unread'
2508
- : 'cumulus-thread-dot ' + (isActive ? 'active' : 'inactive');
2135
+ dot.className = 'cumulus-thread-dot ' + (isActive ? 'active' : 'inactive');
2509
2136
  item.appendChild(dot);
2510
2137
 
2511
2138
  var nameEl = document.createElement('span');
@@ -2528,13 +2155,6 @@
2528
2155
  item.appendChild(countEl);
2529
2156
  }
2530
2157
 
2531
- // Right-click context menu
2532
- item.addEventListener('contextmenu', function (e) {
2533
- e.preventDefault();
2534
- e.stopPropagation();
2535
- showThreadContextMenu(name, e.clientX, e.clientY);
2536
- });
2537
-
2538
2158
  item.addEventListener('click', function (e) {
2539
2159
  var multiSelect = e.metaKey || e.ctrlKey;
2540
2160
  if (multiSelect) {
@@ -2547,118 +2167,8 @@
2547
2167
  return item;
2548
2168
  }
2549
2169
 
2550
- // ── Thread context menu (right-click) ──
2551
- function dismissContextMenu() {
2552
- var existing = document.querySelector('.cumulus-context-menu');
2553
- if (existing) existing.remove();
2554
- document.removeEventListener('click', dismissContextMenu);
2555
- document.removeEventListener('contextmenu', dismissContextMenu);
2556
- }
2557
-
2558
- function showThreadContextMenu(threadName, x, y) {
2559
- dismissContextMenu();
2560
-
2561
- var menu = document.createElement('div');
2562
- menu.className = 'cumulus-context-menu';
2563
- menu.style.left = x + 'px';
2564
- menu.style.top = y + 'px';
2565
-
2566
- var deleteItem = document.createElement('button');
2567
- deleteItem.className = 'cumulus-context-menu-item danger';
2568
- deleteItem.textContent = 'Delete thread';
2569
- deleteItem.setAttribute('data-testid', 'webchat-thread-delete');
2570
- deleteItem.addEventListener('click', function () {
2571
- dismissContextMenu();
2572
- showThreadDeleteConfirm(threadName);
2573
- });
2574
- menu.appendChild(deleteItem);
2575
-
2576
- document.body.appendChild(menu);
2577
-
2578
- // Clamp to viewport
2579
- var rect = menu.getBoundingClientRect();
2580
- if (rect.right > window.innerWidth) menu.style.left = (window.innerWidth - rect.width - 4) + 'px';
2581
- if (rect.bottom > window.innerHeight) menu.style.top = (window.innerHeight - rect.height - 4) + 'px';
2582
-
2583
- // Dismiss on next click or right-click anywhere
2584
- setTimeout(function () {
2585
- document.addEventListener('click', dismissContextMenu);
2586
- document.addEventListener('contextmenu', dismissContextMenu);
2587
- }, 0);
2588
- }
2589
-
2590
- // ── Thread delete confirmation ──
2591
- function showThreadDeleteConfirm(threadName) {
2592
- // Remove any existing confirm dialogs
2593
- var existing = document.querySelector('.cumulus-confirm-dialog[data-context="thread-delete"]');
2594
- if (existing) {
2595
- existing.remove();
2596
- return;
2597
- }
2598
-
2599
- // Build inline confirm dialog attached to the sidebar scroll area
2600
- var dialog = document.createElement('div');
2601
- dialog.className = 'cumulus-confirm-dialog';
2602
- dialog.setAttribute('data-context', 'thread-delete');
2603
- dialog.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 200; display: flex; align-items: center; justify-content: center;';
2604
-
2605
- var box = document.createElement('div');
2606
- box.className = 'cumulus-confirm-dialog-box';
2607
-
2608
- var title = document.createElement('div');
2609
- title.className = 'cumulus-confirm-dialog-title';
2610
- title.textContent = 'Delete Thread';
2611
- box.appendChild(title);
2612
-
2613
- var text = document.createElement('div');
2614
- text.className = 'cumulus-confirm-dialog-text';
2615
- text.textContent = 'Delete "' + threadName + '"?';
2616
- box.appendChild(text);
2617
-
2618
- var warn = document.createElement('div');
2619
- warn.className = 'cumulus-confirm-dialog-warn';
2620
- warn.textContent = 'All messages and history will be permanently removed.';
2621
- box.appendChild(warn);
2622
-
2623
- var actions = document.createElement('div');
2624
- actions.className = 'cumulus-confirm-dialog-actions';
2625
-
2626
- var cancelBtn = document.createElement('button');
2627
- cancelBtn.className = 'cumulus-confirm-dialog-cancel';
2628
- cancelBtn.textContent = 'Cancel';
2629
- cancelBtn.addEventListener('click', function () {
2630
- dialog.remove();
2631
- });
2632
- actions.appendChild(cancelBtn);
2633
-
2634
- var confirmBtn = document.createElement('button');
2635
- confirmBtn.className = 'cumulus-confirm-dialog-confirm';
2636
- confirmBtn.setAttribute('data-testid', 'webchat-thread-delete-confirm');
2637
- confirmBtn.textContent = 'Delete';
2638
- confirmBtn.addEventListener('click', function () {
2639
- confirmBtn.disabled = true;
2640
- confirmBtn.textContent = 'Deleting\u2026';
2641
- if (connection) {
2642
- connection.send({ type: 'delete_thread', threadName: threadName });
2643
- }
2644
- dialog.remove();
2645
- });
2646
- actions.appendChild(confirmBtn);
2647
-
2648
- box.appendChild(actions);
2649
- dialog.appendChild(box);
2650
-
2651
- // Dismiss on backdrop click
2652
- dialog.addEventListener('click', function (e) {
2653
- if (e.target === dialog) dialog.remove();
2654
- });
2655
-
2656
- document.body.appendChild(dialog);
2657
- }
2658
-
2659
2170
  // ── Panel management ──
2660
2171
  function soloThread(name) {
2661
- delete unreadThreads[name];
2662
2172
  visibleThreads = [name];
2663
2173
  renderSidebar();
2664
2174
  renderContentArea();
@@ -2667,7 +2177,6 @@
2667
2177
  }
2668
2178
 
2669
2179
  function togglePanelThread(name) {
2670
- delete unreadThreads[name];
2671
2180
  // On mobile, always solo (no side-by-side)
2672
2181
  if (isMobile()) {
2673
2182
  soloThread(name);
@@ -2798,16 +2307,6 @@
2798
2307
  });
2799
2308
  actions.appendChild(revertBtn);
2800
2309
 
2801
- var selectBtn = document.createElement('button');
2802
- selectBtn.className = 'cumulus-panel-action-btn';
2803
- selectBtn.setAttribute('data-testid', 'webchat-select-btn');
2804
- selectBtn.setAttribute('title', 'Select messages to delete');
2805
- selectBtn.textContent = 'Select';
2806
- selectBtn.addEventListener('click', function () {
2807
- toggleSelectionMode();
2808
- });
2809
- actions.appendChild(selectBtn);
2810
-
2811
2310
  panelHeader.appendChild(actions);
2812
2311
 
2813
2312
  var closeBtn = document.createElement('button');
@@ -2876,11 +2375,7 @@
2876
2375
 
2877
2376
  // ── Panel-local render functions ──
2878
2377
  function scrollToBottom() {
2879
- requestAnimationFrame(function () {
2880
- requestAnimationFrame(function () {
2881
- messagesEl.scrollTop = messagesEl.scrollHeight;
2882
- });
2883
- });
2378
+ messagesEl.scrollTop = messagesEl.scrollHeight;
2884
2379
  }
2885
2380
 
2886
2381
  function isWideUserMessage(content) {
@@ -2949,51 +2444,20 @@
2949
2444
 
2950
2445
  function renderPanelMessages() {
2951
2446
  messagesEl.innerHTML = '';
2952
- // Apply selection mode class to messages container
2953
- if (state.selectionMode) {
2954
- messagesEl.classList.add('cumulus-selection-mode');
2955
- } else {
2956
- messagesEl.classList.remove('cumulus-selection-mode');
2957
- }
2958
2447
  if (state.messages.length === 0 && !state.streaming) {
2959
2448
  messagesEl.innerHTML =
2960
2449
  '<div class="cumulus-empty">Send a message to start chatting</div>';
2961
- updateSelectionBar();
2962
2450
  return;
2963
2451
  }
2964
2452
  for (var i = 0; i < state.messages.length; i++) {
2965
2453
  var msg = state.messages[i];
2966
2454
  var row = document.createElement('div');
2967
2455
  row.className = 'cumulus-msg-row';
2968
- // Add checkbox if in selection mode and message has an id
2969
- if (state.selectionMode && msg.id) {
2970
- var cb = document.createElement('input');
2971
- cb.type = 'checkbox';
2972
- cb.className = 'cumulus-msg-checkbox';
2973
- cb.checked = !!state.selectedIds[msg.id];
2974
- (function (msgId) {
2975
- cb.addEventListener('change', function () {
2976
- if (cb.checked) {
2977
- state.selectedIds[msgId] = true;
2978
- } else {
2979
- delete state.selectedIds[msgId];
2980
- }
2981
- updateSelectionBar();
2982
- });
2983
- })(msg.id);
2984
- row.appendChild(cb);
2985
- }
2986
2456
  if (msg.role === 'user') {
2987
- var agent = parseAgentMessage(msg.content);
2988
- if (agent) {
2989
- row.appendChild(buildAgentMsgEl(agent));
2990
- } else {
2991
- row.appendChild(buildUserMsgEl(msg));
2992
- }
2457
+ row.appendChild(buildUserMsgEl(msg));
2993
2458
  } else {
2994
2459
  row.appendChild(buildAssistantMsgEl(msg.content, false));
2995
2460
  }
2996
- appendTimestamp(row, msg.timestamp);
2997
2461
  messagesEl.appendChild(row);
2998
2462
  }
2999
2463
  if (state.streaming) {
@@ -3003,128 +2467,6 @@
3003
2467
  messagesEl.appendChild(row);
3004
2468
  }
3005
2469
  scrollToBottom();
3006
- updateSelectionBar();
3007
- }
3008
-
3009
- function updateSelectionBar() {
3010
- // Remove existing bar
3011
- var existingBar = panel.querySelector('.cumulus-selection-bar');
3012
- if (existingBar) existingBar.remove();
3013
- if (!state.selectionMode) return;
3014
-
3015
- var selectedCount = Object.keys(state.selectedIds).length;
3016
- var selectableCount = state.messages.filter(function (m) { return !!m.id; }).length;
3017
-
3018
- var bar = document.createElement('div');
3019
- bar.className = 'cumulus-selection-bar';
3020
-
3021
- var countEl = document.createElement('span');
3022
- countEl.className = 'cumulus-selection-count';
3023
- countEl.textContent = selectedCount + ' selected';
3024
- bar.appendChild(countEl);
3025
-
3026
- var selectAllBtn = document.createElement('button');
3027
- selectAllBtn.className = 'cumulus-selection-select-all-btn';
3028
- selectAllBtn.textContent = selectedCount === selectableCount && selectableCount > 0 ? 'Deselect All' : 'Select All';
3029
- selectAllBtn.addEventListener('click', function () {
3030
- if (selectedCount === selectableCount && selectableCount > 0) {
3031
- // Deselect all
3032
- state.selectedIds = {};
3033
- } else {
3034
- // Select all with IDs
3035
- state.messages.forEach(function (m) {
3036
- if (m.id) state.selectedIds[m.id] = true;
3037
- });
3038
- }
3039
- renderPanelMessages();
3040
- });
3041
- bar.appendChild(selectAllBtn);
3042
-
3043
- var deleteBtn = document.createElement('button');
3044
- deleteBtn.className = 'cumulus-selection-delete-btn';
3045
- deleteBtn.textContent = 'Delete';
3046
- deleteBtn.disabled = selectedCount === 0;
3047
- deleteBtn.addEventListener('click', function () {
3048
- if (selectedCount === 0) return;
3049
- showDeleteMessagesConfirm(threadName, panel, Object.keys(state.selectedIds));
3050
- });
3051
- bar.appendChild(deleteBtn);
3052
-
3053
- // Insert before input area
3054
- var inputAreaEl = panel.querySelector('.cumulus-input-area');
3055
- if (inputAreaEl) {
3056
- panel.insertBefore(bar, inputAreaEl);
3057
- } else {
3058
- panel.appendChild(bar);
3059
- }
3060
- }
3061
-
3062
- function toggleSelectionMode() {
3063
- state.selectionMode = !state.selectionMode;
3064
- state.selectedIds = {};
3065
- selectBtn.textContent = state.selectionMode ? 'Done' : 'Select';
3066
- if (state.selectionMode) {
3067
- selectBtn.classList.add('active');
3068
- } else {
3069
- selectBtn.classList.remove('active');
3070
- }
3071
- renderPanelMessages();
3072
- }
3073
-
3074
- function showDeleteMessagesConfirm(tName, panelEl, ids) {
3075
- // Remove any existing confirm dialog
3076
- var existing = panelEl.querySelector('.cumulus-confirm-dialog');
3077
- if (existing) existing.remove();
3078
-
3079
- var dialog = document.createElement('div');
3080
- dialog.className = 'cumulus-confirm-dialog';
3081
-
3082
- var box = document.createElement('div');
3083
- box.className = 'cumulus-confirm-dialog-box';
3084
-
3085
- var title = document.createElement('div');
3086
- title.className = 'cumulus-confirm-dialog-title';
3087
- title.textContent = 'Delete Messages';
3088
- box.appendChild(title);
3089
-
3090
- var text = document.createElement('div');
3091
- text.className = 'cumulus-confirm-dialog-text';
3092
- text.textContent = ids.length + ' message' + (ids.length === 1 ? '' : 's') + ' will be permanently deleted.';
3093
- box.appendChild(text);
3094
-
3095
- var warn = document.createElement('div');
3096
- warn.className = 'cumulus-confirm-dialog-warn';
3097
- warn.textContent = 'This cannot be undone.';
3098
- box.appendChild(warn);
3099
-
3100
- var actions = document.createElement('div');
3101
- actions.className = 'cumulus-confirm-dialog-actions';
3102
-
3103
- var cancelBtn = document.createElement('button');
3104
- cancelBtn.className = 'cumulus-confirm-dialog-cancel';
3105
- cancelBtn.textContent = 'Cancel';
3106
- cancelBtn.addEventListener('click', function () {
3107
- dialog.remove();
3108
- });
3109
- actions.appendChild(cancelBtn);
3110
-
3111
- var confirmBtn = document.createElement('button');
3112
- confirmBtn.className = 'cumulus-confirm-dialog-confirm';
3113
- confirmBtn.setAttribute('data-testid', 'webchat-delete-messages-confirm');
3114
- confirmBtn.textContent = 'Delete';
3115
- confirmBtn.addEventListener('click', function () {
3116
- confirmBtn.disabled = true;
3117
- confirmBtn.textContent = 'Deleting\u2026';
3118
- if (connection) {
3119
- connection.send({ type: 'delete_messages', threadName: tName, messageIds: ids });
3120
- }
3121
- dialog.remove();
3122
- });
3123
- actions.appendChild(confirmBtn);
3124
-
3125
- box.appendChild(actions);
3126
- dialog.appendChild(box);
3127
- panelEl.appendChild(dialog);
3128
2470
  }
3129
2471
 
3130
2472
  function updatePanelSendBtn() {
@@ -3215,9 +2557,6 @@
3215
2557
  state._renderMessages = renderPanelMessages;
3216
2558
  state._updateSendBtn = updatePanelSendBtn;
3217
2559
 
3218
- // Initial render (messages may already be loaded from a previous view)
3219
- renderPanelMessages();
3220
-
3221
2560
  // ── Send message for this panel ──
3222
2561
  function sendPanelMessage() {
3223
2562
  var text = inputEl.value.trim();
@@ -3410,12 +2749,14 @@
3410
2749
  var filePath = addInput.value.trim();
3411
2750
  if (!filePath) return;
3412
2751
  connection &&
3413
- connection.send({
3414
- type: 'include_add',
3415
- threadName: threadName,
3416
- filePath: filePath,
3417
- scope: currentScope,
3418
- });
2752
+ connection.send(
2753
+ JSON.stringify({
2754
+ type: 'include_add',
2755
+ threadName: threadName,
2756
+ filePath: filePath,
2757
+ scope: currentScope,
2758
+ })
2759
+ );
3419
2760
  addInput.value = '';
3420
2761
  });
3421
2762
 
@@ -3439,7 +2780,7 @@
3439
2780
 
3440
2781
  // Request the list
3441
2782
  connection &&
3442
- connection.send({ type: 'include_list', threadName: threadName });
2783
+ connection.send(JSON.stringify({ type: 'include_list', threadName: threadName }));
3443
2784
  }
3444
2785
 
3445
2786
  function renderIncludeList(panel, threadName, files) {
@@ -3476,11 +2817,13 @@
3476
2817
  removeBtn.setAttribute('title', 'Remove');
3477
2818
  removeBtn.addEventListener('click', function () {
3478
2819
  connection &&
3479
- connection.send({
3480
- type: 'include_remove',
3481
- threadName: threadName,
3482
- filePath: f.path,
3483
- });
2820
+ connection.send(
2821
+ JSON.stringify({
2822
+ type: 'include_remove',
2823
+ threadName: threadName,
2824
+ filePath: f.path,
2825
+ })
2826
+ );
3484
2827
  });
3485
2828
  item.appendChild(removeBtn);
3486
2829
 
@@ -3526,7 +2869,7 @@
3526
2869
 
3527
2870
  // Request the turn list
3528
2871
  connection &&
3529
- connection.send({ type: 'revert_list', threadName: threadName });
2872
+ connection.send(JSON.stringify({ type: 'revert_list', threadName: threadName }));
3530
2873
  }
3531
2874
 
3532
2875
  function renderRevertList(panel, threadName, turns) {
@@ -3606,7 +2949,7 @@
3606
2949
  cancelBtn.addEventListener('click', function () {
3607
2950
  // Re-request list
3608
2951
  connection &&
3609
- connection.send({ type: 'revert_list', threadName: threadName });
2952
+ connection.send(JSON.stringify({ type: 'revert_list', threadName: threadName }));
3610
2953
  });
3611
2954
  actions.appendChild(cancelBtn);
3612
2955
 
@@ -3618,11 +2961,13 @@
3618
2961
  revertBtn.disabled = true;
3619
2962
  revertBtn.textContent = 'Reverting\u2026';
3620
2963
  connection &&
3621
- connection.send({
3622
- type: 'revert_execute',
3623
- threadName: threadName,
3624
- targetMessageId: turn.userMessageId,
3625
- });
2964
+ connection.send(
2965
+ JSON.stringify({
2966
+ type: 'revert_execute',
2967
+ threadName: threadName,
2968
+ targetMessageId: turn.userMessageId,
2969
+ })
2970
+ );
3626
2971
  });
3627
2972
  actions.appendChild(revertBtn);
3628
2973
 
@@ -3639,14 +2984,98 @@
3639
2984
  return h + ':' + (m < 10 ? '0' : '') + m + ' ' + ampm;
3640
2985
  }
3641
2986
 
2987
+ // ── Push notification subscription ──
2988
+ function registerPushNotifications() {
2989
+ if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
2990
+ console.log('[Cumulus] Push notifications not supported');
2991
+ return;
2992
+ }
2993
+
2994
+ navigator.serviceWorker
2995
+ .register('/sw.js')
2996
+ .then(function (registration) {
2997
+ console.log('[Cumulus] Service worker registered');
2998
+
2999
+ // Check existing subscription
3000
+ return registration.pushManager.getSubscription().then(function (existing) {
3001
+ if (existing) {
3002
+ // Already subscribed — send to server in case it's new/different
3003
+ sendPushSubscription(existing);
3004
+ return;
3005
+ }
3006
+
3007
+ // Get VAPID key from server
3008
+ var loc = window.location;
3009
+ var apiUrl = loc.protocol + '//' + loc.host;
3010
+ fetch(apiUrl + '/api/push/vapid-key')
3011
+ .then(function (r) {
3012
+ return r.json();
3013
+ })
3014
+ .then(function (data) {
3015
+ if (!data.publicKey) {
3016
+ console.log('[Cumulus] Push not configured on server');
3017
+ return;
3018
+ }
3019
+
3020
+ // Request permission and subscribe
3021
+ return registration.pushManager
3022
+ .subscribe({
3023
+ userVisibleOnly: true,
3024
+ applicationServerKey: urlBase64ToUint8Array(data.publicKey),
3025
+ })
3026
+ .then(function (subscription) {
3027
+ console.log('[Cumulus] Push subscription created');
3028
+ sendPushSubscription(subscription);
3029
+ });
3030
+ })
3031
+ .catch(function (err) {
3032
+ console.warn('[Cumulus] Push subscription failed:', err);
3033
+ });
3034
+ });
3035
+ })
3036
+ .catch(function (err) {
3037
+ console.warn('[Cumulus] Service worker registration failed:', err);
3038
+ });
3039
+ }
3040
+
3041
+ function sendPushSubscription(subscription) {
3042
+ var loc = window.location;
3043
+ var apiUrl = loc.protocol + '//' + loc.host;
3044
+ var raw = subscription.toJSON();
3045
+
3046
+ fetch(apiUrl + '/api/push/subscribe', {
3047
+ method: 'POST',
3048
+ headers: {
3049
+ 'Content-Type': 'application/json',
3050
+ 'X-API-Key': activeApiKey,
3051
+ },
3052
+ body: JSON.stringify({
3053
+ endpoint: raw.endpoint,
3054
+ keys: raw.keys,
3055
+ label: navigator.userAgent.indexOf('iPhone') !== -1 ? 'iPhone' : 'Browser',
3056
+ }),
3057
+ }).catch(function (err) {
3058
+ console.warn('[Cumulus] Failed to send push subscription:', err);
3059
+ });
3060
+ }
3061
+
3062
+ function urlBase64ToUint8Array(base64String) {
3063
+ var padding = '='.repeat((4 - (base64String.length % 4)) % 4);
3064
+ var base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
3065
+ var rawData = atob(base64);
3066
+ var outputArray = new Uint8Array(rawData.length);
3067
+ for (var i = 0; i < rawData.length; ++i) {
3068
+ outputArray[i] = rawData.charCodeAt(i);
3069
+ }
3070
+ return outputArray;
3071
+ }
3072
+
3642
3073
  // ── Message router ──
3643
3074
  function handleServerMessage(data) {
3644
- if (data.type !== 'token') {
3645
- console.log('[CumulusChat] WS recv:', data.type, data.threadName || '', data);
3646
- }
3647
3075
  switch (data.type) {
3648
3076
  case 'auth_ok':
3649
3077
  showAppView();
3078
+ registerPushNotifications();
3650
3079
  break;
3651
3080
 
3652
3081
  case 'auth_error':
@@ -3688,50 +3117,12 @@
3688
3117
  }
3689
3118
  break;
3690
3119
 
3691
- case 'thread_deleted':
3692
- // Server confirms thread deleted; remove from sidebar and panels
3693
- if (data.threadName) {
3694
- allThreads = allThreads.filter(function (t) {
3695
- return t.name !== data.threadName;
3696
- });
3697
- delete threadStates[data.threadName];
3698
- removeThreadFromView(data.threadName);
3699
- renderSidebar();
3700
- }
3701
- break;
3702
-
3703
- case 'messages_deleted':
3704
- // Server confirms messages deleted; reload history for that thread
3705
- if (data.threadName) {
3706
- var dState = getThreadState(data.threadName);
3707
- dState.messages = [];
3708
- dState.historyLoaded = false;
3709
- dState.selectionMode = false;
3710
- dState.selectedIds = {};
3711
- // Exit selection mode on visible panel
3712
- var dPanel = document.querySelector('[data-thread-panel="' + data.threadName + '"]');
3713
- if (dPanel) {
3714
- var dMsgs = dPanel.querySelector('.cumulus-messages');
3715
- if (dMsgs) dMsgs.classList.remove('cumulus-selection-mode');
3716
- var dBar = dPanel.querySelector('.cumulus-selection-bar');
3717
- if (dBar) dBar.remove();
3718
- var dSelectBtn = dPanel.querySelector('[data-testid="webchat-select-btn"]');
3719
- if (dSelectBtn) {
3720
- dSelectBtn.textContent = 'Select';
3721
- dSelectBtn.classList.remove('active');
3722
- }
3723
- }
3724
- connection &&
3725
- connection.send({ type: 'history', threadName: data.threadName, limit: 200 });
3726
- }
3727
- break;
3728
-
3729
3120
  case 'history':
3730
3121
  if (data.threadName) {
3731
3122
  var state = getThreadState(data.threadName);
3732
3123
  if (data.messages && data.messages.length > 0) {
3733
3124
  state.messages = data.messages.map(function (m) {
3734
- return { id: m.id, role: m.role, content: m.content, timestamp: m.timestamp };
3125
+ return { id: m.id, role: m.role, content: m.content };
3735
3126
  });
3736
3127
  }
3737
3128
  refreshThreadPanel(data.threadName);
@@ -3810,22 +3201,6 @@
3810
3201
  }
3811
3202
  break;
3812
3203
 
3813
- case 'new_messages':
3814
- // Agent inject completed — append new messages to the thread and re-render
3815
- if (data.threadName && data.messages && data.messages.length > 0) {
3816
- var state = getThreadState(data.threadName);
3817
- for (var nm = 0; nm < data.messages.length; nm++) {
3818
- state.messages.push({
3819
- id: data.messages[nm].id,
3820
- role: data.messages[nm].role,
3821
- content: data.messages[nm].content,
3822
- });
3823
- }
3824
- updateThreadActivity(data.threadName);
3825
- refreshThreadPanel(data.threadName);
3826
- }
3827
- break;
3828
-
3829
3204
  // ── Include/Revert responses ──
3830
3205
  case 'include_list':
3831
3206
  if (data.threadName && data.files) {
@@ -3858,11 +3233,13 @@
3858
3233
  tState.historyLoaded = false;
3859
3234
  // Re-request history
3860
3235
  connection &&
3861
- connection.send({
3862
- type: 'history',
3863
- threadName: data.threadName,
3864
- limit: 200,
3865
- });
3236
+ connection.send(
3237
+ JSON.stringify({
3238
+ type: 'history',
3239
+ threadName: data.threadName,
3240
+ limit: 200,
3241
+ })
3242
+ );
3866
3243
  if (overlay) overlay.remove();
3867
3244
  } else {
3868
3245
  // Show error in overlay
@@ -3886,10 +3263,6 @@
3886
3263
  break;
3887
3264
  }
3888
3265
  }
3889
- // Mark unread if thread is not currently visible
3890
- if (visibleThreads.indexOf(threadName) === -1) {
3891
- unreadThreads[threadName] = true;
3892
- }
3893
3266
  // Re-render sidebar to reflect new sort order / move thread up
3894
3267
  renderSidebar();
3895
3268
  }
@@ -4042,7 +3415,5 @@
4042
3415
  }
4043
3416
 
4044
3417
  window.CumulusChat = widget;
4045
- window.CumulusChat.version = '0.27.15';
4046
- console.log('[CumulusChat] Widget loaded, version ' + window.CumulusChat.version);
4047
3418
  }
4048
3419
  })();