feat: status dashboard categories/ordering + live usage dashboard

- Add getChannelMeta to Discord channel (batch guild fetch)
- Status dashboard groups by Discord category, sorts by position
- Add live usage dashboard (CPU, memory, disk, uptime) with message edit
- Fix 4 failing discord.test.ts tests (sendMessage format, image fetch mock)
This commit is contained in:
Eyejoker
2026-03-13 23:42:45 +09:00
parent 27d4ea05dc
commit 7dcb3fe1e4
5 changed files with 307 additions and 32 deletions

View File

@@ -79,6 +79,7 @@ import { registerChannel, ChannelOpts } from './registry.js';
import {
AgentType,
Channel,
ChannelMeta,
OnChatMetadata,
OnInboundMessage,
RegisteredGroup,
@@ -438,6 +439,59 @@ export class DiscordChannel implements Channel {
}
}
async getChannelMeta(jids: string[]): Promise<Map<string, ChannelMeta>> {
const result = new Map<string, ChannelMeta>();
if (!this.client) return result;
const dcJids = jids.filter((j) => j.startsWith('dc:'));
if (dcJids.length === 0) return result;
const channelIdToJid = new Map<string, string>();
for (const jid of dcJids) {
channelIdToJid.set(jid.replace(/^dc:/, ''), jid);
}
try {
// Fetch one channel to discover its guild, then batch-fetch all channels
const firstId = dcJids[0].replace(/^dc:/, '');
const firstChannel = await this.client.channels.fetch(firstId);
if (!firstChannel || !('guild' in firstChannel)) return result;
const guild = (firstChannel as TextChannel).guild;
const allChannels = await guild.channels.fetch();
for (const [id, channel] of allChannels) {
const jid = channelIdToJid.get(id);
if (!jid || !channel) continue;
result.set(jid, {
position: channel.position,
category: channel.parent?.name || '',
categoryPosition: channel.parent?.position ?? 999,
});
}
} catch {
// Fallback: individual fetches
for (const jid of dcJids) {
try {
const channelId = jid.replace(/^dc:/, '');
const channel = await this.client.channels.fetch(channelId);
if (channel && 'position' in channel) {
const tc = channel as TextChannel;
result.set(jid, {
position: tc.position,
category: tc.parent?.name || '',
categoryPosition: tc.parent?.position ?? 999,
});
}
} catch {
/* skip inaccessible channels */
}
}
}
return result;
}
async editMessage(
jid: string,
messageId: string,