feat: show watcher counts in status dashboard header
This commit is contained in:
59
src/unified-dashboard.test.ts
Normal file
59
src/unified-dashboard.test.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -25,9 +25,10 @@ import {
|
|||||||
mergeClaudeDashboardAccounts,
|
mergeClaudeDashboardAccounts,
|
||||||
type UsageRow,
|
type UsageRow,
|
||||||
} from './dashboard-usage-rows.js';
|
} 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 type { GroupQueue } from './group-queue.js';
|
||||||
import { logger } from './logger.js';
|
import { logger } from './logger.js';
|
||||||
|
import { isWatchCiTask } from './task-watch-status.js';
|
||||||
import {
|
import {
|
||||||
readStatusSnapshots,
|
readStatusSnapshots,
|
||||||
writeStatusSnapshot,
|
writeStatusSnapshot,
|
||||||
@@ -37,6 +38,7 @@ import type {
|
|||||||
Channel,
|
Channel,
|
||||||
ChannelMeta,
|
ChannelMeta,
|
||||||
RegisteredGroup,
|
RegisteredGroup,
|
||||||
|
ScheduledTask,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
export interface UnifiedDashboardOptions {
|
export interface UnifiedDashboardOptions {
|
||||||
@@ -81,6 +83,43 @@ let cachedCodexUsageRows: UsageRow[] = [];
|
|||||||
/** Codex service only: ISO timestamp of last successful usage fetch. */
|
/** Codex service only: ISO timestamp of last successful usage fetch. */
|
||||||
let codexUsageFetchedAt: string | null = null;
|
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 {
|
function findDiscordChannel(channels: Channel[]): Channel | undefined {
|
||||||
return channels.find(
|
return channels.find(
|
||||||
(channel) => channel.name.startsWith('discord') && channel.isConnected(),
|
(channel) => channel.name.startsWith('discord') && channel.isConnected(),
|
||||||
@@ -212,6 +251,7 @@ function buildStatusContent(): string {
|
|||||||
if (!STATUS_SHOW_ROOMS) return '';
|
if (!STATUS_SHOW_ROOMS) return '';
|
||||||
|
|
||||||
const snapshots = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS);
|
const snapshots = readStatusSnapshots(STATUS_SNAPSHOT_MAX_AGE_MS);
|
||||||
|
const watcherSummary = summarizeWatcherTasks(getAllTasks());
|
||||||
const chatNameByJid = new Map(
|
const chatNameByJid = new Map(
|
||||||
getAllChats().map((chat) => [chat.jid, chat.name]),
|
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({
|
const sections = renderCategorizedRoomSections({
|
||||||
lines: roomLines,
|
lines: roomLines,
|
||||||
showCategoryHeaders: channelMetaCache.size > 0,
|
showCategoryHeaders: channelMetaCache.size > 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user