import { existsSync, readFileSync, unlinkSync } from "node:fs" import { z } from "zod" import { acquireLock, ensureDir, writeJsonAtomic } from "../../features/claude-tasks/storage" import { getTeamInboxDir, getTeamInboxPath } from "./paths" import { validateAgentNameOrLead, validateTeamName } from "./name-validation" import { TeamInboxMessage, TeamInboxMessageSchema } from "./types" const TeamInboxListSchema = z.array(TeamInboxMessageSchema) function nowIso(): string { return new Date().toISOString() } function assertValidTeamName(teamName: string): void { const validationError = validateTeamName(teamName) if (validationError) { throw new Error(validationError) } } function assertValidInboxAgentName(agentName: string): void { const validationError = validateAgentNameOrLead(agentName) if (validationError) { throw new Error(validationError) } } function withInboxLock(teamName: string, operation: () => T): T { assertValidTeamName(teamName) const inboxDir = getTeamInboxDir(teamName) ensureDir(inboxDir) const lock = acquireLock(inboxDir) if (!lock.acquired) { throw new Error("inbox_lock_unavailable") } try { return operation() } finally { lock.release() } } function parseInboxFile(content: string): TeamInboxMessage[] { try { const parsed = JSON.parse(content) const result = TeamInboxListSchema.safeParse(parsed) return result.success ? result.data : [] } catch { return [] } } function readInboxMessages(teamName: string, agentName: string): TeamInboxMessage[] { assertValidTeamName(teamName) assertValidInboxAgentName(agentName) const path = getTeamInboxPath(teamName, agentName) if (!existsSync(path)) { return [] } return parseInboxFile(readFileSync(path, "utf-8")) } function writeInboxMessages(teamName: string, agentName: string, messages: TeamInboxMessage[]): void { assertValidTeamName(teamName) assertValidInboxAgentName(agentName) const path = getTeamInboxPath(teamName, agentName) writeJsonAtomic(path, messages) } export function ensureInbox(teamName: string, agentName: string): void { assertValidTeamName(teamName) assertValidInboxAgentName(agentName) withInboxLock(teamName, () => { const path = getTeamInboxPath(teamName, agentName) if (!existsSync(path)) { writeJsonAtomic(path, []) } }) } export function appendInboxMessage(teamName: string, agentName: string, message: TeamInboxMessage): void { assertValidTeamName(teamName) assertValidInboxAgentName(agentName) withInboxLock(teamName, () => { const path = getTeamInboxPath(teamName, agentName) const messages = existsSync(path) ? parseInboxFile(readFileSync(path, "utf-8")) : [] messages.push(TeamInboxMessageSchema.parse(message)) writeInboxMessages(teamName, agentName, messages) }) } export function clearInbox(teamName: string, agentName: string): void { assertValidTeamName(teamName) assertValidInboxAgentName(agentName) withInboxLock(teamName, () => { const path = getTeamInboxPath(teamName, agentName) if (existsSync(path)) { unlinkSync(path) } }) } export function sendPlainInboxMessage( teamName: string, from: string, to: string, text: string, summary: string, color?: string, ): void { appendInboxMessage(teamName, to, { from, text, timestamp: nowIso(), read: false, summary, ...(color ? { color } : {}), }) } export function sendStructuredInboxMessage( teamName: string, from: string, to: string, payload: Record, summary?: string, ): void { appendInboxMessage(teamName, to, { from, text: JSON.stringify(payload), timestamp: nowIso(), read: false, ...(summary ? { summary } : {}), }) } export function readInbox( teamName: string, agentName: string, unreadOnly = false, markAsRead = true, ): TeamInboxMessage[] { return withInboxLock(teamName, () => { const messages = readInboxMessages(teamName, agentName) const selected = unreadOnly ? messages.filter((message) => !message.read) : [...messages] if (!markAsRead || selected.length === 0) { return selected } const selectedSet = unreadOnly ? new Set(selected) : null let changed = false const updated = messages.map((message) => { if (!unreadOnly) { if (!message.read) { changed = true } return { ...message, read: true } } if (selectedSet?.has(message)) { changed = true return { ...message, read: true } } return message }) if (changed) { writeInboxMessages(teamName, agentName, updated) } return selected }) } export function buildShutdownRequestId(recipient: string): string { return `shutdown-${Date.now()}@${recipient}` }