agent-ecosystem/src/features/member-work-sync/main/adapters/output/TeamTaskStallJournalWorkSyncCooldown.ts
2026-04-30 00:05:58 +03:00

61 lines
1.7 KiB
TypeScript

import { readFile } from 'fs/promises';
import { join } from 'path';
import type { MemberWorkSyncWatchdogCooldownPort } from '../../../core/application';
const DEFAULT_WATCHDOG_COOLDOWN_MS = 10 * 60_000;
interface StallJournalEntry {
taskId: string;
state: string;
alertedAt?: string;
}
function parseTime(value: string | undefined): number | null {
if (!value) {
return null;
}
const time = new Date(value).getTime();
return Number.isFinite(time) ? time : null;
}
export class TeamTaskStallJournalWorkSyncCooldown implements MemberWorkSyncWatchdogCooldownPort {
constructor(
private readonly teamsBasePath: string,
private readonly cooldownMs: number = DEFAULT_WATCHDOG_COOLDOWN_MS
) {}
async hasRecentNudge(input: {
teamName: string;
memberName: string;
taskIds: string[];
nowIso: string;
}): Promise<boolean> {
const taskIds = new Set(input.taskIds);
if (taskIds.size === 0) {
return false;
}
try {
const raw = await readFile(
join(this.teamsBasePath, input.teamName, 'stall-monitor-journal.json'),
'utf8'
);
const parsed = JSON.parse(raw) as unknown;
if (!Array.isArray(parsed)) {
return true;
}
const now = parseTime(input.nowIso) ?? Date.now();
return parsed.some((entry): boolean => {
const row = entry as Partial<StallJournalEntry>;
if (row.state !== 'alerted' || !row.taskId || !taskIds.has(row.taskId)) {
return false;
}
const alertedAt = parseTime(row.alertedAt);
return alertedAt != null && now - alertedAt <= this.cooldownMs;
});
} catch (error) {
return (error as NodeJS.ErrnoException).code !== 'ENOENT';
}
}
}