import { agentApiKeys, agentConfigRevisions, agentRuntimeState, agents, agentTaskSessions, agentWakeupRequests, costEvents, heartbeatRunEvents, heartbeatRuns, } from "@rudderhq/db";
import { AGENT_DICEBEAR_NOTIONISTS_ICON_PREFIX, isUuidLike, normalizeAgentUrlKey, parseShortRef, shortRefFor, } from "@rudderhq/shared";
import { and, desc, eq, gte, inArray, isNull, lt, ne, sql } from "drizzle-orm";
import { createHash, randomBytes, randomUUID } from "node:crypto";
import path from "node:path";
import { deriveUniqueAgentWorkspaceKey } from "../agent-workspace-key.js";
import { conflict, notFound, unprocessable } from "../errors.js";
import { resolveHomeAwarePath, resolveOrganizationAgentsDir } from "../home-paths.js";
import { REDACTED_EVENT_VALUE, sanitizeRecord } from "../redaction.js";
import { pickUniqueAgentName } from "./agent-name-pool.js";
import { normalizeAgentPermissions } from "./agent-permissions.js";
function hashToken(token) {
    return createHash("sha256").update(token).digest("hex");
}
function createToken() {
    return `pcp_${randomBytes(24).toString("hex")}`;
}
const CONFIG_REVISION_FIELDS = [
    "name",
    "role",
    "title",
    "reportsTo",
    "capabilities",
    "agentRuntimeType",
    "agentRuntimeConfig",
    "runtimeConfig",
    "budgetMonthlyCents",
    "metadata",
];
function isPlainRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isHiddenSystemAgentMetadata(metadata) {
    if (!isPlainRecord(metadata))
        return false;
    return metadata.hidden === true || metadata.systemManaged === "rudder_copilot";
}
function jsonEqual(left, right) {
    return JSON.stringify(left) === JSON.stringify(right);
}
function buildConfigSnapshot(row) {
    const agentRuntimeConfig = typeof row.agentRuntimeConfig === "object" && row.agentRuntimeConfig !== null && !Array.isArray(row.agentRuntimeConfig)
        ? sanitizeRecord(row.agentRuntimeConfig)
        : {};
    const runtimeConfig = typeof row.runtimeConfig === "object" && row.runtimeConfig !== null && !Array.isArray(row.runtimeConfig)
        ? sanitizeRecord(row.runtimeConfig)
        : {};
    const metadata = typeof row.metadata === "object" && row.metadata !== null && !Array.isArray(row.metadata)
        ? sanitizeRecord(row.metadata)
        : row.metadata ?? null;
    return {
        name: row.name,
        role: row.role,
        title: row.title,
        reportsTo: row.reportsTo,
        capabilities: row.capabilities,
        agentRuntimeType: row.agentRuntimeType,
        agentRuntimeConfig,
        runtimeConfig,
        budgetMonthlyCents: row.budgetMonthlyCents,
        metadata,
    };
}
function containsRedactedMarker(value) {
    if (value === REDACTED_EVENT_VALUE)
        return true;
    if (Array.isArray(value))
        return value.some((item) => containsRedactedMarker(item));
    if (typeof value !== "object" || value === null)
        return false;
    return Object.values(value).some((entry) => containsRedactedMarker(entry));
}
function hasConfigPatchFields(data) {
    return CONFIG_REVISION_FIELDS.some((field) => Object.prototype.hasOwnProperty.call(data, field));
}
function readNonEmptyString(value) {
    return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
export function createDefaultAgentAvatarIcon() {
    return `${AGENT_DICEBEAR_NOTIONISTS_ICON_PREFIX}${randomUUID()}`;
}
export function normalizeCreatedAgentAvatarIcon(icon) {
    if (typeof icon !== "string")
        return createDefaultAgentAvatarIcon();
    const trimmed = icon.trim();
    if (trimmed.startsWith(AGENT_DICEBEAR_NOTIONISTS_ICON_PREFIX)
        || trimmed.startsWith("asset:")) {
        return trimmed;
    }
    return createDefaultAgentAvatarIcon();
}
function extractWorkspaceKeyFromManagedInstructionsConfig(orgId, agentRuntimeConfig) {
    if (!isPlainRecord(agentRuntimeConfig))
        return null;
    const managedAgentsRoot = path.resolve(resolveOrganizationAgentsDir(orgId));
    const candidatePaths = [
        readNonEmptyString(agentRuntimeConfig.instructionsRootPath),
        readNonEmptyString(agentRuntimeConfig.instructionsFilePath),
        readNonEmptyString(agentRuntimeConfig.agentsMdPath),
    ]
        .filter((value) => Boolean(value))
        .map((value) => resolveHomeAwarePath(value));
    for (const candidatePath of candidatePaths) {
        const relativePath = path.relative(managedAgentsRoot, candidatePath);
        if (relativePath.length === 0
            || relativePath === ".."
            || relativePath.startsWith(`..${path.sep}`)) {
            continue;
        }
        const segments = relativePath.split(path.sep).filter((segment) => segment.length > 0);
        const instructionsIndex = segments.indexOf("instructions");
        if (instructionsIndex <= 0)
            continue;
        const workspaceKey = segments[instructionsIndex - 1]?.trim();
        if (workspaceKey)
            return workspaceKey;
    }
    return null;
}
function diffConfigSnapshot(before, after) {
    return CONFIG_REVISION_FIELDS.filter((field) => !jsonEqual(before[field], after[field]));
}
function configPatchFromSnapshot(snapshot) {
    if (!isPlainRecord(snapshot))
        throw unprocessable("Invalid revision snapshot");
    if (typeof snapshot.name !== "string" || snapshot.name.length === 0) {
        throw unprocessable("Invalid revision snapshot: name");
    }
    if (typeof snapshot.role !== "string" || snapshot.role.length === 0) {
        throw unprocessable("Invalid revision snapshot: role");
    }
    if (typeof snapshot.agentRuntimeType !== "string" || snapshot.agentRuntimeType.length === 0) {
        throw unprocessable("Invalid revision snapshot: agentRuntimeType");
    }
    if (typeof snapshot.budgetMonthlyCents !== "number" || !Number.isFinite(snapshot.budgetMonthlyCents)) {
        throw unprocessable("Invalid revision snapshot: budgetMonthlyCents");
    }
    return {
        name: snapshot.name,
        role: snapshot.role,
        title: typeof snapshot.title === "string" || snapshot.title === null ? snapshot.title : null,
        reportsTo: typeof snapshot.reportsTo === "string" || snapshot.reportsTo === null ? snapshot.reportsTo : null,
        capabilities: typeof snapshot.capabilities === "string" || snapshot.capabilities === null
            ? snapshot.capabilities
            : null,
        agentRuntimeType: snapshot.agentRuntimeType,
        agentRuntimeConfig: isPlainRecord(snapshot.agentRuntimeConfig) ? snapshot.agentRuntimeConfig : {},
        runtimeConfig: isPlainRecord(snapshot.runtimeConfig) ? snapshot.runtimeConfig : {},
        budgetMonthlyCents: Math.max(0, Math.floor(snapshot.budgetMonthlyCents)),
        metadata: isPlainRecord(snapshot.metadata) || snapshot.metadata === null ? snapshot.metadata : null,
    };
}
export function hasAgentShortnameCollision(candidateName, existingAgents, options) {
    const candidateShortname = normalizeAgentUrlKey(candidateName);
    if (!candidateShortname)
        return false;
    return existingAgents.some((agent) => {
        if (agent.status === "terminated")
            return false;
        if (options?.excludeAgentId && agent.id === options.excludeAgentId)
            return false;
        return normalizeAgentUrlKey(agent.name) === candidateShortname;
    });
}
export function deduplicateAgentName(candidateName, existingAgents) {
    if (!hasAgentShortnameCollision(candidateName, existingAgents)) {
        return candidateName;
    }
    for (let i = 2; i <= 100; i++) {
        const suffixed = `${candidateName} ${i}`;
        if (!hasAgentShortnameCollision(suffixed, existingAgents)) {
            return suffixed;
        }
    }
    return `${candidateName} ${Date.now()}`;
}
export function agentService(db) {
    function currentUtcMonthWindow(now = new Date()) {
        const year = now.getUTCFullYear();
        const month = now.getUTCMonth();
        return {
            start: new Date(Date.UTC(year, month, 1, 0, 0, 0, 0)),
            end: new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0)),
        };
    }
    function withUrlKey(row) {
        return {
            ...row,
            shortRef: shortRefFor("agent", row.id),
            urlKey: normalizeAgentUrlKey(row.name) ?? row.id,
        };
    }
    function normalizeAgentInternalRow(row) {
        return withUrlKey({
            ...row,
            permissions: normalizeAgentPermissions(row.permissions, row.role),
        });
    }
    function stripWorkspaceKey(row) {
        const { workspaceKey: _workspaceKey, ...rest } = row;
        return rest;
    }
    function normalizeAgentRow(row) {
        return stripWorkspaceKey(normalizeAgentInternalRow(row));
    }
    function isVisibleAgentRow(row) {
        return row.status !== "terminated" && !isHiddenSystemAgentMetadata(row.metadata);
    }
    async function getMonthlySpendByAgentIds(orgId, agentIds) {
        if (agentIds.length === 0)
            return new Map();
        const { start, end } = currentUtcMonthWindow();
        const rows = await db
            .select({
            agentId: costEvents.agentId,
            spentMonthlyCents: sql `coalesce(sum(${costEvents.costCents}), 0)::int`,
        })
            .from(costEvents)
            .where(and(eq(costEvents.orgId, orgId), inArray(costEvents.agentId, agentIds), gte(costEvents.occurredAt, start), lt(costEvents.occurredAt, end)))
            .groupBy(costEvents.agentId);
        return new Map(rows.map((row) => [row.agentId, Number(row.spentMonthlyCents ?? 0)]));
    }
    async function hydrateAgentSpend(rows) {
        const agentIds = rows.map((row) => row.id);
        const orgId = rows[0]?.orgId;
        if (!orgId || agentIds.length === 0)
            return rows;
        const spendByAgentId = await getMonthlySpendByAgentIds(orgId, agentIds);
        return rows.map((row) => ({
            ...row,
            spentMonthlyCents: spendByAgentId.get(row.id) ?? 0,
        }));
    }
    async function ensureStoredWorkspaceKey(row) {
        const storedWorkspaceKey = row.workspaceKey?.trim();
        if (storedWorkspaceKey)
            return row;
        const existingRows = await db
            .select({ workspaceKey: agents.workspaceKey })
            .from(agents)
            .where(and(eq(agents.orgId, row.orgId), ne(agents.id, row.id)));
        const existingKeys = new Set(existingRows
            .map((entry) => entry.workspaceKey?.trim() ?? "")
            .filter((value) => value.length > 0));
        const preferredWorkspaceKey = extractWorkspaceKeyFromManagedInstructionsConfig(row.orgId, row.agentRuntimeConfig);
        const workspaceKey = preferredWorkspaceKey && !existingKeys.has(preferredWorkspaceKey)
            ? preferredWorkspaceKey
            : deriveUniqueAgentWorkspaceKey({
                agentId: row.id,
                name: row.name,
                existingKeys,
            });
        if (typeof db.update !== "function") {
            return { ...row, workspaceKey };
        }
        const updated = await db
            .update(agents)
            .set({
            workspaceKey,
            updatedAt: new Date(),
        })
            .where(and(eq(agents.id, row.id), isNull(agents.workspaceKey)))
            .returning()
            .then((rows) => rows[0] ?? null);
        if (updated)
            return updated;
        return db
            .select()
            .from(agents)
            .where(eq(agents.id, row.id))
            .then((rows) => rows[0] ?? { ...row, workspaceKey });
    }
    async function getStoredById(id) {
        const row = await db
            .select()
            .from(agents)
            .where(eq(agents.id, id))
            .then((rows) => rows[0] ?? null);
        if (!row)
            return null;
        return ensureStoredWorkspaceKey(row);
    }
    async function getInternalById(id) {
        const row = await getStoredById(id);
        if (!row)
            return null;
        const [hydrated] = await hydrateAgentSpend([row]);
        return normalizeAgentInternalRow(hydrated);
    }
    async function getById(id) {
        const row = await getInternalById(id);
        if (!row)
            return null;
        return stripWorkspaceKey(row);
    }
    async function ensureManager(orgId, managerId) {
        const manager = await getById(managerId);
        if (!manager)
            throw notFound("Manager not found");
        if (manager.orgId !== orgId) {
            throw unprocessable("Manager must belong to same organization");
        }
        return manager;
    }
    async function assertNoCycle(agentId, reportsTo) {
        if (!reportsTo)
            return;
        if (reportsTo === agentId)
            throw unprocessable("Agent cannot report to itself");
        let cursor = reportsTo;
        while (cursor) {
            if (cursor === agentId)
                throw unprocessable("Reporting relationship would create cycle");
            const next = await getById(cursor);
            cursor = next?.reportsTo ?? null;
        }
    }
    async function assertCompanyShortnameAvailable(orgId, candidateName, options) {
        const candidateShortname = normalizeAgentUrlKey(candidateName);
        if (!candidateShortname)
            return;
        const existingAgents = await db
            .select({
            id: agents.id,
            name: agents.name,
            status: agents.status,
        })
            .from(agents)
            .where(eq(agents.orgId, orgId));
        const hasCollision = hasAgentShortnameCollision(candidateName, existingAgents, options);
        if (hasCollision) {
            throw conflict(`Agent shortname '${candidateShortname}' is already in use in this organization`);
        }
    }
    async function updateAgent(id, data, options) {
        const existing = await getStoredById(id);
        if (!existing)
            return null;
        if (existing.status === "terminated" && data.status && data.status !== "terminated") {
            throw conflict("Terminated agents cannot be resumed");
        }
        if (existing.status === "pending_approval" &&
            data.status &&
            data.status !== "pending_approval" &&
            data.status !== "terminated") {
            throw conflict("Pending approval agents cannot be activated directly");
        }
        if (data.workspaceKey !== undefined && data.workspaceKey !== existing.workspaceKey) {
            throw conflict("Agent workspace key is immutable");
        }
        if (data.reportsTo !== undefined) {
            if (data.reportsTo) {
                await ensureManager(existing.orgId, data.reportsTo);
            }
            await assertNoCycle(id, data.reportsTo);
        }
        if (data.name !== undefined) {
            const previousShortname = normalizeAgentUrlKey(existing.name);
            const nextShortname = normalizeAgentUrlKey(data.name);
            if (previousShortname !== nextShortname) {
                await assertCompanyShortnameAvailable(existing.orgId, data.name, { excludeAgentId: id });
            }
        }
        const normalizedPatch = { ...data };
        if (data.permissions !== undefined) {
            const role = (data.role ?? existing.role);
            normalizedPatch.permissions = normalizeAgentPermissions(data.permissions, role);
        }
        delete normalizedPatch.workspaceKey;
        const shouldRecordRevision = Boolean(options?.recordRevision) && hasConfigPatchFields(normalizedPatch);
        const beforeConfig = shouldRecordRevision ? buildConfigSnapshot(existing) : null;
        const updated = await db
            .update(agents)
            .set({ ...normalizedPatch, updatedAt: new Date() })
            .where(eq(agents.id, id))
            .returning()
            .then((rows) => rows[0] ?? null);
        const normalizedUpdated = updated ? normalizeAgentRow(updated) : null;
        if (normalizedUpdated && shouldRecordRevision && beforeConfig) {
            const afterConfig = buildConfigSnapshot(normalizedUpdated);
            const changedKeys = diffConfigSnapshot(beforeConfig, afterConfig);
            if (changedKeys.length > 0) {
                await db.insert(agentConfigRevisions).values({
                    orgId: normalizedUpdated.orgId,
                    agentId: normalizedUpdated.id,
                    createdByAgentId: options?.recordRevision?.createdByAgentId ?? null,
                    createdByUserId: options?.recordRevision?.createdByUserId ?? null,
                    source: options?.recordRevision?.source ?? "patch",
                    rolledBackFromRevisionId: options?.recordRevision?.rolledBackFromRevisionId ?? null,
                    changedKeys,
                    beforeConfig: beforeConfig,
                    afterConfig: afterConfig,
                });
            }
        }
        return normalizedUpdated;
    }
    return {
        list: async (orgId, options) => {
            const conditions = [eq(agents.orgId, orgId)];
            if (!options?.includeTerminated) {
                conditions.push(ne(agents.status, "terminated"));
            }
            const rows = await db.select().from(agents).where(and(...conditions));
            const visibleRows = options?.includeHidden ? rows : rows.filter((row) => !isHiddenSystemAgentMetadata(row.metadata));
            const hydrated = await hydrateAgentSpend(visibleRows);
            return hydrated.map(normalizeAgentRow);
        },
        getById,
        getInternalById,
        suggestName: async (orgId) => {
            const existingAgents = await db
                .select({
                id: agents.id,
                name: agents.name,
                status: agents.status,
            })
                .from(agents)
                .where(eq(agents.orgId, orgId));
            const baseName = pickUniqueAgentName(existingAgents);
            return deduplicateAgentName(baseName, existingAgents);
        },
        create: async (orgId, data) => {
            if (data.reportsTo) {
                await ensureManager(orgId, data.reportsTo);
            }
            const existingAgents = await db
                .select({
                id: agents.id,
                name: agents.name,
                status: agents.status,
                metadata: agents.metadata,
                workspaceKey: agents.workspaceKey,
            })
                .from(agents)
                .where(eq(agents.orgId, orgId));
            const requestedName = typeof data.name === "string" ? data.name.trim() : "";
            const baseName = requestedName.length > 0 ? requestedName : pickUniqueAgentName(existingAgents);
            const uniqueName = deduplicateAgentName(baseName, existingAgents);
            const agentId = data.id ?? randomUUID();
            const workspaceKey = deriveUniqueAgentWorkspaceKey({
                agentId,
                name: uniqueName,
                existingKeys: existingAgents
                    .map((agent) => agent.workspaceKey?.trim() ?? "")
                    .filter((value) => value.length > 0),
            });
            const role = data.role ?? "general";
            const normalizedPermissions = normalizeAgentPermissions(data.permissions, role);
            const icon = normalizeCreatedAgentAvatarIcon(data.icon);
            const created = await db
                .insert(agents)
                .values({
                ...data,
                id: agentId,
                name: uniqueName,
                orgId,
                role,
                icon,
                permissions: normalizedPermissions,
                workspaceKey,
            })
                .returning()
                .then((rows) => rows[0]);
            return normalizeAgentRow(created);
        },
        update: updateAgent,
        pause: async (id, reason = "manual") => {
            const existing = await getById(id);
            if (!existing)
                return null;
            if (existing.status === "terminated")
                throw conflict("Cannot pause terminated agent");
            const updated = await db
                .update(agents)
                .set({
                status: "paused",
                pauseReason: reason,
                pausedAt: new Date(),
                updatedAt: new Date(),
            })
                .where(eq(agents.id, id))
                .returning()
                .then((rows) => rows[0] ?? null);
            return updated ? normalizeAgentRow(updated) : null;
        },
        resume: async (id) => {
            const existing = await getById(id);
            if (!existing)
                return null;
            if (existing.status === "terminated")
                throw conflict("Cannot resume terminated agent");
            if (existing.status === "pending_approval") {
                throw conflict("Pending approval agents cannot be resumed");
            }
            const updated = await db
                .update(agents)
                .set({
                status: "idle",
                pauseReason: null,
                pausedAt: null,
                updatedAt: new Date(),
            })
                .where(eq(agents.id, id))
                .returning()
                .then((rows) => rows[0] ?? null);
            return updated ? normalizeAgentRow(updated) : null;
        },
        terminate: async (id) => {
            const existing = await getById(id);
            if (!existing)
                return null;
            await db
                .update(agents)
                .set({
                status: "terminated",
                pauseReason: null,
                pausedAt: null,
                updatedAt: new Date(),
            })
                .where(eq(agents.id, id));
            await db
                .update(agentApiKeys)
                .set({ revokedAt: new Date() })
                .where(eq(agentApiKeys.agentId, id));
            return getById(id);
        },
        remove: async (id) => {
            const existing = await getById(id);
            if (!existing)
                return null;
            return db.transaction(async (tx) => {
                await tx.update(agents).set({ reportsTo: null }).where(eq(agents.reportsTo, id));
                await tx.delete(heartbeatRunEvents).where(eq(heartbeatRunEvents.agentId, id));
                await tx.delete(agentTaskSessions).where(eq(agentTaskSessions.agentId, id));
                await tx.delete(heartbeatRuns).where(eq(heartbeatRuns.agentId, id));
                await tx.delete(agentWakeupRequests).where(eq(agentWakeupRequests.agentId, id));
                await tx.delete(agentApiKeys).where(eq(agentApiKeys.agentId, id));
                await tx.delete(agentRuntimeState).where(eq(agentRuntimeState.agentId, id));
                const deleted = await tx
                    .delete(agents)
                    .where(eq(agents.id, id))
                    .returning()
                    .then((rows) => rows[0] ?? null);
                return deleted ? normalizeAgentRow(deleted) : null;
            });
        },
        activatePendingApproval: async (id) => {
            const existing = await getById(id);
            if (!existing)
                return null;
            if (existing.status !== "pending_approval")
                return existing;
            const updated = await db
                .update(agents)
                .set({ status: "idle", updatedAt: new Date() })
                .where(eq(agents.id, id))
                .returning()
                .then((rows) => rows[0] ?? null);
            return updated ? normalizeAgentRow(updated) : null;
        },
        updatePermissions: async (id, permissions) => {
            const existing = await getById(id);
            if (!existing)
                return null;
            const nextPermissions = {
                ...existing.permissions,
                ...permissions,
            };
            const updated = await db
                .update(agents)
                .set({
                permissions: normalizeAgentPermissions(nextPermissions, existing.role),
                updatedAt: new Date(),
            })
                .where(eq(agents.id, id))
                .returning()
                .then((rows) => rows[0] ?? null);
            return updated ? normalizeAgentRow(updated) : null;
        },
        listConfigRevisions: async (id) => db
            .select()
            .from(agentConfigRevisions)
            .where(eq(agentConfigRevisions.agentId, id))
            .orderBy(desc(agentConfigRevisions.createdAt)),
        getConfigRevision: async (id, revisionId) => db
            .select()
            .from(agentConfigRevisions)
            .where(and(eq(agentConfigRevisions.agentId, id), eq(agentConfigRevisions.id, revisionId)))
            .then((rows) => rows[0] ?? null),
        rollbackConfigRevision: async (id, revisionId, actor) => {
            const revision = await db
                .select()
                .from(agentConfigRevisions)
                .where(and(eq(agentConfigRevisions.agentId, id), eq(agentConfigRevisions.id, revisionId)))
                .then((rows) => rows[0] ?? null);
            if (!revision)
                return null;
            if (containsRedactedMarker(revision.afterConfig)) {
                throw unprocessable("Cannot roll back a revision that contains redacted secret values");
            }
            const patch = configPatchFromSnapshot(revision.afterConfig);
            return updateAgent(id, patch, {
                recordRevision: {
                    createdByAgentId: actor.agentId ?? null,
                    createdByUserId: actor.userId ?? null,
                    source: "rollback",
                    rolledBackFromRevisionId: revision.id,
                },
            });
        },
        createApiKey: async (id, name) => {
            const existing = await getById(id);
            if (!existing)
                throw notFound("Agent not found");
            if (existing.status === "pending_approval") {
                throw conflict("Cannot create keys for pending approval agents");
            }
            if (existing.status === "terminated") {
                throw conflict("Cannot create keys for terminated agents");
            }
            const token = createToken();
            const keyHash = hashToken(token);
            const created = await db
                .insert(agentApiKeys)
                .values({
                agentId: id,
                orgId: existing.orgId,
                name,
                keyHash,
            })
                .returning()
                .then((rows) => rows[0]);
            return {
                id: created.id,
                name: created.name,
                token,
                createdAt: created.createdAt,
            };
        },
        listKeys: (id) => db
            .select({
            id: agentApiKeys.id,
            name: agentApiKeys.name,
            createdAt: agentApiKeys.createdAt,
            revokedAt: agentApiKeys.revokedAt,
        })
            .from(agentApiKeys)
            .where(eq(agentApiKeys.agentId, id)),
        revokeKey: async (keyId) => {
            const rows = await db
                .update(agentApiKeys)
                .set({ revokedAt: new Date() })
                .where(eq(agentApiKeys.id, keyId))
                .returning();
            return rows[0] ?? null;
        },
        orgForCompany: async (orgId) => {
            const rows = await db
                .select()
                .from(agents)
                .where(and(eq(agents.orgId, orgId), ne(agents.status, "terminated")));
            const normalizedRows = rows.filter((row) => !isHiddenSystemAgentMetadata(row.metadata)).map(normalizeAgentRow);
            const byManager = new Map();
            for (const row of normalizedRows) {
                const key = row.reportsTo ?? null;
                const group = byManager.get(key) ?? [];
                group.push(row);
                byManager.set(key, group);
            }
            const build = (managerId) => {
                const members = byManager.get(managerId) ?? [];
                return members.map((member) => ({
                    ...member,
                    reports: build(member.id),
                }));
            };
            return build(null);
        },
        getChainOfCommand: async (agentId) => {
            const chain = [];
            const visited = new Set([agentId]);
            const start = await getById(agentId);
            let currentId = start?.reportsTo ?? null;
            while (currentId && !visited.has(currentId) && chain.length < 50) {
                visited.add(currentId);
                const mgr = await getById(currentId);
                if (!mgr)
                    break;
                chain.push({ id: mgr.id, name: mgr.name, role: mgr.role, title: mgr.title ?? null });
                currentId = mgr.reportsTo ?? null;
            }
            return chain;
        },
        runningForAgent: (agentId) => db
            .select()
            .from(heartbeatRuns)
            .where(and(eq(heartbeatRuns.agentId, agentId), inArray(heartbeatRuns.status, ["queued", "running"]))),
        resolveByReference: async (orgId, reference) => {
            const raw = reference.trim();
            if (raw.length === 0) {
                return { agent: null, ambiguous: false };
            }
            if (isUuidLike(raw)) {
                const byId = await getById(raw);
                if (!byId || byId.orgId !== orgId) {
                    return { agent: null, ambiguous: false };
                }
                return { agent: byId, ambiguous: false };
            }
            const shortRef = parseShortRef(raw);
            if (shortRef?.kind === "agent") {
                const rows = await db.select().from(agents).where(eq(agents.orgId, orgId));
                const matches = rows
                    .map(normalizeAgentRow)
                    .filter((agent) => isVisibleAgentRow(agent) && agent.id.replace(/-/g, "").toLowerCase().startsWith(shortRef.prefix));
                if (matches.length === 1) {
                    return { agent: matches[0] ?? null, ambiguous: false };
                }
                if (matches.length > 1) {
                    return { agent: null, ambiguous: true };
                }
                return { agent: null, ambiguous: false };
            }
            const urlKey = normalizeAgentUrlKey(raw);
            if (!urlKey) {
                return { agent: null, ambiguous: false };
            }
            const rows = await db.select().from(agents).where(eq(agents.orgId, orgId));
            const matches = rows
                .map(normalizeAgentRow)
                .filter((agent) => isVisibleAgentRow(agent) && agent.urlKey === urlKey);
            if (matches.length === 1) {
                return { agent: matches[0] ?? null, ambiguous: false };
            }
            if (matches.length > 1) {
                return { agent: null, ambiguous: true };
            }
            return { agent: null, ambiguous: false };
        },
    };
}
//# sourceMappingURL=agents.js.map