@jungjaehoon/mama-os 0.9.0 → 0.9.2

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 (61) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/cli/commands/start.d.ts.map +1 -1
  3. package/dist/cli/commands/start.js +37 -9
  4. package/dist/cli/commands/start.js.map +1 -1
  5. package/dist/cli/config/types.d.ts +17 -0
  6. package/dist/cli/config/types.d.ts.map +1 -1
  7. package/dist/cli/config/types.js.map +1 -1
  8. package/dist/gateways/discord.d.ts +4 -0
  9. package/dist/gateways/discord.d.ts.map +1 -1
  10. package/dist/gateways/discord.js +48 -2
  11. package/dist/gateways/discord.js.map +1 -1
  12. package/dist/gateways/image-analyzer.d.ts.map +1 -1
  13. package/dist/gateways/image-analyzer.js +10 -1
  14. package/dist/gateways/image-analyzer.js.map +1 -1
  15. package/dist/gateways/slack.d.ts.map +1 -1
  16. package/dist/gateways/slack.js +3 -0
  17. package/dist/gateways/slack.js.map +1 -1
  18. package/dist/multi-agent/agent-process-manager.d.ts +10 -0
  19. package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
  20. package/dist/multi-agent/agent-process-manager.js +36 -2
  21. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  22. package/dist/multi-agent/agent-process-pool.d.ts.map +1 -1
  23. package/dist/multi-agent/agent-process-pool.js +32 -4
  24. package/dist/multi-agent/agent-process-pool.js.map +1 -1
  25. package/dist/multi-agent/multi-agent-base.d.ts +19 -0
  26. package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
  27. package/dist/multi-agent/multi-agent-base.js +96 -0
  28. package/dist/multi-agent/multi-agent-base.js.map +1 -1
  29. package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
  30. package/dist/multi-agent/multi-agent-discord.js +36 -0
  31. package/dist/multi-agent/multi-agent-discord.js.map +1 -1
  32. package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
  33. package/dist/multi-agent/multi-agent-slack.js +38 -2
  34. package/dist/multi-agent/multi-agent-slack.js.map +1 -1
  35. package/dist/multi-agent/pr-review-poller.d.ts +16 -0
  36. package/dist/multi-agent/pr-review-poller.d.ts.map +1 -1
  37. package/dist/multi-agent/pr-review-poller.js +38 -0
  38. package/dist/multi-agent/pr-review-poller.js.map +1 -1
  39. package/dist/multi-agent/types.d.ts +18 -2
  40. package/dist/multi-agent/types.d.ts.map +1 -1
  41. package/dist/multi-agent/types.js.map +1 -1
  42. package/dist/multi-agent/workflow-engine.d.ts +80 -0
  43. package/dist/multi-agent/workflow-engine.d.ts.map +1 -0
  44. package/dist/multi-agent/workflow-engine.js +395 -0
  45. package/dist/multi-agent/workflow-engine.js.map +1 -0
  46. package/dist/multi-agent/workflow-types.d.ts +111 -0
  47. package/dist/multi-agent/workflow-types.d.ts.map +1 -0
  48. package/dist/multi-agent/workflow-types.js +9 -0
  49. package/dist/multi-agent/workflow-types.js.map +1 -0
  50. package/dist/setup/setup-prompt.d.ts +1 -1
  51. package/dist/setup/setup-prompt.d.ts.map +1 -1
  52. package/dist/setup/setup-prompt.js +25 -0
  53. package/dist/setup/setup-prompt.js.map +1 -1
  54. package/package.json +1 -1
  55. package/public/viewer/js/modules/chat.js +30 -14
  56. package/public/viewer/js/utils/dom.js +15 -15
  57. package/public/viewer/src/modules/chat.ts +32 -20
  58. package/public/viewer/src/utils/dom.ts +16 -16
  59. package/public/viewer/viewer.css +45 -2
  60. package/public/viewer/viewer.html +19 -8
  61. package/templates/personas/sisyphus.md +8 -0
