refactor: consolidate runtime helpers
This commit is contained in:
128
src/task-watch-status.ts
Normal file
128
src/task-watch-status.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { TIMEZONE } from './config.js';
|
||||
import type { ScheduledTask } from './types.js';
|
||||
|
||||
export type WatcherStatusPhase =
|
||||
| 'checking'
|
||||
| 'waiting'
|
||||
| 'retrying'
|
||||
| 'completed';
|
||||
|
||||
export const WATCH_CI_PREFIX = '[BACKGROUND CI WATCH]';
|
||||
export const TASK_STATUS_MESSAGE_PREFIX = '\u2063\u2063\u2063';
|
||||
|
||||
export function isWatchCiTask(task: Pick<ScheduledTask, 'prompt'>): boolean {
|
||||
return task.prompt.startsWith(WATCH_CI_PREFIX);
|
||||
}
|
||||
|
||||
export function isTaskStatusControlMessage(content: string): boolean {
|
||||
return content.startsWith(TASK_STATUS_MESSAGE_PREFIX);
|
||||
}
|
||||
|
||||
export function extractWatchCiTarget(prompt: string): string | null {
|
||||
const match = prompt.match(/Watch target:\n([\s\S]*?)\n\nTask ID:/);
|
||||
return match?.[1]?.trim() || null;
|
||||
}
|
||||
|
||||
function formatTimeLabel(timestampIso: string): string {
|
||||
return new Intl.DateTimeFormat('ko-KR', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: TIMEZONE,
|
||||
})
|
||||
.format(new Date(timestampIso))
|
||||
.replace(/:/g, '시 ')
|
||||
.replace(/시 (\d{2})$/, '분 $1초');
|
||||
}
|
||||
|
||||
function formatWatchIntervalLabel(
|
||||
task: Pick<ScheduledTask, 'schedule_type' | 'schedule_value'>,
|
||||
): string | null {
|
||||
if (task.schedule_type !== 'interval') return null;
|
||||
const ms = parseInt(task.schedule_value, 10);
|
||||
if (!Number.isFinite(ms) || ms <= 0) return null;
|
||||
|
||||
const totalSeconds = Math.floor(ms / 1000);
|
||||
if (totalSeconds < 60) return `${totalSeconds}초`;
|
||||
|
||||
const totalMinutes = Math.floor(totalSeconds / 60);
|
||||
if (totalMinutes < 60) {
|
||||
const seconds = totalSeconds % 60;
|
||||
return seconds > 0 ? `${totalMinutes}분 ${seconds}초` : `${totalMinutes}분`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
return minutes > 0 ? `${hours}시간 ${minutes}분` : `${hours}시간`;
|
||||
}
|
||||
|
||||
function formatElapsedLabel(
|
||||
startedAtIso: string,
|
||||
checkedAtIso: string,
|
||||
): string {
|
||||
const elapsedMs = Math.max(
|
||||
0,
|
||||
new Date(checkedAtIso).getTime() - new Date(startedAtIso).getTime(),
|
||||
);
|
||||
const totalSeconds = Math.floor(elapsedMs / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
const parts: string[] = [];
|
||||
if (hours > 0) parts.push(`${hours}시간`);
|
||||
if (minutes > 0) parts.push(`${minutes}분`);
|
||||
parts.push(`${seconds}초`);
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export function renderWatchCiStatusMessage(args: {
|
||||
task: Pick<ScheduledTask, 'prompt' | 'schedule_type' | 'schedule_value'>;
|
||||
phase: WatcherStatusPhase;
|
||||
checkedAt: string;
|
||||
statusStartedAt?: string | null;
|
||||
nextRun?: string | null;
|
||||
}): string {
|
||||
const target = extractWatchCiTarget(args.task.prompt) || 'CI watcher';
|
||||
const title =
|
||||
args.phase === 'completed'
|
||||
? `CI 감시 종료: ${target}`
|
||||
: `CI 감시 중: ${target}`;
|
||||
const statusLabel =
|
||||
args.phase === 'checking'
|
||||
? '확인 중'
|
||||
: args.phase === 'retrying'
|
||||
? '재시도 대기'
|
||||
: args.phase === 'completed'
|
||||
? '완료'
|
||||
: '대기 중';
|
||||
|
||||
const lines = [
|
||||
title,
|
||||
`- 상태: ${statusLabel}`,
|
||||
`- 마지막 확인: ${formatTimeLabel(args.checkedAt)}`,
|
||||
];
|
||||
if (args.statusStartedAt) {
|
||||
lines.push(
|
||||
`- 경과 시간: ${formatElapsedLabel(args.statusStartedAt, args.checkedAt)}`,
|
||||
);
|
||||
}
|
||||
const intervalLabel = formatWatchIntervalLabel(args.task);
|
||||
if (intervalLabel) {
|
||||
lines.push(`- 확인 간격: ${intervalLabel}`);
|
||||
}
|
||||
|
||||
if (args.nextRun) {
|
||||
lines.push(`- 다음 확인: ${formatTimeLabel(args.nextRun)}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function getTaskQueueJid(
|
||||
task: Pick<ScheduledTask, 'chat_jid' | 'context_mode' | 'id' | 'prompt'>,
|
||||
): string {
|
||||
return task.context_mode === 'isolated' || isWatchCiTask(task)
|
||||
? `${task.chat_jid}::task:${task.id}`
|
||||
: task.chat_jid;
|
||||
}
|
||||
Reference in New Issue
Block a user