import type { MemberWorkSyncEventQueue, MemberWorkSyncTriggerReason, } from '../../infrastructure/MemberWorkSyncEventQueue'; import type { TeamChangeEvent, ToolActivityEventPayload } from '@shared/types'; interface MemberTurnSettledEventPayload { memberName?: string; sourceId?: string; provider?: string; } interface MemberWorkSyncRosterSource { loadActiveMemberNames(teamName: string): Promise; } interface MemberWorkSyncMemberStorageMaterializer { materializeMember(teamName: string, memberName: string): Promise; } const TEAM_WIDE_REASONS: Partial> = { config: 'config_changed', task: 'task_changed', 'task-log-change': 'runtime_activity', 'log-source-change': 'runtime_activity', process: 'runtime_activity', 'lead-activity': 'runtime_activity', }; function parseInboxRecipient(detail: string | undefined): string | null { if (!detail) { return null; } const match = /^inboxes\/(.+)\.json$/.exec(detail); return match?.[1]?.trim() || null; } function parseToolActivity(detail: string | undefined): ToolActivityEventPayload | null { if (!detail) { return null; } try { const parsed = JSON.parse(detail) as ToolActivityEventPayload; return parsed && typeof parsed === 'object' ? parsed : null; } catch { return null; } } function parseMemberTurnSettled(detail: string | undefined): MemberTurnSettledEventPayload | null { if (!detail) { return null; } try { const parsed = JSON.parse(detail) as MemberTurnSettledEventPayload; return parsed && typeof parsed === 'object' ? parsed : null; } catch { return null; } } export class MemberWorkSyncTeamChangeRouter { constructor( private readonly rosterSource: MemberWorkSyncRosterSource, private readonly queue: MemberWorkSyncEventQueue, private readonly materializer?: MemberWorkSyncMemberStorageMaterializer ) {} async enqueueStartupScan(teamNames: string[]): Promise { await Promise.allSettled( teamNames.map((teamName) => this.enqueueTeam(teamName, 'startup_scan', 30_000)) ); } noteTeamChange(event: TeamChangeEvent): void { if (event.type === 'lead-activity' && event.detail === 'offline') { this.queue.dropTeam(event.teamName); return; } if (event.type === 'member-spawn') { const memberName = event.detail?.trim(); if (memberName) { this.queue.enqueue({ teamName: event.teamName, memberName, triggerReason: 'member_spawned', runAfterMs: 30_000, }); } else { void this.enqueueTeam(event.teamName, 'member_spawned', 30_000).catch(() => undefined); } return; } if (event.type === 'tool-activity') { const payload = parseToolActivity(event.detail); if (payload?.action === 'finish' && payload.memberName) { this.queue.enqueue({ teamName: event.teamName, memberName: payload.memberName, triggerReason: 'tool_finished', }); } return; } if (event.type === 'member-turn-settled') { const payload = parseMemberTurnSettled(event.detail); const memberName = payload?.memberName?.trim(); if (memberName) { this.queue.enqueue({ teamName: event.teamName, memberName, triggerReason: 'turn_settled', }); } return; } if (event.type === 'inbox' || event.type === 'lead-message') { const recipient = parseInboxRecipient(event.detail); if (recipient) { this.queue.enqueue({ teamName: event.teamName, memberName: recipient, triggerReason: 'inbox_changed', }); } return; } const teamWideReason = TEAM_WIDE_REASONS[event.type]; if (teamWideReason) { void this.enqueueTeam(event.teamName, teamWideReason).catch(() => undefined); } } private async enqueueTeam( teamName: string, triggerReason: MemberWorkSyncTriggerReason, runAfterMs?: number ): Promise { const activeMembers = await this.rosterSource.loadActiveMemberNames(teamName); const materializer = this.materializer; if (materializer) { await Promise.allSettled( activeMembers.map((memberName) => materializer.materializeMember(teamName, memberName)) ); } for (const memberName of activeMembers) { this.queue.enqueue({ teamName, memberName, triggerReason, runAfterMs }); } } }