feat: show watcher counts in status dashboard header

This commit is contained in:
Eyejoker
2026-03-25 22:52:02 +09:00
parent 20ca6c10fc
commit c7ba983ea6
2 changed files with 105 additions and 2 deletions

View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vitest';
import {
formatStatusHeader,
summarizeWatcherTasks,
} from './unified-dashboard.js';
describe('summarizeWatcherTasks', () => {
it('counts active and paused watcher tasks only', () => {
const summary = summarizeWatcherTasks([
{
prompt:
'[BACKGROUND CI WATCH]\n\nWatch target:\nGitHub Actions run 1\n\nTask ID:\na',
status: 'active',
},
{
prompt:
'[BACKGROUND CI WATCH]\n\nWatch target:\nGitHub Actions run 2\n\nTask ID:\nb',
status: 'paused',
},
{
prompt:
'[BACKGROUND CI WATCH]\n\nWatch target:\nGitHub Actions run 3\n\nTask ID:\nc',
status: 'completed',
},
{
prompt: 'normal scheduled task',
status: 'active',
},
]);
expect(summary).toEqual({
active: 1,
paused: 1,
});
});
});
describe('formatStatusHeader', () => {
it('shows active watcher count in the dashboard header', () => {
expect(
formatStatusHeader({
totalActive: 3,
totalRooms: 8,
watchers: { active: 2, paused: 0 },
}),
).toBe('**📊 에이전트 상태** — 활성 3 / 8 | 감시 2');
});
it('adds paused watcher count only when present', () => {
expect(
formatStatusHeader({
totalActive: 3,
totalRooms: 8,
watchers: { active: 2, paused: 1 },
}),
).toBe('**📊 에이전트 상태** — 활성 3 / 8 | 감시 2 | 일시정지 1');
});
});

View File

@@ -25,9 +25,10 @@ import {
mergeClaudeDashboardAccounts,
type UsageRow,
} from './dashboard-usage-rows.js';
import { getAllChats, updateRegisteredGroupName } from './db.js';
import { getAllChats, getAllTasks, updateRegisteredGroupName } from './db.js';
import type { GroupQueue } from './group-queue.js';
import { logger } from './logger.js';
import { isWatchCiTask } from './task-watch-status.js';
import {
readStatusSnapshots,
writeStatusSnapshot,
@@ -37,6 +38,7 @@ import type {
Channel,
ChannelMeta,
RegisteredGroup,
ScheduledTask,
} from './types.js';
export interface UnifiedDashboardOptions {
@@ -81,6 +83,43 @@ let cachedCodexUsageRows: UsageRow[] = [];
/** Codex service only: ISO timestamp of last successful usage fetch. */
let codexUsageFetchedAt: string | null = null;
export interface WatcherTaskSummary {
active: number;
paused: number;
}
export function summarizeWatcherTasks(
tasks: Array<Pick<ScheduledTask, 'prompt' | 'status'>>,
): WatcherTaskSummary {
let active = 0;
let paused = 0;
for (const task of tasks) {
if (!isWatchCiTask(task)) continue;
if (task.status === 'active') active += 1;
if (task.status === 'paused') paused += 1;
}
return { active, paused };
}
export function formatStatusHeader(args: {
totalActive: number;
totalRooms: number;
watchers: WatcherTaskSummary;
}): string {
const parts = [
`**📊 에이전트 상태** — 활성 ${args.totalActive} / ${args.totalRooms}`,
`감시 ${args.watchers.active}`,
];
if (args.watchers.paused > 0) {
parts.push(`일시정지 ${args.watchers.paused}`);
}
return parts.join(' | ');
}
function findDiscordChannel(channels: Channel[]): Channel | undefined {
return channels.find(
(channel) => channel.name.startsWith('discord') && channel.isConnected(),
@@ -212,6 +251,7 @@ function buildStatusContent(): string {
if (!STATUS_SHOW_ROOMS) return '';
const snapshots = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS);
const watcherSummary = summarizeWatcherTasks(getAllTasks());
const chatNameByJid = new Map(
getAllChats().map((chat) => [chat.jid, chat.name]),
);
@@ -313,7 +353,11 @@ function buildStatusContent(): string {
}
}
const header = `**📊 에이전트 상태** — 활성 ${totalActive} / ${totalRooms}`;
const header = formatStatusHeader({
totalActive,
totalRooms,
watchers: watcherSummary,
});
const sections = renderCategorizedRoomSections({
lines: roomLines,
showCategoryHeaders: channelMetaCache.size > 0,