@@ -43,6 +43,7 @@ export class ChatModule {
43
43
  history = [];
44
44
  historyPrefix = 'mama_chat_history_';
45
45
  maxHistoryMessages = 50;
46
+ maxDomMessages = 100; // Limit DOM elements for performance
46
47
  historyExpiryMs = 24 * 60 * 60 * 1000;
47
48
  checkpointCooldown = false;
48
49
  COOLDOWN_MS = 60 * 1000;
@@ -1361,7 +1362,7 @@ export class ChatModule {
1361
1362
  }
1362
1363
  }
1363
1364
  /**
1364
- * Restore chat history
1365
+ * Restore chat history (optimized with DocumentFragment)
1365
1366
  */
1366
1367
  restoreHistory(sessionId) {
1367
1368
  const history = this.loadHistory(sessionId);
@@ -1374,7 +1375,11 @@ export class ChatModule {
1374
1375
  return false;
1375
1376
  }
1376
1377
  this.removePlaceholder();
1377
- history.forEach((msg) => {
1378
+ // Use DocumentFragment for batch DOM insertion
1379
+ const fragment = document.createDocumentFragment();
1380
+ // Limit to last N messages for DOM performance
1381
+ const messagesToRender = history.slice(-this.maxDomMessages);
1382
+ messagesToRender.forEach((msg) => {
1378
1383
  const msgEl = document.createElement('div');
1379
1384
  msgEl.className = `chat-message ${msg.role}`;
1380
1385
  if (msg.role === 'user') {
@@ -1407,14 +1412,15 @@ export class ChatModule {
1407
1412
  <div class="message-content">${escapeHtml(msg.content)}</div>
1408
1413
  `;
1409
1414
  }
1410
- container.appendChild(msgEl);
1415
+ fragment.appendChild(msgEl);
1411
1416
  });
1417
+ container.appendChild(fragment);
1412
1418
  scrollToBottom(container);
1413
1419
  showToast('Previous conversation restored');
1414
1420
  return true;
1415
1421
  }
1416
1422
  /**
1417
- * Display history received from server
1423
+ * Display history received from server (optimized with DocumentFragment)
1418
1424
  */
1419
1425
  displayHistory(messages) {
1420
1426
  const container = getElementByIdOrNull('chat-messages');
@@ -1427,8 +1433,12 @@ export class ChatModule {
1427
1433
  return;
1428
1434
  }
1429
1435
  container.innerHTML = '';
1430
- this.history = [];
1431
- messages.forEach((msg) => {
1436
+ this.history = messages;
1437
+ // Use DocumentFragment for batch DOM insertion
1438
+ const fragment = document.createDocumentFragment();
1439
+ // Limit to last N messages for DOM performance
1440
+ const messagesToRender = messages.slice(-this.maxDomMessages);
1441
+ messagesToRender.forEach((msg) => {
1432
1442
  const msgEl = document.createElement('div');
1433
1443
  msgEl.className = `chat-message ${msg.role}`;
1434
1444
  const timestamp = msg.timestamp ? new Date(msg.timestamp) : new Date();
@@ -1449,10 +1459,11 @@ export class ChatModule {
1449
1459
  <div class="message-content">${escapeHtml(msg.content)}</div>
1450
1460
  `;
1451
1461
  }
1452
- container.appendChild(msgEl);
1462
+ fragment.appendChild(msgEl);
1453
1463
  });
1464
+ container.appendChild(fragment);
1454
1465
  scrollToBottom(container);
1455
- logger.info('Displayed', messages.length, 'history messages');
1466
+ logger.info('Displayed', messagesToRender.length, 'history messages');
1456
1467
  }
1457
1468
  /**
1458
1469
  * Clear chat history
@@ -1737,10 +1748,15 @@ export class ChatModule {
1737
1748
  if (!panel) {
1738
1749
  return;
1739
1750
  }
1740
- const shouldOpen = forceState !== undefined ? forceState : panel.classList.contains('hidden');
1751
+ const isClosed = panel.classList.contains('chat-panel-closed');
1752
+ const shouldOpen = forceState !== undefined ? forceState : isClosed;
1741
1753
  if (shouldOpen) {
1742
- panel.classList.remove('hidden');
1743
- panel.classList.add('animate-slide-up');
1754
+ // Lazy session init on first open
1755
+ if (!this.ws) {
1756
+ this.initSession();
1757
+ }
1758
+ panel.classList.remove('chat-panel-closed');
1759
+ panel.classList.add('chat-panel-open', 'animate-slide-up');
1744
1760
  this.restorePanelState(panel);
1745
1761
  if (bubble) {
1746
1762
  bubble.classList.add('scale-0');
@@ -1758,8 +1774,8 @@ export class ChatModule {
1758
1774
  }
1759
1775
  }
1760
1776
  else {
1761
- panel.classList.add('hidden');
1762
- panel.classList.remove('animate-slide-up');
1777
+ panel.classList.add('chat-panel-closed');
1778
+ panel.classList.remove('chat-panel-open', 'animate-slide-up');
1763
1779
  if (bubble) {
1764
1780
  bubble.classList.remove('scale-0');
1765
1781
  }
@@ -1814,7 +1830,7 @@ export class ChatModule {
1814
1830
  */
1815
1831
  isFloatingOpen() {
1816
1832
  const panel = getElementByIdOrNull('chat-panel');
1817
- return Boolean(panel && !panel.classList.contains('hidden'));
1833
+ return Boolean(panel && panel.classList.contains('chat-panel-open'));
1818
1834
  }
1819
1835
  /**
1820
1836
  * Show unread badge on bubble when panel is closed
@@ -97,15 +97,11 @@ export function showToast(message, duration = 3000) {
97
97
  * @param {HTMLElement} container - Container to scroll
98
98
  */
99
99
  export function scrollToBottom(container) {
100
- // Use setTimeout to ensure DOM has updated before scrolling
101
- const doScroll = () => {
102
- container.scrollTop = container.scrollHeight;
103
- if (container.scrollTo) {
104
- container.scrollTo({ top: container.scrollHeight, behavior: 'auto' });
105
- }
106
- };
107
- setTimeout(doScroll, 50);
108
- requestAnimationFrame(doScroll);
100
+ // Use requestAnimationFrame to batch layout read/write and avoid forced reflow
101
+ requestAnimationFrame(() => {
102
+ const scrollHeight = container.scrollHeight; // Single read
103
+ container.scrollTop = scrollHeight; // Single write
104
+ });
109
105
  }
110
106
  /**
111
107
  * Auto-resize textarea to fit content
@@ -113,10 +109,14 @@ export function scrollToBottom(container) {
113
109
  * @param {number} maxRows - Maximum number of rows (default: 5)
114
110
  */
115
111
  export function autoResizeTextarea(textarea, maxRows = 5) {
116
- textarea.style.height = 'auto';
117
- const computedLineHeight = Number.parseFloat(getComputedStyle(textarea).lineHeight);
118
- const lineHeight = Number.isFinite(computedLineHeight) && computedLineHeight > 0 ? computedLineHeight : 20;
119
- const maxHeight = lineHeight * maxRows;
120
- const newHeight = Math.min(textarea.scrollHeight, maxHeight);
121
- textarea.style.height = newHeight + 'px';
112
+ // Use requestAnimationFrame to defer resize to the next frame, avoiding layout thrash from rapid input events
113
+ requestAnimationFrame(() => {
114
+ textarea.style.height = 'auto'; // Reset height before measuring
115
+ const computedLineHeight = Number.parseFloat(getComputedStyle(textarea).lineHeight); // Forces reflow (unavoidable)
116
+ const scrollHeight = textarea.scrollHeight;
117
+ const lineHeight = Number.isFinite(computedLineHeight) && computedLineHeight > 0 ? computedLineHeight : 20;
118
+ const maxHeight = lineHeight * maxRows;
119
+ const newHeight = Math.min(scrollHeight, maxHeight);
120
+ textarea.style.height = newHeight + 'px';
121
+ });
122
122
  }
@@ -66,12 +66,6 @@ type CheckpointRecord = {
66
66
  summary: string;
67
67
  };
68
68
 
69
- // Reserved for future use - attachment message handling
70
- type _ChatMessageWithAttachment = {
71
- id?: string;
72
- [key: string]: unknown;
73
- };
74
-
75
69
  /**
76
70
  * Chat Module Class
77
71
  */
@@ -104,6 +98,7 @@ export class ChatModule {
104
98
  history: ChatHistoryMessage[] = [];
105
99
  historyPrefix = 'mama_chat_history_';
106
100
  maxHistoryMessages = 50;
101
+ maxDomMessages = 100; // Limit DOM elements for performance
107
102
  historyExpiryMs = 24 * 60 * 60 * 1000;
108
103
  checkpointCooldown = false;
109
104
  COOLDOWN_MS = 60 * 1000;
@@ -1666,7 +1661,7 @@ export class ChatModule {
1666
1661
  }
1667
1662
 
1668
1663
  /**
1669
- * Restore chat history
1664
+ * Restore chat history (optimized with DocumentFragment)
1670
1665
  */
1671
1666
  restoreHistory(sessionId: string): boolean {
1672
1667
  const history = this.loadHistory(sessionId);
@@ -1683,7 +1678,12 @@ export class ChatModule {
1683
1678
 
1684
1679
  this.removePlaceholder();
1685
1680
 
1686
- history.forEach((msg) => {
1681
+ // Use DocumentFragment for batch DOM insertion
1682
+ const fragment = document.createDocumentFragment();
1683
+ // Limit to last N messages for DOM performance
1684
+ const messagesToRender = history.slice(-this.maxDomMessages);
1685
+
1686
+ messagesToRender.forEach((msg) => {
1687
1687
  const msgEl = document.createElement('div');
1688
1688
  msgEl.className = `chat-message ${msg.role}`;
1689
1689
 
@@ -1715,9 +1715,10 @@ export class ChatModule {
1715
1715
  `;
1716
1716
  }
1717
1717
 
1718
- container.appendChild(msgEl);
1718
+ fragment.appendChild(msgEl);
1719
1719
  });
1720
1720
 
1721
+ container.appendChild(fragment);
1721
1722
  scrollToBottom(container);
1722
1723
  showToast('Previous conversation restored');
1723
1724
 
@@ -1725,7 +1726,7 @@ export class ChatModule {
1725
1726
  }
1726
1727
 
1727
1728
  /**
1728
- * Display history received from server
1729
+ * Display history received from server (optimized with DocumentFragment)
1729
1730
  */
1730
1731
  displayHistory(messages: ChatHistoryMessage[]): void {
1731
1732
  const container = getElementByIdOrNull<HTMLDivElement>('chat-messages');
@@ -1740,9 +1741,14 @@ export class ChatModule {
1740
1741
  }
1741
1742
 
1742
1743
  container.innerHTML = '';
1743
- this.history = [];
1744
+ this.history = messages;
1745
+
1746
+ // Use DocumentFragment for batch DOM insertion
1747
+ const fragment = document.createDocumentFragment();
1748
+ // Limit to last N messages for DOM performance
1749
+ const messagesToRender = messages.slice(-this.maxDomMessages);
1744
1750
 
1745
- messages.forEach((msg) => {
1751
+ messagesToRender.forEach((msg) => {
1746
1752
  const msgEl = document.createElement('div');
1747
1753
  msgEl.className = `chat-message ${msg.role}`;
1748
1754
 
@@ -1764,11 +1770,12 @@ export class ChatModule {
1764
1770
  `;
1765
1771
  }
1766
1772
 
1767
- container.appendChild(msgEl);
1773
+ fragment.appendChild(msgEl);
1768
1774
  });
1769
1775
 
1776
+ container.appendChild(fragment);
1770
1777
  scrollToBottom(container);
1771
- logger.info('Displayed', messages.length, 'history messages');
1778
+ logger.info('Displayed', messagesToRender.length, 'history messages');
1772
1779
  }
1773
1780
 
1774
1781
  /**
@@ -2088,11 +2095,16 @@ export class ChatModule {
2088
2095
  return;
2089
2096
  }
2090
2097
 
2091
- const shouldOpen = forceState !== undefined ? forceState : panel.classList.contains('hidden');
2098
+ const isClosed = panel.classList.contains('chat-panel-closed');
2099
+ const shouldOpen = forceState !== undefined ? forceState : isClosed;
2092
2100
 
2093
2101
  if (shouldOpen) {
2094
- panel.classList.remove('hidden');
2095
- panel.classList.add('animate-slide-up');
2102
+ // Lazy session init on first open
2103
+ if (!this.ws) {
2104
+ this.initSession();
2105
+ }
2106
+ panel.classList.remove('chat-panel-closed');
2107
+ panel.classList.add('chat-panel-open', 'animate-slide-up');
2096
2108
  this.restorePanelState(panel);
2097
2109
  if (bubble) {
2098
2110
  bubble.classList.add('scale-0');
@@ -2109,8 +2121,8 @@ export class ChatModule {
2109
2121
  messages.scrollTop = messages.scrollHeight;
2110
2122
  }
2111
2123
  } else {
2112
- panel.classList.add('hidden');
2113
- panel.classList.remove('animate-slide-up');
2124
+ panel.classList.add('chat-panel-closed');
2125
+ panel.classList.remove('chat-panel-open', 'animate-slide-up');
2114
2126
  if (bubble) {
2115
2127
  bubble.classList.remove('scale-0');
2116
2128
  }
@@ -2166,7 +2178,7 @@ export class ChatModule {
2166
2178
  */
2167
2179
  isFloatingOpen(): boolean {
2168
2180
  const panel = getElementByIdOrNull<HTMLDivElement>('chat-panel');
2169
- return Boolean(panel && !panel.classList.contains('hidden'));
2181
+ return Boolean(panel && panel.classList.contains('chat-panel-open'));
2170
2182
  }
2171
2183
 
2172
2184
  /**
@@ -111,15 +111,11 @@ export function showToast(message: string, duration = 3000): void {
111
111
  * @param {HTMLElement} container - Container to scroll
112
112
  */
113
113
  export function scrollToBottom(container: HTMLElement): void {
114
- // Use setTimeout to ensure DOM has updated before scrolling
115
- const doScroll = () => {
116
- container.scrollTop = container.scrollHeight;
117
- if (container.scrollTo) {
118
- container.scrollTo({ top: container.scrollHeight, behavior: 'auto' });
119
- }
120
- };
121
- setTimeout(doScroll, 50);
122
- requestAnimationFrame(doScroll);
114
+ // Use requestAnimationFrame to batch layout read/write and avoid forced reflow
115
+ requestAnimationFrame(() => {
116
+ const scrollHeight = container.scrollHeight; // Single read
117
+ container.scrollTop = scrollHeight; // Single write
118
+ });
123
119
  }
124
120
 
125
121
  /**
@@ -128,11 +124,15 @@ export function scrollToBottom(container: HTMLElement): void {
128
124
  * @param {number} maxRows - Maximum number of rows (default: 5)
129
125
  */
130
126
  export function autoResizeTextarea(textarea: HTMLTextAreaElement, maxRows = 5): void {
131
- textarea.style.height = 'auto';
132
- const computedLineHeight = Number.parseFloat(getComputedStyle(textarea).lineHeight);
133
- const lineHeight =
134
- Number.isFinite(computedLineHeight) && computedLineHeight > 0 ? computedLineHeight : 20;
135
- const maxHeight = lineHeight * maxRows;
136
- const newHeight = Math.min(textarea.scrollHeight, maxHeight);
137
- textarea.style.height = newHeight + 'px';
127
+ // Use requestAnimationFrame to defer resize to the next frame, avoiding layout thrash from rapid input events
128
+ requestAnimationFrame(() => {
129
+ textarea.style.height = 'auto'; // Reset height before measuring
130
+ const computedLineHeight = Number.parseFloat(getComputedStyle(textarea).lineHeight); // Forces reflow (unavoidable)
131
+ const scrollHeight = textarea.scrollHeight;
132
+ const lineHeight =
133
+ Number.isFinite(computedLineHeight) && computedLineHeight > 0 ? computedLineHeight : 20;
134
+ const maxHeight = lineHeight * maxRows;
135
+ const newHeight = Math.min(scrollHeight, maxHeight);
136
+ textarea.style.height = newHeight + 'px';
137
+ });
138
138
  }
@@ -4,6 +4,25 @@
4
4
  Typography: Fredoka (display) + Nunito (body)
5
5
  ============================================================================ */
6
6
 
7
+ /* ============================================================================
8
+ CRITICAL CSS - Prevent CLS before Tailwind loads
9
+ ============================================================================ */
10
+
11
+ header {
12
+ min-height: 56px;
13
+ display: flex;
14
+ align-items: center;
15
+ }
16
+
17
+ main {
18
+ min-height: calc(100vh - 56px);
19
+ }
20
+
21
+ [data-tab] {
22
+ min-width: 80px;
23
+ min-height: 36px;
24
+ }
25
+
7
26
  /* ============================================================================
8
27
  ANIMATIONS
9
28
  ============================================================================ */
@@ -68,6 +87,19 @@
68
87
  min-height: 320px;
69
88
  max-width: 96vw;
70
89
  max-height: 85vh;
90
+ /* GPU compositing for smooth toggle */
91
+ transform: translateZ(0);
92
+ will-change: opacity;
93
+ }
94
+
95
+ #chat-panel.chat-panel-closed {
96
+ opacity: 0;
97
+ pointer-events: none;
98
+ }
99
+
100
+ #chat-panel.chat-panel-open {
101
+ opacity: 1;
102
+ pointer-events: auto;
71
103
  }
72
104
  .chat-panel-draggable {
73
105
  position: fixed !important;
@@ -461,6 +493,7 @@
461
493
  /* Tab visibility */
462
494
  .tab-content {
463
495
  display: none;
496
+ content-visibility: hidden;
464
497
  }
465
498
  .tab-content.active {
466
499
  display: flex;
@@ -468,6 +501,12 @@
468
501
  flex: 1;
469
502
  height: 100%;
470
503
  min-height: 0;
504
+ content-visibility: visible;
505
+ }
506
+ /* Memory tab needs visible content-visibility for checkpoint details elements */
507
+ #tab-memory,
508
+ #tab-memory.active {
509
+ content-visibility: visible;
471
510
  }
472
511
 
473
512
  /* Modal visibility */
@@ -634,8 +673,12 @@
634
673
  background: #FFCE00;
635
674
  }
636
675
 
637
- /* Firefox scrollbar */
638
- * {
676
+ /* Firefox scrollbar - scoped to scrollable containers */
677
+ body,
678
+ .tab-content,
679
+ #chat-messages,
680
+ .overflow-y-auto,
681
+ .overflow-auto {
639
682
  scrollbar-width: thin;
640
683
  scrollbar-color: #D4C4E0 transparent;
641
684
  }
@@ -18,17 +18,29 @@
18
18
  <link rel="icon" type="image/svg+xml" href="/viewer/icons/mama-icon.svg" />
19
19
  <link rel="icon" type="image/png" sizes="192x192" href="/viewer/icons/icon-192.png" />
20
20
 
21
+ <!-- Preconnect to CDN origins -->
21
22
  <link rel="preconnect" href="https://fonts.googleapis.com" />
22
23
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
24
+ <link rel="preconnect" href="https://cdn.tailwindcss.com" />
25
+ <link rel="preconnect" href="https://unpkg.com" />
26
+ <link rel="preconnect" href="https://cdn.jsdelivr.net" />
27
+
28
+ <!-- Preload critical resources -->
29
+ <link rel="preload" href="https://cdn.tailwindcss.com" as="script" />
30
+ <link rel="preload" href="/viewer/viewer.css" as="style" />
31
+
32
+ <!-- Fonts with reduced weights -->
23
33
  <link
24
- href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
34
+ href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;700&family=Nunito:wght@400;600&display=swap"
25
35
  rel="stylesheet"
26
36
  />
27
37
 
28
- <script src="https://unpkg.com/vis-network@9/dist/vis-network.min.js"></script>
29
- <script src="https://unpkg.com/lucide@latest"></script>
30
- <script src="https://cdn.jsdelivr.net/npm/marked@11/marked.min.js"></script>
31
- <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js"></script>
38
+ <!-- External libraries with defer -->
39
+ <script defer src="https://unpkg.com/vis-network@9/dist/vis-network.min.js"></script>
40
+ <script defer src="https://unpkg.com/lucide@0.460.0"></script>
41
+ <script defer src="https://cdn.jsdelivr.net/npm/marked@11/marked.min.js"></script>
42
+ <script defer src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js"></script>
43
+ <!-- Tailwind must load synchronously -->
32
44
  <script src="https://cdn.tailwindcss.com"></script>
33
45
  <script>
34
46
  tailwind.config = {
@@ -1002,7 +1014,7 @@
1002
1014
  <span id="chat-badge" class="hidden absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full border-2 border-white animate-pulse"></span>
1003
1015
  </button>
1004
1016
 
1005
- <div id="chat-panel" class="hidden absolute bottom-[4.5rem] right-0 w-[400px] h-[560px] bg-white rounded-2xl shadow-float border border-mama-lavender-dark flex flex-col overflow-hidden">
1017
+ <div id="chat-panel" class="chat-panel-closed absolute bottom-[4.5rem] right-0 w-[400px] h-[560px] bg-white rounded-2xl shadow-float border border-mama-lavender-dark flex flex-col overflow-hidden">
1006
1018
  <!-- Header -->
1007
1019
  <div id="chat-header" class="h-12 bg-mama-yellow/20 flex items-center px-4 border-b border-mama-lavender-dark shrink-0">
1008
1020
  <svg class="w-5 h-5 text-mama-black mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -2059,9 +2071,8 @@
2059
2071
  document.documentElement.lang = userLang.split('-')[0];
2060
2072
 
2061
2073
  initTheme();
2062
- // Initialize floating chat (WebSocket + panel bindings)
2074
+ // Initialize floating chat panel bindings (session init is lazy, on first open)
2063
2075
  chat.initFloating();
2064
- chat.initSession();
2065
2076
  // Default to dashboard tab
2066
2077
  switchTab('dashboard');
2067
2078
  });
@@ -94,6 +94,14 @@ CONTEXT:
94
94
  - Prior analysis: {sub-agent result summary or file path}
95
95
  ```
96
96
 
97
+ **Discord note (delegation trigger):**
98
+
99
+ - `DELEGATE::...` text is only parsed if the Discord gateway processes the message.
100
+ - If the guild/channel is configured with `requireMention: true`, normal messages without an @mention are ignored.
101
+ - Delegation commands are treated as explicit triggers: if any line starts with `DELEGATE::` / `DELEGATE_BG::`, it will still be processed (even without an @mention).
102
+ - Including the bot mention (example: `<@BOT_ID> DELEGATE::developer::...`) is still OK and makes intent obvious.
103
+ - For low-friction delegation, prefer a dedicated swarm channel with `requireMention: false`.
104
+
97
105
  #### Asynchronous Delegation (background — do not wait for result)
98
106
 
99
107
  Use when assigning **independent tasks** to another agent while continuing your own work: