@jungjaehoon/mama-os 0.9.1 → 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.
- package/CHANGELOG.md +13 -16
- package/dist/gateways/discord.d.ts +4 -0
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +40 -2
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/image-analyzer.d.ts.map +1 -1
- package/dist/gateways/image-analyzer.js +10 -1
- package/dist/gateways/image-analyzer.js.map +1 -1
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +3 -0
- package/dist/gateways/slack.js.map +1 -1
- package/dist/multi-agent/agent-process-manager.d.ts +10 -0
- package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
- package/dist/multi-agent/agent-process-manager.js +36 -2
- package/dist/multi-agent/agent-process-manager.js.map +1 -1
- package/dist/multi-agent/multi-agent-base.d.ts +19 -0
- package/dist/multi-agent/multi-agent-base.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-base.js +96 -0
- package/dist/multi-agent/multi-agent-base.js.map +1 -1
- package/dist/multi-agent/multi-agent-discord.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-discord.js +36 -0
- package/dist/multi-agent/multi-agent-discord.js.map +1 -1
- package/dist/multi-agent/multi-agent-slack.d.ts.map +1 -1
- package/dist/multi-agent/multi-agent-slack.js +38 -2
- package/dist/multi-agent/multi-agent-slack.js.map +1 -1
- package/dist/multi-agent/types.d.ts +8 -2
- package/dist/multi-agent/types.d.ts.map +1 -1
- package/dist/multi-agent/types.js.map +1 -1
- package/dist/multi-agent/workflow-engine.d.ts +80 -0
- package/dist/multi-agent/workflow-engine.d.ts.map +1 -0
- package/dist/multi-agent/workflow-engine.js +395 -0
- package/dist/multi-agent/workflow-engine.js.map +1 -0
- package/dist/multi-agent/workflow-types.d.ts +111 -0
- package/dist/multi-agent/workflow-types.d.ts.map +1 -0
- package/dist/multi-agent/workflow-types.js +9 -0
- package/dist/multi-agent/workflow-types.js.map +1 -0
- package/package.json +1 -1
- package/public/viewer/js/modules/chat.js +34 -15
- package/public/viewer/js/modules/dashboard.js +41 -46
- package/public/viewer/js/utils/dom.js +15 -15
- package/public/viewer/src/modules/chat.ts +36 -15
- package/public/viewer/src/modules/dashboard.ts +43 -52
- package/public/viewer/src/utils/dom.ts +16 -16
- package/public/viewer/viewer.css +45 -2
- package/public/viewer/viewer.html +16 -7
|
@@ -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;
|
|
@@ -80,7 +81,10 @@ export class ChatModule {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
async autoCheckpoint() {
|
|
83
|
-
// Auto-checkpoint
|
|
84
|
+
// DISABLED: Auto-checkpoint was saving raw conversation history to MAMA memory.
|
|
85
|
+
// Checkpoints should only be saved manually via /checkpoint command with proper summaries.
|
|
86
|
+
// The viewer chat uses localStorage for session persistence instead.
|
|
87
|
+
logger.info('Auto-checkpoint disabled (use /checkpoint for manual saves)');
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
86
90
|
// =============================================
|
|
@@ -1358,7 +1362,7 @@ export class ChatModule {
|
|
|
1358
1362
|
}
|
|
1359
1363
|
}
|
|
1360
1364
|
/**
|
|
1361
|
-
* Restore chat history
|
|
1365
|
+
* Restore chat history (optimized with DocumentFragment)
|
|
1362
1366
|
*/
|
|
1363
1367
|
restoreHistory(sessionId) {
|
|
1364
1368
|
const history = this.loadHistory(sessionId);
|
|
@@ -1371,7 +1375,11 @@ export class ChatModule {
|
|
|
1371
1375
|
return false;
|
|
1372
1376
|
}
|
|
1373
1377
|
this.removePlaceholder();
|
|
1374
|
-
|
|
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) => {
|
|
1375
1383
|
const msgEl = document.createElement('div');
|
|
1376
1384
|
msgEl.className = `chat-message ${msg.role}`;
|
|
1377
1385
|
if (msg.role === 'user') {
|
|
@@ -1404,14 +1412,15 @@ export class ChatModule {
|
|
|
1404
1412
|
<div class="message-content">${escapeHtml(msg.content)}</div>
|
|
1405
1413
|
`;
|
|
1406
1414
|
}
|
|
1407
|
-
|
|
1415
|
+
fragment.appendChild(msgEl);
|
|
1408
1416
|
});
|
|
1417
|
+
container.appendChild(fragment);
|
|
1409
1418
|
scrollToBottom(container);
|
|
1410
1419
|
showToast('Previous conversation restored');
|
|
1411
1420
|
return true;
|
|
1412
1421
|
}
|
|
1413
1422
|
/**
|
|
1414
|
-
* Display history received from server
|
|
1423
|
+
* Display history received from server (optimized with DocumentFragment)
|
|
1415
1424
|
*/
|
|
1416
1425
|
displayHistory(messages) {
|
|
1417
1426
|
const container = getElementByIdOrNull('chat-messages');
|
|
@@ -1424,8 +1433,12 @@ export class ChatModule {
|
|
|
1424
1433
|
return;
|
|
1425
1434
|
}
|
|
1426
1435
|
container.innerHTML = '';
|
|
1427
|
-
this.history =
|
|
1428
|
-
|
|
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) => {
|
|
1429
1442
|
const msgEl = document.createElement('div');
|
|
1430
1443
|
msgEl.className = `chat-message ${msg.role}`;
|
|
1431
1444
|
const timestamp = msg.timestamp ? new Date(msg.timestamp) : new Date();
|
|
@@ -1446,10 +1459,11 @@ export class ChatModule {
|
|
|
1446
1459
|
<div class="message-content">${escapeHtml(msg.content)}</div>
|
|
1447
1460
|
`;
|
|
1448
1461
|
}
|
|
1449
|
-
|
|
1462
|
+
fragment.appendChild(msgEl);
|
|
1450
1463
|
});
|
|
1464
|
+
container.appendChild(fragment);
|
|
1451
1465
|
scrollToBottom(container);
|
|
1452
|
-
logger.info('Displayed',
|
|
1466
|
+
logger.info('Displayed', messagesToRender.length, 'history messages');
|
|
1453
1467
|
}
|
|
1454
1468
|
/**
|
|
1455
1469
|
* Clear chat history
|
|
@@ -1734,10 +1748,15 @@ export class ChatModule {
|
|
|
1734
1748
|
if (!panel) {
|
|
1735
1749
|
return;
|
|
1736
1750
|
}
|
|
1737
|
-
const
|
|
1751
|
+
const isClosed = panel.classList.contains('chat-panel-closed');
|
|
1752
|
+
const shouldOpen = forceState !== undefined ? forceState : isClosed;
|
|
1738
1753
|
if (shouldOpen) {
|
|
1739
|
-
|
|
1740
|
-
|
|
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');
|
|
1741
1760
|
this.restorePanelState(panel);
|
|
1742
1761
|
if (bubble) {
|
|
1743
1762
|
bubble.classList.add('scale-0');
|
|
@@ -1755,8 +1774,8 @@ export class ChatModule {
|
|
|
1755
1774
|
}
|
|
1756
1775
|
}
|
|
1757
1776
|
else {
|
|
1758
|
-
panel.classList.add('
|
|
1759
|
-
panel.classList.remove('animate-slide-up');
|
|
1777
|
+
panel.classList.add('chat-panel-closed');
|
|
1778
|
+
panel.classList.remove('chat-panel-open', 'animate-slide-up');
|
|
1760
1779
|
if (bubble) {
|
|
1761
1780
|
bubble.classList.remove('scale-0');
|
|
1762
1781
|
}
|
|
@@ -1811,7 +1830,7 @@ export class ChatModule {
|
|
|
1811
1830
|
*/
|
|
1812
1831
|
isFloatingOpen() {
|
|
1813
1832
|
const panel = getElementByIdOrNull('chat-panel');
|
|
1814
|
-
return Boolean(panel &&
|
|
1833
|
+
return Boolean(panel && panel.classList.contains('chat-panel-open'));
|
|
1815
1834
|
}
|
|
1816
1835
|
/**
|
|
1817
1836
|
* Show unread badge on bubble when panel is closed
|
|
@@ -72,68 +72,48 @@ export class DashboardModule {
|
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
74
74
|
* Load dashboard status from API
|
|
75
|
-
* Uses Promise.allSettled for parallel loading to improve performance
|
|
76
75
|
*/
|
|
77
76
|
async loadStatus() {
|
|
78
77
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
API.get('/api/multi-agent/status')
|
|
83
|
-
API.get('/api/multi-agent/delegations?limit=10'),
|
|
84
|
-
API.getCronJobs(),
|
|
85
|
-
Promise.all([API.getTokenSummary(), API.getTokensByAgent()]),
|
|
86
|
-
API.get('/api/mcp-servers'),
|
|
87
|
-
]);
|
|
88
|
-
// Process dashboard status (required)
|
|
89
|
-
if (dashboardResult.status === 'fulfilled') {
|
|
90
|
-
this.data = dashboardResult.value;
|
|
78
|
+
this.data = await API.get('/api/dashboard/status');
|
|
79
|
+
// Load multi-agent status (Sprint 3 F2)
|
|
80
|
+
try {
|
|
81
|
+
this.multiAgentData = await API.get('/api/multi-agent/status');
|
|
91
82
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
// Process multi-agent status
|
|
96
|
-
if (multiAgentResult.status === 'fulfilled') {
|
|
97
|
-
this.multiAgentData = multiAgentResult.value;
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
logger.warn('[Dashboard] Multi-agent status unavailable:', multiAgentResult.reason);
|
|
83
|
+
catch (e) {
|
|
84
|
+
logger.warn('[Dashboard] Multi-agent status unavailable:', e);
|
|
101
85
|
this.multiAgentData = { enabled: false, agents: [] };
|
|
102
86
|
}
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
this.delegationsData =
|
|
87
|
+
// Load delegations (F4 endpoint)
|
|
88
|
+
try {
|
|
89
|
+
this.delegationsData = await API.get('/api/multi-agent/delegations?limit=10');
|
|
106
90
|
}
|
|
107
|
-
|
|
108
|
-
logger.warn('[Dashboard] Delegations unavailable:',
|
|
91
|
+
catch (e) {
|
|
92
|
+
logger.warn('[Dashboard] Delegations unavailable:', e);
|
|
109
93
|
this.delegationsData = { delegations: [], count: 0 };
|
|
110
94
|
}
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
this.cronData =
|
|
95
|
+
// Load cron jobs
|
|
96
|
+
try {
|
|
97
|
+
this.cronData = await API.getCronJobs();
|
|
114
98
|
}
|
|
115
|
-
|
|
116
|
-
logger.warn('[Dashboard] Cron data unavailable:',
|
|
99
|
+
catch (e) {
|
|
100
|
+
logger.warn('[Dashboard] Cron data unavailable:', e);
|
|
117
101
|
this.cronData = null;
|
|
118
102
|
}
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
const [summary, byAgent] =
|
|
103
|
+
// Load token summary
|
|
104
|
+
try {
|
|
105
|
+
const [summary, byAgent] = await Promise.all([
|
|
106
|
+
API.getTokenSummary(),
|
|
107
|
+
API.getTokensByAgent(),
|
|
108
|
+
]);
|
|
122
109
|
this.tokenData = { summary, byAgent };
|
|
123
110
|
}
|
|
124
|
-
|
|
125
|
-
logger.warn('[Dashboard] Token data unavailable:',
|
|
111
|
+
catch (e) {
|
|
112
|
+
logger.warn('[Dashboard] Token data unavailable:', e);
|
|
126
113
|
this.tokenData = null;
|
|
127
114
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
this.mcpServers = mcpResult.value.servers || [];
|
|
131
|
-
this.renderMCPServers();
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
logger.warn('[Dashboard] MCP servers unavailable');
|
|
135
|
-
this.mcpServers = [];
|
|
136
|
-
}
|
|
115
|
+
// Load MCP servers
|
|
116
|
+
await this.loadMCPServers();
|
|
137
117
|
this.render();
|
|
138
118
|
this.setStatus(`Last updated: ${new Date().toLocaleTimeString()}`);
|
|
139
119
|
}
|
|
@@ -142,6 +122,21 @@ export class DashboardModule {
|
|
|
142
122
|
this.setStatus(`Error: ${getErrorMessage(error)}`, 'error');
|
|
143
123
|
}
|
|
144
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Load MCP servers from API
|
|
127
|
+
*/
|
|
128
|
+
async loadMCPServers() {
|
|
129
|
+
try {
|
|
130
|
+
const data = await API.get('/api/mcp-servers');
|
|
131
|
+
if (data && 'servers' in data) {
|
|
132
|
+
this.mcpServers = data.servers || [];
|
|
133
|
+
this.renderMCPServers();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
logger.error('Failed to load MCP servers:', error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
145
140
|
/**
|
|
146
141
|
* Render all dashboard sections
|
|
147
142
|
*/
|
|
@@ -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
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
}
|
|
@@ -98,6 +98,7 @@ export class ChatModule {
|
|
|
98
98
|
history: ChatHistoryMessage[] = [];
|
|
99
99
|
historyPrefix = 'mama_chat_history_';
|
|
100
100
|
maxHistoryMessages = 50;
|
|
101
|
+
maxDomMessages = 100; // Limit DOM elements for performance
|
|
101
102
|
historyExpiryMs = 24 * 60 * 60 * 1000;
|
|
102
103
|
checkpointCooldown = false;
|
|
103
104
|
COOLDOWN_MS = 60 * 1000;
|
|
@@ -147,7 +148,10 @@ export class ChatModule {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
async autoCheckpoint(): Promise<void> {
|
|
150
|
-
// Auto-checkpoint
|
|
151
|
+
// DISABLED: Auto-checkpoint was saving raw conversation history to MAMA memory.
|
|
152
|
+
// Checkpoints should only be saved manually via /checkpoint command with proper summaries.
|
|
153
|
+
// The viewer chat uses localStorage for session persistence instead.
|
|
154
|
+
logger.info('Auto-checkpoint disabled (use /checkpoint for manual saves)');
|
|
151
155
|
return;
|
|
152
156
|
}
|
|
153
157
|
|
|
@@ -1657,7 +1661,7 @@ export class ChatModule {
|
|
|
1657
1661
|
}
|
|
1658
1662
|
|
|
1659
1663
|
/**
|
|
1660
|
-
* Restore chat history
|
|
1664
|
+
* Restore chat history (optimized with DocumentFragment)
|
|
1661
1665
|
*/
|
|
1662
1666
|
restoreHistory(sessionId: string): boolean {
|
|
1663
1667
|
const history = this.loadHistory(sessionId);
|
|
@@ -1674,7 +1678,12 @@ export class ChatModule {
|
|
|
1674
1678
|
|
|
1675
1679
|
this.removePlaceholder();
|
|
1676
1680
|
|
|
1677
|
-
|
|
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) => {
|
|
1678
1687
|
const msgEl = document.createElement('div');
|
|
1679
1688
|
msgEl.className = `chat-message ${msg.role}`;
|
|
1680
1689
|
|
|
@@ -1706,9 +1715,10 @@ export class ChatModule {
|
|
|
1706
1715
|
`;
|
|
1707
1716
|
}
|
|
1708
1717
|
|
|
1709
|
-
|
|
1718
|
+
fragment.appendChild(msgEl);
|
|
1710
1719
|
});
|
|
1711
1720
|
|
|
1721
|
+
container.appendChild(fragment);
|
|
1712
1722
|
scrollToBottom(container);
|
|
1713
1723
|
showToast('Previous conversation restored');
|
|
1714
1724
|
|
|
@@ -1716,7 +1726,7 @@ export class ChatModule {
|
|
|
1716
1726
|
}
|
|
1717
1727
|
|
|
1718
1728
|
/**
|
|
1719
|
-
* Display history received from server
|
|
1729
|
+
* Display history received from server (optimized with DocumentFragment)
|
|
1720
1730
|
*/
|
|
1721
1731
|
displayHistory(messages: ChatHistoryMessage[]): void {
|
|
1722
1732
|
const container = getElementByIdOrNull<HTMLDivElement>('chat-messages');
|
|
@@ -1731,9 +1741,14 @@ export class ChatModule {
|
|
|
1731
1741
|
}
|
|
1732
1742
|
|
|
1733
1743
|
container.innerHTML = '';
|
|
1734
|
-
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);
|
|
1735
1750
|
|
|
1736
|
-
|
|
1751
|
+
messagesToRender.forEach((msg) => {
|
|
1737
1752
|
const msgEl = document.createElement('div');
|
|
1738
1753
|
msgEl.className = `chat-message ${msg.role}`;
|
|
1739
1754
|
|
|
@@ -1755,11 +1770,12 @@ export class ChatModule {
|
|
|
1755
1770
|
`;
|
|
1756
1771
|
}
|
|
1757
1772
|
|
|
1758
|
-
|
|
1773
|
+
fragment.appendChild(msgEl);
|
|
1759
1774
|
});
|
|
1760
1775
|
|
|
1776
|
+
container.appendChild(fragment);
|
|
1761
1777
|
scrollToBottom(container);
|
|
1762
|
-
logger.info('Displayed',
|
|
1778
|
+
logger.info('Displayed', messagesToRender.length, 'history messages');
|
|
1763
1779
|
}
|
|
1764
1780
|
|
|
1765
1781
|
/**
|
|
@@ -2079,11 +2095,16 @@ export class ChatModule {
|
|
|
2079
2095
|
return;
|
|
2080
2096
|
}
|
|
2081
2097
|
|
|
2082
|
-
const
|
|
2098
|
+
const isClosed = panel.classList.contains('chat-panel-closed');
|
|
2099
|
+
const shouldOpen = forceState !== undefined ? forceState : isClosed;
|
|
2083
2100
|
|
|
2084
2101
|
if (shouldOpen) {
|
|
2085
|
-
|
|
2086
|
-
|
|
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');
|
|
2087
2108
|
this.restorePanelState(panel);
|
|
2088
2109
|
if (bubble) {
|
|
2089
2110
|
bubble.classList.add('scale-0');
|
|
@@ -2100,8 +2121,8 @@ export class ChatModule {
|
|
|
2100
2121
|
messages.scrollTop = messages.scrollHeight;
|
|
2101
2122
|
}
|
|
2102
2123
|
} else {
|
|
2103
|
-
panel.classList.add('
|
|
2104
|
-
panel.classList.remove('animate-slide-up');
|
|
2124
|
+
panel.classList.add('chat-panel-closed');
|
|
2125
|
+
panel.classList.remove('chat-panel-open', 'animate-slide-up');
|
|
2105
2126
|
if (bubble) {
|
|
2106
2127
|
bubble.classList.remove('scale-0');
|
|
2107
2128
|
}
|
|
@@ -2157,7 +2178,7 @@ export class ChatModule {
|
|
|
2157
2178
|
*/
|
|
2158
2179
|
isFloatingOpen(): boolean {
|
|
2159
2180
|
const panel = getElementByIdOrNull<HTMLDivElement>('chat-panel');
|
|
2160
|
-
return Boolean(panel &&
|
|
2181
|
+
return Boolean(panel && panel.classList.contains('chat-panel-open'));
|
|
2161
2182
|
}
|
|
2162
2183
|
|
|
2163
2184
|
/**
|
|
@@ -177,75 +177,51 @@ export class DashboardModule {
|
|
|
177
177
|
|
|
178
178
|
/**
|
|
179
179
|
* Load dashboard status from API
|
|
180
|
-
* Uses Promise.allSettled for parallel loading to improve performance
|
|
181
180
|
*/
|
|
182
181
|
async loadStatus(): Promise<void> {
|
|
183
182
|
try {
|
|
184
|
-
|
|
185
|
-
const [
|
|
186
|
-
dashboardResult,
|
|
187
|
-
multiAgentResult,
|
|
188
|
-
delegationsResult,
|
|
189
|
-
cronResult,
|
|
190
|
-
tokenResult,
|
|
191
|
-
mcpResult,
|
|
192
|
-
] = await Promise.allSettled([
|
|
193
|
-
API.get<DashboardData>('/api/dashboard/status'),
|
|
194
|
-
API.get<MultiAgentDashboardStatus>('/api/multi-agent/status'),
|
|
195
|
-
API.get<DashboardDelegationsData>('/api/multi-agent/delegations?limit=10'),
|
|
196
|
-
API.getCronJobs(),
|
|
197
|
-
Promise.all([API.getTokenSummary(), API.getTokensByAgent()]),
|
|
198
|
-
API.get<McpServersResponse>('/api/mcp-servers'),
|
|
199
|
-
]);
|
|
200
|
-
|
|
201
|
-
// Process dashboard status (required)
|
|
202
|
-
if (dashboardResult.status === 'fulfilled') {
|
|
203
|
-
this.data = dashboardResult.value;
|
|
204
|
-
} else {
|
|
205
|
-
throw dashboardResult.reason;
|
|
206
|
-
}
|
|
183
|
+
this.data = await API.get<DashboardData>('/api/dashboard/status');
|
|
207
184
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
this.multiAgentData =
|
|
211
|
-
}
|
|
212
|
-
logger.warn('[Dashboard] Multi-agent status unavailable:',
|
|
185
|
+
// Load multi-agent status (Sprint 3 F2)
|
|
186
|
+
try {
|
|
187
|
+
this.multiAgentData = await API.get<MultiAgentDashboardStatus>('/api/multi-agent/status');
|
|
188
|
+
} catch (e) {
|
|
189
|
+
logger.warn('[Dashboard] Multi-agent status unavailable:', e);
|
|
213
190
|
this.multiAgentData = { enabled: false, agents: [] };
|
|
214
191
|
}
|
|
215
192
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
this.delegationsData =
|
|
219
|
-
|
|
220
|
-
|
|
193
|
+
// Load delegations (F4 endpoint)
|
|
194
|
+
try {
|
|
195
|
+
this.delegationsData = await API.get<DashboardDelegationsData>(
|
|
196
|
+
'/api/multi-agent/delegations?limit=10'
|
|
197
|
+
);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
logger.warn('[Dashboard] Delegations unavailable:', e);
|
|
221
200
|
this.delegationsData = { delegations: [], count: 0 };
|
|
222
201
|
}
|
|
223
202
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
this.cronData =
|
|
227
|
-
}
|
|
228
|
-
logger.warn('[Dashboard] Cron data unavailable:',
|
|
203
|
+
// Load cron jobs
|
|
204
|
+
try {
|
|
205
|
+
this.cronData = await API.getCronJobs();
|
|
206
|
+
} catch (e) {
|
|
207
|
+
logger.warn('[Dashboard] Cron data unavailable:', e);
|
|
229
208
|
this.cronData = null;
|
|
230
209
|
}
|
|
231
210
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
const [summary, byAgent] =
|
|
211
|
+
// Load token summary
|
|
212
|
+
try {
|
|
213
|
+
const [summary, byAgent] = await Promise.all([
|
|
214
|
+
API.getTokenSummary(),
|
|
215
|
+
API.getTokensByAgent(),
|
|
216
|
+
]);
|
|
235
217
|
this.tokenData = { summary, byAgent };
|
|
236
|
-
}
|
|
237
|
-
logger.warn('[Dashboard] Token data unavailable:',
|
|
218
|
+
} catch (e) {
|
|
219
|
+
logger.warn('[Dashboard] Token data unavailable:', e);
|
|
238
220
|
this.tokenData = null;
|
|
239
221
|
}
|
|
240
222
|
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
this.mcpServers = mcpResult.value.servers || [];
|
|
244
|
-
this.renderMCPServers();
|
|
245
|
-
} else {
|
|
246
|
-
logger.warn('[Dashboard] MCP servers unavailable');
|
|
247
|
-
this.mcpServers = [];
|
|
248
|
-
}
|
|
223
|
+
// Load MCP servers
|
|
224
|
+
await this.loadMCPServers();
|
|
249
225
|
|
|
250
226
|
this.render();
|
|
251
227
|
this.setStatus(`Last updated: ${new Date().toLocaleTimeString()}`);
|
|
@@ -255,6 +231,21 @@ export class DashboardModule {
|
|
|
255
231
|
}
|
|
256
232
|
}
|
|
257
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Load MCP servers from API
|
|
236
|
+
*/
|
|
237
|
+
async loadMCPServers(): Promise<void> {
|
|
238
|
+
try {
|
|
239
|
+
const data = await API.get<McpServersResponse>('/api/mcp-servers');
|
|
240
|
+
if (data && 'servers' in data) {
|
|
241
|
+
this.mcpServers = data.servers || [];
|
|
242
|
+
this.renderMCPServers();
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
logger.error('Failed to load MCP servers:', error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
258
249
|
/**
|
|
259
250
|
* Render all dashboard sections
|
|
260
251
|
*/
|
|
@@ -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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
Number.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
}
|
package/public/viewer/viewer.css
CHANGED
|
@@ -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
|
}
|