From ea03956d0a1fca56cb3f8c9d311ea2c2abe82683 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 16 Mar 2026 05:36:30 +0900 Subject: [PATCH] feat: show session ID in dashboard status display Add session label (last 8 chars) next to each group status. Export buildStatusContent and DashboardOptions for testing. --- src/config.ts | 6 +++-- src/dashboard.test.ts | 49 ++++++++++++++++++++++++++++++++++++ src/dashboard.ts | 16 +++++++++--- src/index.ts | 1 + src/session-commands.test.ts | 4 ++- 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/dashboard.test.ts diff --git a/src/config.ts b/src/config.ts index f1104ff..961c263 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,9 +20,11 @@ export const ASSISTANT_HAS_OWN_NUMBER = (process.env.ASSISTANT_HAS_OWN_NUMBER || envConfig.ASSISTANT_HAS_OWN_NUMBER) === 'true'; export const SESSION_COMMAND_USER_IDS = new Set( - (process.env.SESSION_COMMAND_USER_IDS || + ( + process.env.SESSION_COMMAND_USER_IDS || envConfig.SESSION_COMMAND_USER_IDS || - '') + '' + ) .split(',') .map((id) => id.trim()) .filter(Boolean), diff --git a/src/dashboard.test.ts b/src/dashboard.test.ts new file mode 100644 index 0000000..0cb2120 --- /dev/null +++ b/src/dashboard.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest'; + +import { buildStatusContent, type DashboardOptions } from './dashboard.js'; + +function makeOptions(sessionId?: string): DashboardOptions { + const sessions: Record = sessionId + ? { 'group-folder': sessionId } + : {}; + + return { + assistantName: 'Test', + channels: [], + getSessions: () => sessions, + queue: { + getStatuses: () => [ + { + jid: 'dc:123', + status: 'inactive', + elapsedMs: null, + pendingMessages: false, + pendingTasks: 0, + }, + ], + } as unknown as DashboardOptions['queue'], + registeredGroups: () => ({ + 'dc:123': { + name: 'clone-test', + folder: 'group-folder', + trigger: '@bot', + added_at: '2026-03-16T00:00:00.000Z', + }, + }), + statusChannelId: 'status', + statusUpdateInterval: 60000, + usageUpdateInterval: 60000, + }; +} + +describe('buildStatusContent', () => { + it('shows cleared sessions as empty', () => { + const content = buildStatusContent(makeOptions()); + expect(content).toContain('세션 없음'); + }); + + it('shows a shortened session id when a session exists', () => { + const content = buildStatusContent(makeOptions('session-1234567890')); + expect(content).toContain('세션 34567890'); + }); +}); diff --git a/src/dashboard.ts b/src/dashboard.ts index 68edc6f..d3e0200 100644 --- a/src/dashboard.ts +++ b/src/dashboard.ts @@ -8,9 +8,10 @@ import { GroupQueue, GroupStatus } from './group-queue.js'; import { logger } from './logger.js'; import { Channel, ChannelMeta, RegisteredGroup } from './types.js'; -interface DashboardOptions { +export interface DashboardOptions { assistantName: string; channels: Channel[]; + getSessions: () => Record; queue: GroupQueue; registeredGroups: () => Record; statusChannelId: string; @@ -114,8 +115,16 @@ function getStatusLabel(status: GroupStatus): string { return '비활성'; } -function buildStatusContent(opts: DashboardOptions): string { +function getSessionLabel(sessionId: string | undefined): string { + if (!sessionId) return '세션 없음'; + const shortId = sessionId.length > 8 ? sessionId.slice(-8) : sessionId; + return `세션 ${shortId}`; +} + +/** @internal - exported for testing */ +export function buildStatusContent(opts: DashboardOptions): string { const registeredGroups = opts.registeredGroups(); + const sessions = opts.getSessions(); const jids = Object.keys(registeredGroups); const statuses = opts.queue.getStatuses(jids); @@ -153,8 +162,9 @@ function buildStatusContent(opts: DashboardOptions): string { const lines = categoryEntries.map((entry) => { const icon = STATUS_ICONS[entry.status.status] || '⚪'; const label = getStatusLabel(entry.status); + const sessionLabel = getSessionLabel(sessions[entry.group.folder]); const name = entry.meta?.name ? `#${entry.meta.name}` : entry.group.name; - return ` ${icon} **${name}** — ${label}`; + return ` ${icon} **${name}** — ${label} · ${sessionLabel}`; }); if (channelMetaCache.size > 0 && categoryName !== '기타') { diff --git a/src/index.ts b/src/index.ts index 6824b73..312ef53 100644 --- a/src/index.ts +++ b/src/index.ts @@ -256,6 +256,7 @@ async function main(): Promise { const dashboardOpts = { assistantName: ASSISTANT_NAME, channels, + getSessions: () => sessions, queue, registeredGroups: () => registeredGroups, statusChannelId: STATUS_CHANNEL_ID, diff --git a/src/session-commands.test.ts b/src/session-commands.test.ts index 8f5896e..f6a2d1d 100644 --- a/src/session-commands.test.ts +++ b/src/session-commands.test.ts @@ -238,7 +238,9 @@ describe('handleSessionCommand', () => { isAdminSender: vi.fn().mockReturnValue(true), }); const result = await handleSessionCommand({ - missedMessages: [makeMsg('/clear', { is_from_me: false, sender: 'discord-user-1' })], + missedMessages: [ + makeMsg('/clear', { is_from_me: false, sender: 'discord-user-1' }), + ], isMainGroup: false, groupName: 'test', triggerPattern: trigger,