From 127eacb56447d2b8969989f393b9b6dd7403fe78 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 23 Mar 2026 05:22:52 +0900 Subject: [PATCH] fix: restore watcher elapsed status updates --- src/task-scheduler.test.ts | 30 ++++++++++++++++++++++++++++++ src/task-scheduler.ts | 24 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/task-scheduler.test.ts b/src/task-scheduler.test.ts index 94e9964..56b7cf4 100644 --- a/src/task-scheduler.test.ts +++ b/src/task-scheduler.test.ts @@ -134,18 +134,48 @@ Check the run. } as any, phase: 'waiting', checkedAt: '2026-03-19T07:02:10.000Z', + statusStartedAt: '2026-03-19T07:00:00.000Z', nextRun: '2026-03-19T07:04:10.000Z', }); expect(rendered).toContain('CI 감시 중: GitHub Actions run 123456'); expect(rendered).toContain('- 상태: 대기 중'); expect(rendered).toContain('- 마지막 확인: 16시 02분 10초'); + expect(rendered).toContain('- 경과 시간: 2분 10초'); expect(rendered).toContain('- 확인 간격: 1분'); expect(rendered).toContain('- 다음 확인: 16시 04분 10초'); expect(rendered).not.toContain('16:02:10'); expect(rendered).not.toContain('16:04:10'); }); + it('omits watcher elapsed time when tracking has not started yet', () => { + const prompt = ` +[BACKGROUND CI WATCH] + +Watch target: +GitHub Actions run 123456 + +Task ID: +task-123 + +Check instructions: +Check the run. +`.trim(); + + const rendered = renderWatchCiStatusMessage({ + task: { + prompt, + schedule_type: 'interval', + schedule_value: '60000', + } as any, + phase: 'checking', + checkedAt: '2026-03-19T07:02:10.000Z', + statusStartedAt: null, + }); + + expect(rendered).not.toContain('- 경과 시간:'); + }); + it('computeNextRun anchors interval tasks to scheduled time to prevent drift', () => { const scheduledTime = new Date(Date.now() - 2000).toISOString(); // 2s ago const task = { diff --git a/src/task-scheduler.ts b/src/task-scheduler.ts index c98372b..e29a929 100644 --- a/src/task-scheduler.ts +++ b/src/task-scheduler.ts @@ -140,10 +140,28 @@ function formatWatchIntervalLabel( 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; phase: WatcherStatusPhase; checkedAt: string; + statusStartedAt?: string | null; nextRun?: string | null; }): string { const target = extractWatchCiTarget(args.task.prompt) || 'CI watcher'; @@ -165,6 +183,11 @@ export function renderWatchCiStatusMessage(args: { `- 상태: ${statusLabel}`, `- 마지막 확인: ${formatTimeLabel(args.checkedAt)}`, ]; + if (args.statusStartedAt) { + lines.push( + `- 경과 시간: ${formatElapsedLabel(args.statusStartedAt, args.checkedAt)}`, + ); + } const intervalLabel = formatWatchIntervalLabel(args.task); if (intervalLabel) { lines.push(`- 확인 간격: ${intervalLabel}`); @@ -290,6 +313,7 @@ async function runTask( task, phase, checkedAt, + statusStartedAt, nextRun, }); const payload = `${TASK_STATUS_MESSAGE_PREFIX}${text}`;