@link-assistant/hive-mind 0.42.2 โ 0.42.3
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 +17 -0
- package/package.json +1 -1
- package/src/telegram-bot.mjs +28 -36
- package/src/telegram-top-command.lib.mjs +325 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.42.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 64d6cf8: Add experimental /top command to Telegram bot
|
|
8
|
+
|
|
9
|
+
- Added /top command to show live system monitor in Telegram
|
|
10
|
+
- Displays auto-updating `top` output in a single message (updates every 2 seconds)
|
|
11
|
+
- Owner-only access with chat authorization checks
|
|
12
|
+
- Session isolation per chat using GNU screen
|
|
13
|
+
- Clean stop button to terminate monitoring session
|
|
14
|
+
- Marked as EXPERIMENTAL feature with user warnings
|
|
15
|
+
- Not documented in /help as requested
|
|
16
|
+
- Requires GNU screen to be installed on the system
|
|
17
|
+
|
|
18
|
+
Fixes #500
|
|
19
|
+
|
|
3
20
|
## 0.42.2
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/package.json
CHANGED
package/src/telegram-bot.mjs
CHANGED
|
@@ -1225,45 +1225,37 @@ bot.command(/^hive$/i, async (ctx) => {
|
|
|
1225
1225
|
}
|
|
1226
1226
|
});
|
|
1227
1227
|
|
|
1228
|
+
// Register /top command from separate module
|
|
1229
|
+
// This keeps telegram-bot.mjs under the 1500 line limit
|
|
1230
|
+
const { registerTopCommand } = await import('./telegram-top-command.lib.mjs');
|
|
1231
|
+
registerTopCommand(bot, {
|
|
1232
|
+
VERBOSE,
|
|
1233
|
+
isOldMessage,
|
|
1234
|
+
isForwardedOrReply,
|
|
1235
|
+
isGroupChat,
|
|
1236
|
+
isChatAuthorized
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1228
1239
|
// Add message listener for verbose debugging
|
|
1229
|
-
// This helps diagnose if bot is receiving messages at all
|
|
1230
1240
|
if (VERBOSE) {
|
|
1231
1241
|
bot.on('message', (ctx, next) => {
|
|
1232
|
-
console.log('[VERBOSE] Message received:');
|
|
1233
|
-
console.log('[VERBOSE] Chat ID:', ctx.chat?.id);
|
|
1234
|
-
console.log('[VERBOSE] Chat type:', ctx.chat?.type);
|
|
1235
|
-
console.log('[VERBOSE] Is forum:', ctx.chat?.is_forum);
|
|
1236
|
-
console.log('[VERBOSE] Is topic message:', ctx.message?.is_topic_message);
|
|
1237
|
-
console.log('[VERBOSE] Message thread ID:', ctx.message?.message_thread_id);
|
|
1238
|
-
console.log('[VERBOSE] Message date:', ctx.message?.date);
|
|
1239
|
-
console.log('[VERBOSE] Message text:', ctx.message?.text?.substring(0, 100));
|
|
1240
|
-
console.log('[VERBOSE] From user:', ctx.from?.username || ctx.from?.id);
|
|
1241
|
-
console.log('[VERBOSE] Bot start time:', BOT_START_TIME);
|
|
1242
|
-
console.log('[VERBOSE] Is old message:', isOldMessage(ctx));
|
|
1243
|
-
|
|
1244
|
-
// Detailed forwarding/reply detection debug info
|
|
1245
1242
|
const msg = ctx.message;
|
|
1246
|
-
|
|
1247
|
-
|
|
1243
|
+
console.log('[VERBOSE] Message:', {
|
|
1244
|
+
chatId: ctx.chat?.id, chatType: ctx.chat?.type, isForum: ctx.chat?.is_forum,
|
|
1245
|
+
isTopicMsg: msg?.is_topic_message, threadId: msg?.message_thread_id, date: msg?.date,
|
|
1246
|
+
text: msg?.text?.substring(0, 100), user: ctx.from?.username || ctx.from?.id,
|
|
1247
|
+
botStartTime: BOT_START_TIME, isOld: isOldMessage(ctx), isForwarded: isForwardedOrReply(ctx),
|
|
1248
|
+
isAuthorized: isChatAuthorized(ctx.chat?.id)
|
|
1249
|
+
});
|
|
1248
1250
|
if (msg) {
|
|
1249
|
-
|
|
1250
|
-
console.log('[VERBOSE]
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
console.log('[VERBOSE] - forward_from_chat:', JSON.stringify(msg.forward_from_chat));
|
|
1257
|
-
console.log('[VERBOSE] - forward_date:', msg.forward_date);
|
|
1258
|
-
console.log('[VERBOSE] - reply_to_message:', JSON.stringify(msg.reply_to_message));
|
|
1259
|
-
console.log('[VERBOSE] - reply_to_message type:', typeof msg.reply_to_message);
|
|
1260
|
-
console.log('[VERBOSE] - reply_to_message truthy?:', !!msg.reply_to_message);
|
|
1261
|
-
console.log('[VERBOSE] - reply_to_message.message_id:', msg.reply_to_message?.message_id);
|
|
1262
|
-
console.log('[VERBOSE] - reply_to_message.forum_topic_created:', JSON.stringify(msg.reply_to_message?.forum_topic_created));
|
|
1251
|
+
console.log('[VERBOSE] Msg fields:', Object.keys(msg));
|
|
1252
|
+
console.log('[VERBOSE] Forward/reply:', {
|
|
1253
|
+
forward_origin: msg.forward_origin, forward_from: msg.forward_from,
|
|
1254
|
+
forward_from_chat: msg.forward_from_chat, forward_date: msg.forward_date,
|
|
1255
|
+
reply_to_message: msg.reply_to_message, reply_id: msg.reply_to_message?.message_id,
|
|
1256
|
+
forum_topic_created: msg.reply_to_message?.forum_topic_created
|
|
1257
|
+
});
|
|
1263
1258
|
}
|
|
1264
|
-
|
|
1265
|
-
console.log('[VERBOSE] Is authorized:', isChatAuthorized(ctx.chat?.id));
|
|
1266
|
-
// Continue to next handler
|
|
1267
1259
|
return next();
|
|
1268
1260
|
});
|
|
1269
1261
|
}
|
|
@@ -1391,9 +1383,9 @@ bot.telegram.deleteWebhook({ drop_pending_updates: true })
|
|
|
1391
1383
|
});
|
|
1392
1384
|
}
|
|
1393
1385
|
return bot.launch({
|
|
1394
|
-
//
|
|
1395
|
-
// This ensures the bot receives all message types including commands
|
|
1396
|
-
allowedUpdates: ['message'],
|
|
1386
|
+
// Receive message updates (commands, text messages) and callback queries (button clicks)
|
|
1387
|
+
// This ensures the bot receives all message types including commands and button interactions
|
|
1388
|
+
allowedUpdates: ['message', 'callback_query'],
|
|
1397
1389
|
// Drop any pending updates that were sent before the bot started
|
|
1398
1390
|
// This ensures we only process new messages sent after this bot instance started
|
|
1399
1391
|
dropPendingUpdates: true
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram /top command implementation
|
|
3
|
+
*
|
|
4
|
+
* This module provides the /top command functionality for the Telegram bot,
|
|
5
|
+
* allowing chat owners to view live system monitor output in an auto-updating message.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Live system monitoring using GNU screen and top command
|
|
9
|
+
* - Auto-updates every 2 seconds
|
|
10
|
+
* - Owner-only access control
|
|
11
|
+
* - Session management per chat
|
|
12
|
+
* - Clean cleanup on stop
|
|
13
|
+
*
|
|
14
|
+
* @experimental This feature is marked as experimental
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { promisify } from 'util';
|
|
18
|
+
import { exec as execCallback } from 'child_process';
|
|
19
|
+
|
|
20
|
+
const exec = promisify(execCallback);
|
|
21
|
+
|
|
22
|
+
// Store active top sessions: Map<chatId, { messageId, screenName, intervalId }>
|
|
23
|
+
const activeTopSessions = new Map();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Captures top output from the file for a given chat
|
|
27
|
+
* @param {number} chatId - The chat ID
|
|
28
|
+
* @returns {Promise<string|null>} The formatted top output or null on error
|
|
29
|
+
*/
|
|
30
|
+
async function captureTopOutput(chatId) {
|
|
31
|
+
try {
|
|
32
|
+
const outputFile = `/tmp/top-output-${chatId}.txt`;
|
|
33
|
+
const { readFile } = await import('fs/promises');
|
|
34
|
+
const output = await readFile(outputFile, 'utf-8');
|
|
35
|
+
|
|
36
|
+
// Format output for Telegram (limit to first 30 lines to fit in message)
|
|
37
|
+
const lines = output.split('\n').slice(0, 30);
|
|
38
|
+
return lines.join('\n');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('[ERROR] Failed to capture top output:', error);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Registers the /top command handler with the bot
|
|
47
|
+
* @param {Object} bot - The Telegraf bot instance
|
|
48
|
+
* @param {Object} options - Options object
|
|
49
|
+
* @param {boolean} options.VERBOSE - Whether to enable verbose logging
|
|
50
|
+
* @param {Function} options.isOldMessage - Function to check if message is old
|
|
51
|
+
* @param {Function} options.isForwardedOrReply - Function to check if message is forwarded/reply
|
|
52
|
+
* @param {Function} options.isGroupChat - Function to check if chat is a group
|
|
53
|
+
* @param {Function} options.isChatAuthorized - Function to check if chat is authorized
|
|
54
|
+
*/
|
|
55
|
+
export function registerTopCommand(bot, options) {
|
|
56
|
+
const {
|
|
57
|
+
VERBOSE = false,
|
|
58
|
+
isOldMessage,
|
|
59
|
+
isForwardedOrReply,
|
|
60
|
+
isGroupChat,
|
|
61
|
+
isChatAuthorized
|
|
62
|
+
} = options;
|
|
63
|
+
|
|
64
|
+
// /top command - show system top output in an auto-updating message (EXPERIMENTAL)
|
|
65
|
+
// Only accessible by chat owner
|
|
66
|
+
// Not documented in /help as requested in issue #500
|
|
67
|
+
bot.command('top', async (ctx) => {
|
|
68
|
+
if (VERBOSE) {
|
|
69
|
+
console.log('[VERBOSE] /top command received');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ignore messages sent before bot started
|
|
73
|
+
if (isOldMessage(ctx)) {
|
|
74
|
+
if (VERBOSE) {
|
|
75
|
+
console.log('[VERBOSE] /top ignored: old message');
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Ignore forwarded or reply messages
|
|
81
|
+
if (isForwardedOrReply(ctx)) {
|
|
82
|
+
if (VERBOSE) {
|
|
83
|
+
console.log('[VERBOSE] /top ignored: forwarded or reply');
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!isGroupChat(ctx)) {
|
|
89
|
+
if (VERBOSE) {
|
|
90
|
+
console.log('[VERBOSE] /top ignored: not a group chat');
|
|
91
|
+
}
|
|
92
|
+
await ctx.reply('โ The /top command only works in group chats.', { reply_to_message_id: ctx.message.message_id });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const chatId = ctx.chat.id;
|
|
97
|
+
if (!isChatAuthorized(chatId)) {
|
|
98
|
+
if (VERBOSE) {
|
|
99
|
+
console.log('[VERBOSE] /top ignored: chat not authorized');
|
|
100
|
+
}
|
|
101
|
+
await ctx.reply(`โ This chat (ID: ${chatId}) is not authorized to use this bot.`, { reply_to_message_id: ctx.message.message_id });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if user is chat owner
|
|
106
|
+
try {
|
|
107
|
+
const chatMember = await ctx.telegram.getChatMember(chatId, ctx.from.id);
|
|
108
|
+
if (chatMember.status !== 'creator') {
|
|
109
|
+
if (VERBOSE) {
|
|
110
|
+
console.log('[VERBOSE] /top ignored: user is not chat owner');
|
|
111
|
+
}
|
|
112
|
+
await ctx.reply('โ This command is only available to the chat owner.', { reply_to_message_id: ctx.message.message_id });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('[ERROR] Failed to check chat member status:', error);
|
|
117
|
+
await ctx.reply('โ Failed to verify permissions.', { reply_to_message_id: ctx.message.message_id });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (VERBOSE) {
|
|
122
|
+
console.log('[VERBOSE] /top passed all checks, starting...');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Show experimental feature warning
|
|
126
|
+
await ctx.reply('๐งช *EXPERIMENTAL FEATURE*\n\nThis command is experimental and may have issues. Use with caution.', {
|
|
127
|
+
parse_mode: 'Markdown',
|
|
128
|
+
reply_to_message_id: ctx.message.message_id
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Check if there's already an active top session for this chat
|
|
132
|
+
if (activeTopSessions.has(chatId)) {
|
|
133
|
+
await ctx.reply('โ A top session is already running for this chat. Stop it first using the button.', { reply_to_message_id: ctx.message.message_id });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Generate screen session name with chat ID
|
|
138
|
+
const screenName = `top-chat-${chatId}`;
|
|
139
|
+
|
|
140
|
+
// Check if screen session already exists
|
|
141
|
+
let sessionExists = false;
|
|
142
|
+
try {
|
|
143
|
+
const { stdout } = await exec('screen -ls');
|
|
144
|
+
sessionExists = stdout.includes(screenName);
|
|
145
|
+
} catch {
|
|
146
|
+
// screen -ls returns non-zero when no sessions exist
|
|
147
|
+
sessionExists = false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create screen session if it doesn't exist
|
|
151
|
+
// We'll use a different approach: run top in batch mode with output redirected to a file
|
|
152
|
+
// that we continuously read instead of using screen hardcopy
|
|
153
|
+
const outputFile = `/tmp/top-output-${chatId}.txt`;
|
|
154
|
+
|
|
155
|
+
if (!sessionExists) {
|
|
156
|
+
try {
|
|
157
|
+
// Start top in a screen session with batch mode, outputting to a file
|
|
158
|
+
// -b: batch mode, -d 2: 2 second delay between updates, -n: number of iterations (unlimited)
|
|
159
|
+
await exec(`screen -dmS ${screenName} bash -c 'while true; do top -b -n 1 > ${outputFile}; sleep 2; done'`);
|
|
160
|
+
if (VERBOSE) {
|
|
161
|
+
console.log(`[VERBOSE] Created screen session: ${screenName}`);
|
|
162
|
+
}
|
|
163
|
+
// Give top a moment to start and produce first output
|
|
164
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('[ERROR] Failed to create screen session:', error);
|
|
167
|
+
await ctx.reply('โ Failed to start top command.', { reply_to_message_id: ctx.message.message_id });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Send initial message with loading indicator
|
|
173
|
+
const initialMessage = await ctx.reply('๐งช ๐ Loading system monitor... (EXPERIMENTAL)', {
|
|
174
|
+
reply_to_message_id: ctx.message.message_id,
|
|
175
|
+
reply_markup: {
|
|
176
|
+
inline_keyboard: [[
|
|
177
|
+
{ text: '๐ Stop', callback_data: `stop_top_${chatId}` }
|
|
178
|
+
]]
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Capture and display first output
|
|
183
|
+
const firstOutput = await captureTopOutput(chatId);
|
|
184
|
+
if (firstOutput) {
|
|
185
|
+
try {
|
|
186
|
+
await ctx.telegram.editMessageText(
|
|
187
|
+
chatId,
|
|
188
|
+
initialMessage.message_id,
|
|
189
|
+
undefined,
|
|
190
|
+
`\`\`\`\n${firstOutput}\n\`\`\``,
|
|
191
|
+
{
|
|
192
|
+
parse_mode: 'Markdown',
|
|
193
|
+
reply_markup: {
|
|
194
|
+
inline_keyboard: [[
|
|
195
|
+
{ text: '๐ Stop', callback_data: `stop_top_${chatId}` }
|
|
196
|
+
]]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error('[ERROR] Failed to update message:', error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Set up periodic update (every 2 seconds)
|
|
206
|
+
const intervalId = setInterval(async () => {
|
|
207
|
+
const output = await captureTopOutput(chatId);
|
|
208
|
+
if (output) {
|
|
209
|
+
try {
|
|
210
|
+
await ctx.telegram.editMessageText(
|
|
211
|
+
chatId,
|
|
212
|
+
initialMessage.message_id,
|
|
213
|
+
undefined,
|
|
214
|
+
`\`\`\`\n${output}\n\`\`\``,
|
|
215
|
+
{
|
|
216
|
+
parse_mode: 'Markdown',
|
|
217
|
+
reply_markup: {
|
|
218
|
+
inline_keyboard: [[
|
|
219
|
+
{ text: '๐ Stop', callback_data: `stop_top_${chatId}` }
|
|
220
|
+
]]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// Ignore "message is not modified" errors
|
|
226
|
+
if (!error.message?.includes('message is not modified')) {
|
|
227
|
+
console.error('[ERROR] Failed to update message:', error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}, 2000);
|
|
232
|
+
|
|
233
|
+
// Store session info
|
|
234
|
+
activeTopSessions.set(chatId, {
|
|
235
|
+
messageId: initialMessage.message_id,
|
|
236
|
+
screenName,
|
|
237
|
+
intervalId
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (VERBOSE) {
|
|
241
|
+
console.log(`[VERBOSE] Top session started for chat ${chatId}`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Handle stop button callback
|
|
246
|
+
bot.action(/^stop_top_(.+)$/, async (ctx) => {
|
|
247
|
+
const chatId = parseInt(ctx.match[1]);
|
|
248
|
+
|
|
249
|
+
if (VERBOSE) {
|
|
250
|
+
console.log(`[VERBOSE] Stop top callback received for chat ${chatId}`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check if user is chat owner
|
|
254
|
+
try {
|
|
255
|
+
const chatMember = await ctx.telegram.getChatMember(chatId, ctx.from.id);
|
|
256
|
+
if (chatMember.status !== 'creator') {
|
|
257
|
+
await ctx.answerCbQuery('โ Only the chat owner can stop the top session.');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('[ERROR] Failed to check chat member status:', error);
|
|
262
|
+
await ctx.answerCbQuery('โ Failed to verify permissions.');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const session = activeTopSessions.get(chatId);
|
|
267
|
+
if (!session) {
|
|
268
|
+
await ctx.answerCbQuery('โ No active top session found.');
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Stop the update interval
|
|
273
|
+
clearInterval(session.intervalId);
|
|
274
|
+
|
|
275
|
+
// Kill the screen session
|
|
276
|
+
try {
|
|
277
|
+
await exec(`screen -S ${session.screenName} -X quit`);
|
|
278
|
+
if (VERBOSE) {
|
|
279
|
+
console.log(`[VERBOSE] Killed screen session: ${session.screenName}`);
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('[ERROR] Failed to kill screen session:', error);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Clean up the output file
|
|
286
|
+
try {
|
|
287
|
+
const { unlink } = await import('fs/promises');
|
|
288
|
+
await unlink(`/tmp/top-output-${chatId}.txt`);
|
|
289
|
+
if (VERBOSE) {
|
|
290
|
+
console.log(`[VERBOSE] Cleaned up output file for chat ${chatId}`);
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
// Ignore file cleanup errors
|
|
294
|
+
if (VERBOSE) {
|
|
295
|
+
console.log(`[VERBOSE] Could not clean up output file: ${error.message}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Remove from active sessions
|
|
300
|
+
activeTopSessions.delete(chatId);
|
|
301
|
+
|
|
302
|
+
// Update the message to show it's stopped
|
|
303
|
+
try {
|
|
304
|
+
await ctx.editMessageText('๐ Top session stopped.', {
|
|
305
|
+
parse_mode: 'Markdown'
|
|
306
|
+
});
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error('[ERROR] Failed to edit message:', error);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
await ctx.answerCbQuery('โ
Top session stopped successfully.');
|
|
312
|
+
|
|
313
|
+
if (VERBOSE) {
|
|
314
|
+
console.log(`[VERBOSE] Top session stopped for chat ${chatId}`);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Gets information about active top sessions
|
|
321
|
+
* @returns {Map} Map of active sessions
|
|
322
|
+
*/
|
|
323
|
+
export function getActiveTopSessions() {
|
|
324
|
+
return activeTopSessions;
|
|
325
|
+
}
|