import { getTeamsBasePath } from '@main/utils/pathDecoder'; import * as fs from 'fs'; import * as path from 'path'; import { atomicWriteAsync } from './atomicWrite'; import type { TeamMember } from '@shared/types'; interface TeamMembersMetaFile { version: 1; members: TeamMember[]; } const MAX_META_FILE_BYTES = 256 * 1024; function normalizeMember(member: TeamMember): TeamMember | null { const trimmedName = member.name?.trim(); if (!trimmedName) { return null; } return { name: trimmedName, role: typeof member.role === 'string' ? member.role.trim() || undefined : undefined, agentType: typeof member.agentType === 'string' ? member.agentType.trim() || undefined : undefined, color: typeof member.color === 'string' ? member.color.trim() || undefined : undefined, joinedAt: typeof member.joinedAt === 'number' ? member.joinedAt : undefined, agentId: typeof member.agentId === 'string' ? member.agentId : undefined, removedAt: typeof member.removedAt === 'number' ? member.removedAt : undefined, }; } export class TeamMembersMetaStore { private getMetaPath(teamName: string): string { return path.join(getTeamsBasePath(), teamName, 'members.meta.json'); } async getMembers(teamName: string): Promise { const metaPath = this.getMetaPath(teamName); try { const stat = await fs.promises.stat(metaPath); if (stat.isFile() && stat.size > MAX_META_FILE_BYTES) { return []; } } catch { // ignore - readFile below will handle ENOENT and throw on other errors } let raw: string; try { raw = await fs.promises.readFile(metaPath, 'utf8'); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { return []; } throw error; } let parsed: unknown; try { parsed = JSON.parse(raw) as unknown; } catch { return []; } if (!parsed || typeof parsed !== 'object') { return []; } const file = parsed as Partial; if (!Array.isArray(file.members)) { return []; } const deduped = new Map(); for (const item of file.members) { if (!item || typeof item !== 'object') { continue; } const normalized = normalizeMember(item); if (!normalized) { continue; } deduped.set(normalized.name, normalized); } return Array.from(deduped.values()).sort((a, b) => a.name.localeCompare(b.name)); } async writeMembers(teamName: string, members: TeamMember[]): Promise { const deduped = new Map(); for (const member of members) { const normalized = normalizeMember(member); if (!normalized) { continue; } deduped.set(normalized.name, normalized); } const payload: TeamMembersMetaFile = { version: 1, members: Array.from(deduped.values()).sort((a, b) => a.name.localeCompare(b.name)), }; await atomicWriteAsync(this.getMetaPath(teamName), JSON.stringify(payload, null, 2)); } }