import { Router } from "express";
import { generateKeyPairSync, randomUUID } from "node:crypto";
import path from "node:path";
import { agents as agentsTable, companies, heartbeatRuns, issues as issuesTable } from "@zmeel/db";
import { and, desc, eq, inArray, not, sql } from "drizzle-orm";
import { agentSkillSyncSchema, agentMineInboxQuerySchema, createAgentKeySchema, createAgentHireSchema, createAgentSchema, deriveAgentUrlKey, isUuidLike, resetAgentSessionSchema, testAdapterEnvironmentSchema, upsertAgentInstructionsFileSchema, updateAgentInstructionsBundleSchema, updateAgentPermissionsSchema, updateAgentInstructionsPathSchema, wakeAgentSchema, updateAgentSchema, } from "@zmeel/shared";
import { readzmeelSkillSyncPreference, writezmeelSkillSyncPreference, } from "@zmeel/adapter-utils/server-utils";
import { validate } from "../middleware/validate.js";
import { agentService, agentInstructionsService, accessService, approvalService, companySkillService, budgetService, heartbeatService, issueApprovalService, issueService, logActivity, secretService, syncInstructionsBundleConfigFromFilePath, workspaceOperationService, } from "../services/index.js";
import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
import { assertBoard, assertCompanyAccess, assertInstanceAdmin, getActorInfo } from "./authz.js";
import { findServerAdapter, listAdapterModels, detectAdapterModel } from "../adapters/index.js";
import { redactEventPayload } from "../redaction.js";
import { redactCurrentUserValue } from "../log-redaction.js";
import { renderOrgChartSvg, renderOrgChartPng, ORG_CHART_STYLES } from "./org-chart-svg.js";
import { instanceSettingsService } from "../services/instance-settings.js";
import { runClaudeLogin } from "@zmeel/adapter-claude-local/server";
import { DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX, DEFAULT_CODEX_LOCAL_MODEL, } from "@zmeel/adapter-codex-local";
import { DEFAULT_CURSOR_LOCAL_MODEL } from "@zmeel/adapter-cursor-local";
import { DEFAULT_GEMINI_LOCAL_MODEL } from "@zmeel/adapter-gemini-local";
import { ensureOpenCodeModelConfiguredAndAvailable } from "@zmeel/adapter-opencode-local/server";
import { loadDefaultAgentInstructionsBundle, resolveDefaultAgentInstructionsBundleRole, } from "../services/default-agent-instructions.js";
export function agentRoutes(db) {
    const DEFAULT_INSTRUCTIONS_PATH_KEYS = {
        claude_local: "instructionsFilePath",
        codex_local: "instructionsFilePath",
        gemini_local: "instructionsFilePath",
        opencode_local: "instructionsFilePath",
        cursor: "instructionsFilePath",
        pi_local: "instructionsFilePath",
    };
    const DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES = new Set(Object.keys(DEFAULT_INSTRUCTIONS_PATH_KEYS));
    const KNOWN_INSTRUCTIONS_PATH_KEYS = new Set(["instructionsFilePath", "agentsMdPath"]);
    const KNOWN_INSTRUCTIONS_BUNDLE_KEYS = [
        "instructionsBundleMode",
        "instructionsRootPath",
        "instructionsEntryFile",
        "instructionsFilePath",
        "agentsMdPath",
    ];
    const router = Router();
    const svc = agentService(db);
    const access = accessService(db);
    const approvalsSvc = approvalService(db);
    const budgets = budgetService(db);
    const heartbeat = heartbeatService(db);
    const issueApprovalsSvc = issueApprovalService(db);
    const secretsSvc = secretService(db);
    const instructions = agentInstructionsService();
    const companySkills = companySkillService(db);
    const workspaceOperations = workspaceOperationService(db);
    const instanceSettings = instanceSettingsService(db);
    const strictSecretsMode = process.env.ZMEEL_SECRETS_STRICT_MODE === "true";
    async function getCurrentUserRedactionOptions() {
        return {
            enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
        };
    }
    function canCreateAgents(agent) {
        if (!agent.permissions || typeof agent.permissions !== "object")
            return false;
        return Boolean(agent.permissions.canCreateAgents);
    }
    async function buildAgentAccessState(agent) {
        const membership = await access.getMembership(agent.companyId, "agent", agent.id);
        const grants = membership
            ? await access.listPrincipalGrants(agent.companyId, "agent", agent.id)
            : [];
        const hasExplicitTaskAssignGrant = grants.some((grant) => grant.permissionKey === "tasks:assign");
        if (agent.role === "ceo") {
            return {
                canAssignTasks: true,
                taskAssignSource: "ceo_role",
                membership,
                grants,
            };
        }
        if (canCreateAgents(agent)) {
            return {
                canAssignTasks: true,
                taskAssignSource: "agent_creator",
                membership,
                grants,
            };
        }
        if (hasExplicitTaskAssignGrant) {
            return {
                canAssignTasks: true,
                taskAssignSource: "explicit_grant",
                membership,
                grants,
            };
        }
        return {
            canAssignTasks: false,
            taskAssignSource: "none",
            membership,
            grants,
        };
    }
    async function buildAgentDetail(agent, options) {
        const [chainOfCommand, accessState] = await Promise.all([
            svc.getChainOfCommand(agent.id),
            buildAgentAccessState(agent),
        ]);
        return {
            ...(options?.restricted ? redactForRestrictedAgentView(agent) : agent),
            chainOfCommand,
            access: accessState,
        };
    }
    async function applyDefaultAgentTaskAssignGrant(companyId, agentId, grantedByUserId) {
        await access.ensureMembership(companyId, "agent", agentId, "member", "active");
        await access.setPrincipalPermission(companyId, "agent", agentId, "tasks:assign", true, grantedByUserId);
    }
    async function assertCanCreateAgentsForCompany(req, companyId) {
        assertCompanyAccess(req, companyId);
        if (req.actor.type === "board") {
            if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
                return null;
            const allowed = await access.canUser(companyId, req.actor.userId, "agents:create");
            if (!allowed) {
                throw forbidden("Missing permission: agents:create");
            }
            return null;
        }
        if (!req.actor.agentId)
            throw forbidden("Agent authentication required");
        const actorAgent = await svc.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== companyId) {
            throw forbidden("Agent key cannot access another company");
        }
        const allowedByGrant = await access.hasPermission(companyId, "agent", actorAgent.id, "agents:create");
        if (!allowedByGrant && !canCreateAgents(actorAgent)) {
            throw forbidden("Missing permission: can create agents");
        }
        return actorAgent;
    }
    async function assertCanReadConfigurations(req, companyId) {
        return assertCanCreateAgentsForCompany(req, companyId);
    }
    async function actorCanReadConfigurationsForCompany(req, companyId) {
        assertCompanyAccess(req, companyId);
        if (req.actor.type === "board") {
            if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin)
                return true;
            return access.canUser(companyId, req.actor.userId, "agents:create");
        }
        if (!req.actor.agentId)
            return false;
        const actorAgent = await svc.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== companyId)
            return false;
        const allowedByGrant = await access.hasPermission(companyId, "agent", actorAgent.id, "agents:create");
        return allowedByGrant || canCreateAgents(actorAgent);
    }
    async function buildSkippedWakeupResponse(agent, payload) {
        const issueId = typeof payload?.issueId === "string" && payload.issueId.trim() ? payload.issueId : null;
        if (!issueId) {
            return {
                status: "skipped",
                reason: "wakeup_skipped",
                message: "Wakeup was skipped.",
                issueId: null,
                executionRunId: null,
                executionAgentId: null,
                executionAgentName: null,
            };
        }
        const issue = await db
            .select({
            id: issuesTable.id,
            executionRunId: issuesTable.executionRunId,
        })
            .from(issuesTable)
            .where(and(eq(issuesTable.id, issueId), eq(issuesTable.companyId, agent.companyId)))
            .then((rows) => rows[0] ?? null);
        if (!issue?.executionRunId) {
            return {
                status: "skipped",
                reason: "wakeup_skipped",
                message: "Wakeup was skipped.",
                issueId,
                executionRunId: null,
                executionAgentId: null,
                executionAgentName: null,
            };
        }
        const executionRun = await heartbeat.getRun(issue.executionRunId);
        if (!executionRun || (executionRun.status !== "queued" && executionRun.status !== "running")) {
            return {
                status: "skipped",
                reason: "wakeup_skipped",
                message: "Wakeup was skipped.",
                issueId,
                executionRunId: issue.executionRunId,
                executionAgentId: null,
                executionAgentName: null,
            };
        }
        const executionAgent = await svc.getById(executionRun.agentId);
        const executionAgentName = executionAgent?.name ?? null;
        return {
            status: "skipped",
            reason: "issue_execution_deferred",
            message: executionAgentName
                ? `Wakeup was deferred because this issue is already being executed by ${executionAgentName}.`
                : "Wakeup was deferred because this issue already has an active execution run.",
            issueId,
            executionRunId: executionRun.id,
            executionAgentId: executionRun.agentId,
            executionAgentName,
        };
    }
    async function assertCanUpdateAgent(req, targetAgent) {
        assertCompanyAccess(req, targetAgent.companyId);
        if (req.actor.type === "board")
            return;
        if (!req.actor.agentId)
            throw forbidden("Agent authentication required");
        const actorAgent = await svc.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== targetAgent.companyId) {
            throw forbidden("Agent key cannot access another company");
        }
        if (actorAgent.id === targetAgent.id)
            return;
        if (actorAgent.role === "ceo")
            return;
        const allowedByGrant = await access.hasPermission(targetAgent.companyId, "agent", actorAgent.id, "agents:create");
        if (allowedByGrant || canCreateAgents(actorAgent))
            return;
        throw forbidden("Only CEO or agent creators can modify other agents");
    }
    async function assertCanReadAgent(req, targetAgent) {
        assertCompanyAccess(req, targetAgent.companyId);
        if (req.actor.type === "board")
            return;
        if (!req.actor.agentId)
            throw forbidden("Agent authentication required");
        const actorAgent = await svc.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== targetAgent.companyId) {
            throw forbidden("Agent key cannot access another company");
        }
    }
    async function resolveCompanyIdForAgentReference(req) {
        const companyIdQuery = req.query.companyId;
        const requestedCompanyId = typeof companyIdQuery === "string" && companyIdQuery.trim().length > 0
            ? companyIdQuery.trim()
            : null;
        if (requestedCompanyId) {
            assertCompanyAccess(req, requestedCompanyId);
            return requestedCompanyId;
        }
        if (req.actor.type === "agent" && req.actor.companyId) {
            return req.actor.companyId;
        }
        return null;
    }
    async function normalizeAgentReference(req, rawId) {
        const raw = rawId.trim();
        if (isUuidLike(raw))
            return raw;
        const companyId = await resolveCompanyIdForAgentReference(req);
        if (!companyId) {
            throw unprocessable("Agent shortname lookup requires companyId query parameter");
        }
        const resolved = await svc.resolveByReference(companyId, raw);
        if (resolved.ambiguous) {
            throw conflict("Agent shortname is ambiguous in this company. Use the agent ID.");
        }
        if (!resolved.agent) {
            throw notFound("Agent not found");
        }
        return resolved.agent.id;
    }
    function parseSourceIssueIds(input) {
        const values = [];
        if (Array.isArray(input.sourceIssueIds))
            values.push(...input.sourceIssueIds);
        if (typeof input.sourceIssueId === "string" && input.sourceIssueId.length > 0) {
            values.push(input.sourceIssueId);
        }
        return Array.from(new Set(values));
    }
    function asRecord(value) {
        if (typeof value !== "object" || value === null || Array.isArray(value))
            return null;
        return value;
    }
    function asNonEmptyString(value) {
        if (typeof value !== "string")
            return null;
        const trimmed = value.trim();
        return trimmed.length > 0 ? trimmed : null;
    }
    function preserveInstructionsBundleConfig(existingAdapterConfig, nextAdapterConfig) {
        const nextKeys = new Set(Object.keys(nextAdapterConfig));
        if (KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => nextKeys.has(key))) {
            return nextAdapterConfig;
        }
        const merged = { ...nextAdapterConfig };
        for (const key of KNOWN_INSTRUCTIONS_BUNDLE_KEYS) {
            if (merged[key] === undefined && existingAdapterConfig[key] !== undefined) {
                merged[key] = existingAdapterConfig[key];
            }
        }
        return merged;
    }
    function parseBooleanLike(value) {
        if (typeof value === "boolean")
            return value;
        if (typeof value === "number") {
            if (value === 1)
                return true;
            if (value === 0)
                return false;
            return null;
        }
        if (typeof value !== "string")
            return null;
        const normalized = value.trim().toLowerCase();
        if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") {
            return true;
        }
        if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off") {
            return false;
        }
        return null;
    }
    function parseNumberLike(value) {
        if (typeof value === "number" && Number.isFinite(value))
            return value;
        if (typeof value !== "string")
            return null;
        const parsed = Number(value.trim());
        return Number.isFinite(parsed) ? parsed : null;
    }
    function parseSchedulerHeartbeatPolicy(runtimeConfig) {
        const heartbeat = asRecord(asRecord(runtimeConfig)?.heartbeat) ?? {};
        return {
            enabled: parseBooleanLike(heartbeat.enabled) ?? true,
            intervalSec: Math.max(0, parseNumberLike(heartbeat.intervalSec) ?? 0),
        };
    }
    function generateEd25519PrivateKeyPem() {
        const { privateKey } = generateKeyPairSync("ed25519");
        return privateKey.export({ type: "pkcs8", format: "pem" }).toString();
    }
    function ensureGatewayDeviceKey(adapterType, adapterConfig) {
        if (adapterType !== "openclaw_gateway")
            return adapterConfig;
        const disableDeviceAuth = parseBooleanLike(adapterConfig.disableDeviceAuth) === true;
        if (disableDeviceAuth)
            return adapterConfig;
        if (asNonEmptyString(adapterConfig.devicePrivateKeyPem))
            return adapterConfig;
        return { ...adapterConfig, devicePrivateKeyPem: generateEd25519PrivateKeyPem() };
    }
    function applyCreateDefaultsByAdapterType(adapterType, adapterConfig) {
        const next = { ...adapterConfig };
        if (adapterType === "codex_local") {
            if (!asNonEmptyString(next.model)) {
                next.model = DEFAULT_CODEX_LOCAL_MODEL;
            }
            const hasBypassFlag = typeof next.dangerouslyBypassApprovalsAndSandbox === "boolean" ||
                typeof next.dangerouslyBypassSandbox === "boolean";
            if (!hasBypassFlag) {
                next.dangerouslyBypassApprovalsAndSandbox = DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX;
            }
            return ensureGatewayDeviceKey(adapterType, next);
        }
        if (adapterType === "gemini_local" && !asNonEmptyString(next.model)) {
            next.model = DEFAULT_GEMINI_LOCAL_MODEL;
            return ensureGatewayDeviceKey(adapterType, next);
        }
        // OpenCode requires explicit model selection — no default
        if (adapterType === "cursor" && !asNonEmptyString(next.model)) {
            next.model = DEFAULT_CURSOR_LOCAL_MODEL;
        }
        return ensureGatewayDeviceKey(adapterType, next);
    }
    async function assertAdapterConfigConstraints(companyId, adapterType, adapterConfig) {
        if (adapterType !== "opencode_local")
            return;
        const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, adapterConfig);
        const runtimeEnv = asRecord(runtimeConfig.env) ?? {};
        try {
            await ensureOpenCodeModelConfiguredAndAvailable({
                model: runtimeConfig.model,
                command: runtimeConfig.command,
                cwd: runtimeConfig.cwd,
                env: runtimeEnv,
            });
        }
        catch (err) {
            const reason = err instanceof Error ? err.message : String(err);
            throw unprocessable(`Invalid opencode_local adapterConfig: ${reason}`);
        }
    }
    function resolveInstructionsFilePath(candidatePath, adapterConfig) {
        const trimmed = candidatePath.trim();
        if (path.isAbsolute(trimmed))
            return trimmed;
        const cwd = asNonEmptyString(adapterConfig.cwd);
        if (!cwd) {
            throw unprocessable("Relative instructions path requires adapterConfig.cwd to be set to an absolute path");
        }
        if (!path.isAbsolute(cwd)) {
            throw unprocessable("adapterConfig.cwd must be an absolute path to resolve relative instructions path");
        }
        return path.resolve(cwd, trimmed);
    }
    async function materializeDefaultInstructionsBundleForNewAgent(agent) {
        if (!DEFAULT_MANAGED_INSTRUCTIONS_ADAPTER_TYPES.has(agent.adapterType)) {
            return agent;
        }
        const adapterConfig = asRecord(agent.adapterConfig) ?? {};
        const hasExplicitInstructionsBundle = Boolean(asNonEmptyString(adapterConfig.instructionsBundleMode))
            || Boolean(asNonEmptyString(adapterConfig.instructionsRootPath))
            || Boolean(asNonEmptyString(adapterConfig.instructionsEntryFile))
            || Boolean(asNonEmptyString(adapterConfig.instructionsFilePath))
            || Boolean(asNonEmptyString(adapterConfig.agentsMdPath));
        if (hasExplicitInstructionsBundle) {
            return agent;
        }
        const promptTemplate = typeof adapterConfig.promptTemplate === "string"
            ? adapterConfig.promptTemplate
            : "";
        const files = promptTemplate.trim().length === 0
            ? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role))
            : { "AGENTS.md": promptTemplate };
        const materialized = await instructions.materializeManagedBundle(agent, files, { entryFile: "AGENTS.md", replaceExisting: false });
        const nextAdapterConfig = { ...materialized.adapterConfig };
        delete nextAdapterConfig.promptTemplate;
        const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig });
        return updated ?? { ...agent, adapterConfig: nextAdapterConfig };
    }
    async function assertCanManageInstructionsPath(req, targetAgent) {
        assertCompanyAccess(req, targetAgent.companyId);
        if (req.actor.type === "board")
            return;
        if (!req.actor.agentId)
            throw forbidden("Agent authentication required");
        const actorAgent = await svc.getById(req.actor.agentId);
        if (!actorAgent || actorAgent.companyId !== targetAgent.companyId) {
            throw forbidden("Agent key cannot access another company");
        }
        if (actorAgent.id === targetAgent.id)
            return;
        const chainOfCommand = await svc.getChainOfCommand(targetAgent.id);
        if (chainOfCommand.some((manager) => manager.id === actorAgent.id))
            return;
        throw forbidden("Only the target agent or an ancestor manager can update instructions path");
    }
    function summarizeAgentUpdateDetails(patch) {
        const changedTopLevelKeys = Object.keys(patch).sort();
        const details = { changedTopLevelKeys };
        const adapterConfigPatch = asRecord(patch.adapterConfig);
        if (adapterConfigPatch) {
            details.changedAdapterConfigKeys = Object.keys(adapterConfigPatch).sort();
        }
        const runtimeConfigPatch = asRecord(patch.runtimeConfig);
        if (runtimeConfigPatch) {
            details.changedRuntimeConfigKeys = Object.keys(runtimeConfigPatch).sort();
        }
        return details;
    }
    function buildUnsupportedSkillSnapshot(adapterType, desiredSkills = []) {
        return {
            adapterType,
            supported: false,
            mode: "unsupported",
            desiredSkills,
            entries: [],
            warnings: ["This adapter does not implement skill sync yet."],
        };
    }
    const ADAPTERS_REQUIRING_MATERIALIZED_RUNTIME_SKILLS = new Set([
        "cursor",
        "gemini_local",
        "opencode_local",
        "pi_local",
    ]);
    function shouldMaterializeRuntimeSkillsForAdapter(adapterType) {
        return ADAPTERS_REQUIRING_MATERIALIZED_RUNTIME_SKILLS.has(adapterType);
    }
    async function buildRuntimeSkillConfig(companyId, adapterType, config) {
        const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(companyId, {
            materializeMissing: shouldMaterializeRuntimeSkillsForAdapter(adapterType),
        });
        return {
            ...config,
            zmeelRuntimeSkills: runtimeSkillEntries,
        };
    }
    async function resolveDesiredSkillAssignment(companyId, adapterType, adapterConfig, requestedDesiredSkills) {
        if (!requestedDesiredSkills) {
            return {
                adapterConfig,
                desiredSkills: null,
                runtimeSkillEntries: null,
            };
        }
        const resolvedRequestedSkills = await companySkills.resolveRequestedSkillKeys(companyId, requestedDesiredSkills);
        const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(companyId, {
            materializeMissing: shouldMaterializeRuntimeSkillsForAdapter(adapterType),
        });
        const requiredSkills = runtimeSkillEntries
            .filter((entry) => entry.required)
            .map((entry) => entry.key);
        const desiredSkills = Array.from(new Set([...requiredSkills, ...resolvedRequestedSkills]));
        return {
            adapterConfig: writezmeelSkillSyncPreference(adapterConfig, desiredSkills),
            desiredSkills,
            runtimeSkillEntries,
        };
    }
    function redactForRestrictedAgentView(agent) {
        if (!agent)
            return null;
        return {
            ...agent,
            adapterConfig: {},
            runtimeConfig: {},
        };
    }
    function redactAgentConfiguration(agent) {
        if (!agent)
            return null;
        return {
            id: agent.id,
            companyId: agent.companyId,
            name: agent.name,
            role: agent.role,
            title: agent.title,
            status: agent.status,
            reportsTo: agent.reportsTo,
            adapterType: agent.adapterType,
            adapterConfig: redactEventPayload(agent.adapterConfig),
            runtimeConfig: redactEventPayload(agent.runtimeConfig),
            permissions: agent.permissions,
            updatedAt: agent.updatedAt,
        };
    }
    function redactRevisionSnapshot(snapshot) {
        if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
            return {};
        const record = snapshot;
        return {
            ...record,
            adapterConfig: redactEventPayload(typeof record.adapterConfig === "object" && record.adapterConfig !== null
                ? record.adapterConfig
                : {}),
            runtimeConfig: redactEventPayload(typeof record.runtimeConfig === "object" && record.runtimeConfig !== null
                ? record.runtimeConfig
                : {}),
            metadata: typeof record.metadata === "object" && record.metadata !== null
                ? redactEventPayload(record.metadata)
                : record.metadata ?? null,
        };
    }
    function redactConfigRevision(revision) {
        return {
            ...revision,
            beforeConfig: redactRevisionSnapshot(revision.beforeConfig),
            afterConfig: redactRevisionSnapshot(revision.afterConfig),
        };
    }
    function toLeanOrgNode(node) {
        const reports = Array.isArray(node.reports)
            ? node.reports.map((report) => toLeanOrgNode(report))
            : [];
        return {
            id: String(node.id),
            name: String(node.name),
            role: String(node.role),
            status: String(node.status),
            reports,
        };
    }
    router.param("id", async (req, _res, next, rawId) => {
        try {
            req.params.id = await normalizeAgentReference(req, String(rawId));
            next();
        }
        catch (err) {
            next(err);
        }
    });
    router.get("/companies/:companyId/adapters/:type/models", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const type = req.params.type;
        const models = await listAdapterModels(type);
        res.json(models);
    });
    router.get("/companies/:companyId/adapters/:type/detect-model", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const type = req.params.type;
        const detected = await detectAdapterModel(type);
        res.json(detected);
    });
    router.post("/companies/:companyId/adapters/:type/test-environment", validate(testAdapterEnvironmentSchema), async (req, res) => {
        const companyId = req.params.companyId;
        const type = req.params.type;
        await assertCanReadConfigurations(req, companyId);
        const adapter = findServerAdapter(type);
        if (!adapter) {
            res.status(404).json({ error: `Unknown adapter type: ${type}` });
            return;
        }
        const inputAdapterConfig = (req.body?.adapterConfig ?? {});
        const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, inputAdapterConfig, { strictMode: strictSecretsMode });
        const { config: runtimeAdapterConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, normalizedAdapterConfig);
        const result = await adapter.testEnvironment({
            companyId,
            adapterType: type,
            config: runtimeAdapterConfig,
        });
        res.json(result);
    });
    router.get("/agents/:id/skills", async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanReadConfigurations(req, agent.companyId);
        const adapter = findServerAdapter(agent.adapterType);
        if (!adapter?.listSkills) {
            const preference = readzmeelSkillSyncPreference(agent.adapterConfig);
            const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(agent.companyId, {
                materializeMissing: false,
            });
            const requiredSkills = runtimeSkillEntries.filter((entry) => entry.required).map((entry) => entry.key);
            res.json(buildUnsupportedSkillSnapshot(agent.adapterType, Array.from(new Set([...requiredSkills, ...preference.desiredSkills]))));
            return;
        }
        const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, agent.adapterConfig);
        const runtimeSkillConfig = await buildRuntimeSkillConfig(agent.companyId, agent.adapterType, runtimeConfig);
        const snapshot = await adapter.listSkills({
            agentId: agent.id,
            companyId: agent.companyId,
            adapterType: agent.adapterType,
            config: runtimeSkillConfig,
        });
        res.json(snapshot);
    });
    router.post("/agents/:id/skills/sync", validate(agentSkillSyncSchema), async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanUpdateAgent(req, agent);
        const requestedSkills = Array.from(new Set(req.body.desiredSkills
            .map((value) => value.trim())
            .filter(Boolean)));
        const { adapterConfig: nextAdapterConfig, desiredSkills, runtimeSkillEntries, } = await resolveDesiredSkillAssignment(agent.companyId, agent.adapterType, agent.adapterConfig, requestedSkills);
        if (!desiredSkills || !runtimeSkillEntries) {
            throw unprocessable("Skill sync requires desiredSkills.");
        }
        const actor = getActorInfo(req);
        const updated = await svc.update(agent.id, {
            adapterConfig: nextAdapterConfig,
        }, {
            recordRevision: {
                createdByAgentId: actor.agentId,
                createdByUserId: actor.actorType === "user" ? actor.actorId : null,
                source: "skill-sync",
            },
        });
        if (!updated) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        const adapter = findServerAdapter(updated.adapterType);
        const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(updated.companyId, updated.adapterConfig);
        const runtimeSkillConfig = {
            ...runtimeConfig,
            zmeelRuntimeSkills: runtimeSkillEntries,
        };
        const snapshot = adapter?.syncSkills
            ? await adapter.syncSkills({
                agentId: updated.id,
                companyId: updated.companyId,
                adapterType: updated.adapterType,
                config: runtimeSkillConfig,
            }, desiredSkills)
            : adapter?.listSkills
                ? await adapter.listSkills({
                    agentId: updated.id,
                    companyId: updated.companyId,
                    adapterType: updated.adapterType,
                    config: runtimeSkillConfig,
                })
                : buildUnsupportedSkillSnapshot(updated.adapterType, desiredSkills);
        await logActivity(db, {
            companyId: updated.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            action: "agent.skills_synced",
            entityType: "agent",
            entityId: updated.id,
            agentId: actor.agentId,
            runId: actor.runId,
            details: {
                adapterType: updated.adapterType,
                desiredSkills,
                mode: snapshot.mode,
                supported: snapshot.supported,
                entryCount: snapshot.entries.length,
                warningCount: snapshot.warnings.length,
            },
        });
        res.json(snapshot);
    });
    router.get("/companies/:companyId/agents", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const result = await svc.list(companyId);
        const canReadConfigs = await actorCanReadConfigurationsForCompany(req, companyId);
        if (canReadConfigs || req.actor.type === "board") {
            res.json(result);
            return;
        }
        res.json(result.map((agent) => redactForRestrictedAgentView(agent)));
    });
    router.get("/instance/scheduler-heartbeats", async (req, res) => {
        assertInstanceAdmin(req);
        const rows = await db
            .select({
            id: agentsTable.id,
            companyId: agentsTable.companyId,
            agentName: agentsTable.name,
            role: agentsTable.role,
            title: agentsTable.title,
            status: agentsTable.status,
            adapterType: agentsTable.adapterType,
            runtimeConfig: agentsTable.runtimeConfig,
            lastHeartbeatAt: agentsTable.lastHeartbeatAt,
            companyName: companies.name,
            companyIssuePrefix: companies.issuePrefix,
        })
            .from(agentsTable)
            .innerJoin(companies, eq(agentsTable.companyId, companies.id))
            .orderBy(companies.name, agentsTable.name);
        const items = rows
            .map((row) => {
            const policy = parseSchedulerHeartbeatPolicy(row.runtimeConfig);
            const statusEligible = row.status !== "paused" &&
                row.status !== "terminated" &&
                row.status !== "pending_approval";
            return {
                id: row.id,
                companyId: row.companyId,
                companyName: row.companyName,
                companyIssuePrefix: row.companyIssuePrefix,
                agentName: row.agentName,
                agentUrlKey: deriveAgentUrlKey(row.agentName, row.id),
                role: row.role,
                title: row.title,
                status: row.status,
                adapterType: row.adapterType,
                intervalSec: policy.intervalSec,
                heartbeatEnabled: policy.enabled,
                schedulerActive: statusEligible && policy.enabled && policy.intervalSec > 0,
                lastHeartbeatAt: row.lastHeartbeatAt,
            };
        })
            .filter((item) => item.status !== "paused" &&
            item.status !== "terminated" &&
            item.status !== "pending_approval")
            .sort((left, right) => {
            if (left.schedulerActive !== right.schedulerActive) {
                return left.schedulerActive ? -1 : 1;
            }
            const companyOrder = left.companyName.localeCompare(right.companyName);
            if (companyOrder !== 0)
                return companyOrder;
            return left.agentName.localeCompare(right.agentName);
        });
        res.json(items);
    });
    router.get("/companies/:companyId/org", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const tree = await svc.orgForCompany(companyId);
        const leanTree = tree.map((node) => toLeanOrgNode(node));
        res.json(leanTree);
    });
    router.get("/companies/:companyId/org.svg", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const style = (ORG_CHART_STYLES.includes(req.query.style) ? req.query.style : "warmth");
        const tree = await svc.orgForCompany(companyId);
        const leanTree = tree.map((node) => toLeanOrgNode(node));
        const svg = renderOrgChartSvg(leanTree, style);
        res.setHeader("Content-Type", "image/svg+xml");
        res.setHeader("Cache-Control", "no-cache");
        res.send(svg);
    });
    router.get("/companies/:companyId/org.png", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const style = (ORG_CHART_STYLES.includes(req.query.style) ? req.query.style : "warmth");
        const tree = await svc.orgForCompany(companyId);
        const leanTree = tree.map((node) => toLeanOrgNode(node));
        const png = await renderOrgChartPng(leanTree, style);
        res.setHeader("Content-Type", "image/png");
        res.setHeader("Cache-Control", "no-cache");
        res.send(png);
    });
    router.get("/companies/:companyId/agent-configurations", async (req, res) => {
        const companyId = req.params.companyId;
        await assertCanReadConfigurations(req, companyId);
        const rows = await svc.list(companyId);
        res.json(rows.map((row) => redactAgentConfiguration(row)));
    });
    router.get("/agents/me", async (req, res) => {
        if (req.actor.type !== "agent" || !req.actor.agentId) {
            res.status(401).json({ error: "Agent authentication required" });
            return;
        }
        const agent = await svc.getById(req.actor.agentId);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        res.json(await buildAgentDetail(agent));
    });
    router.get("/agents/me/inbox-lite", async (req, res) => {
        if (req.actor.type !== "agent" || !req.actor.agentId || !req.actor.companyId) {
            res.status(401).json({ error: "Agent authentication required" });
            return;
        }
        const issuesSvc = issueService(db);
        const rows = await issuesSvc.list(req.actor.companyId, {
            assigneeAgentId: req.actor.agentId,
            status: "todo,in_progress,blocked",
        });
        res.json(rows.map((issue) => ({
            id: issue.id,
            identifier: issue.identifier,
            title: issue.title,
            status: issue.status,
            priority: issue.priority,
            projectId: issue.projectId,
            goalId: issue.goalId,
            parentId: issue.parentId,
            updatedAt: issue.updatedAt,
            activeRun: issue.activeRun,
        })));
    });
    router.get("/agents/me/inbox/mine", async (req, res) => {
        if (req.actor.type !== "agent" || !req.actor.agentId || !req.actor.companyId) {
            res.status(401).json({ error: "Agent authentication required" });
            return;
        }
        const query = agentMineInboxQuerySchema.parse(req.query);
        const issuesSvc = issueService(db);
        const rows = await issuesSvc.list(req.actor.companyId, {
            touchedByUserId: query.userId,
            inboxArchivedByUserId: query.userId,
            status: query.status,
        });
        res.json(rows);
    });
    router.get("/agents/:id", async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        if (req.actor.type === "agent" && req.actor.agentId !== id) {
            const canRead = await actorCanReadConfigurationsForCompany(req, agent.companyId);
            if (!canRead) {
                res.json(await buildAgentDetail(agent, { restricted: true }));
                return;
            }
        }
        res.json(await buildAgentDetail(agent));
    });
    router.get("/agents/:id/configuration", async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanReadConfigurations(req, agent.companyId);
        res.json(redactAgentConfiguration(agent));
    });
    router.get("/agents/:id/config-revisions", async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanReadConfigurations(req, agent.companyId);
        const revisions = await svc.listConfigRevisions(id);
        res.json(revisions.map((revision) => redactConfigRevision(revision)));
    });
    router.get("/agents/:id/config-revisions/:revisionId", async (req, res) => {
        const id = req.params.id;
        const revisionId = req.params.revisionId;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanReadConfigurations(req, agent.companyId);
        const revision = await svc.getConfigRevision(id, revisionId);
        if (!revision) {
            res.status(404).json({ error: "Revision not found" });
            return;
        }
        res.json(redactConfigRevision(revision));
    });
    router.post("/agents/:id/config-revisions/:revisionId/rollback", async (req, res) => {
        const id = req.params.id;
        const revisionId = req.params.revisionId;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanUpdateAgent(req, existing);
        const actor = getActorInfo(req);
        const updated = await svc.rollbackConfigRevision(id, revisionId, {
            agentId: actor.agentId,
            userId: actor.actorType === "user" ? actor.actorId : null,
        });
        if (!updated) {
            res.status(404).json({ error: "Revision not found" });
            return;
        }
        await logActivity(db, {
            companyId: updated.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.config_rolled_back",
            entityType: "agent",
            entityId: updated.id,
            details: { revisionId },
        });
        res.json(updated);
    });
    router.get("/agents/:id/runtime-state", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        const state = await heartbeat.getRuntimeState(id);
        res.json(state);
    });
    router.get("/agents/:id/task-sessions", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        const sessions = await heartbeat.listTaskSessions(id);
        res.json(sessions.map((session) => ({
            ...session,
            sessionParamsJson: redactEventPayload(session.sessionParamsJson ?? null),
        })));
    });
    router.post("/agents/:id/runtime-state/reset-session", validate(resetAgentSessionSchema), async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        const taskKey = typeof req.body.taskKey === "string" && req.body.taskKey.trim().length > 0
            ? req.body.taskKey.trim()
            : null;
        const state = await heartbeat.resetRuntimeSession(id, { taskKey });
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: "user",
            actorId: req.actor.userId ?? "board",
            action: "agent.runtime_session_reset",
            entityType: "agent",
            entityId: id,
            details: { taskKey: taskKey ?? null },
        });
        res.json(state);
    });
    router.post("/companies/:companyId/agent-hires", validate(createAgentHireSchema), async (req, res) => {
        const companyId = req.params.companyId;
        await assertCanCreateAgentsForCompany(req, companyId);
        const sourceIssueIds = parseSourceIssueIds(req.body);
        const { desiredSkills: requestedDesiredSkills, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body;
        const requestedAdapterConfig = applyCreateDefaultsByAdapterType(hireInput.adapterType, (hireInput.adapterConfig ?? {}));
        const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, hireInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
        const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, desiredSkillAssignment.adapterConfig, { strictMode: strictSecretsMode });
        await assertAdapterConfigConstraints(companyId, hireInput.adapterType, normalizedAdapterConfig);
        const normalizedHireInput = {
            ...hireInput,
            adapterConfig: normalizedAdapterConfig,
        };
        const company = await db
            .select()
            .from(companies)
            .where(eq(companies.id, companyId))
            .then((rows) => rows[0] ?? null);
        if (!company) {
            res.status(404).json({ error: "Company not found" });
            return;
        }
        const requiresApproval = company.requireBoardApprovalForNewAgents;
        const status = requiresApproval ? "pending_approval" : "idle";
        const createdAgent = await svc.create(companyId, {
            ...normalizedHireInput,
            status,
            spentMonthlyCents: 0,
            lastHeartbeatAt: null,
        });
        const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent);
        let approval = null;
        const actor = getActorInfo(req);
        if (requiresApproval) {
            const requestedAdapterType = normalizedHireInput.adapterType ?? agent.adapterType;
            const requestedAdapterConfig = redactEventPayload((agent.adapterConfig ?? normalizedHireInput.adapterConfig)) ?? {};
            const requestedRuntimeConfig = redactEventPayload((normalizedHireInput.runtimeConfig ?? agent.runtimeConfig)) ?? {};
            const requestedMetadata = redactEventPayload((normalizedHireInput.metadata ?? agent.metadata ?? {})) ?? {};
            approval = await approvalsSvc.create(companyId, {
                type: "hire_agent",
                requestedByAgentId: actor.actorType === "agent" ? actor.actorId : null,
                requestedByUserId: actor.actorType === "user" ? actor.actorId : null,
                status: "pending",
                payload: {
                    name: normalizedHireInput.name,
                    role: normalizedHireInput.role,
                    title: normalizedHireInput.title ?? null,
                    icon: normalizedHireInput.icon ?? null,
                    reportsTo: normalizedHireInput.reportsTo ?? null,
                    capabilities: normalizedHireInput.capabilities ?? null,
                    adapterType: requestedAdapterType,
                    adapterConfig: requestedAdapterConfig,
                    runtimeConfig: requestedRuntimeConfig,
                    budgetMonthlyCents: typeof normalizedHireInput.budgetMonthlyCents === "number"
                        ? normalizedHireInput.budgetMonthlyCents
                        : agent.budgetMonthlyCents,
                    desiredSkills: desiredSkillAssignment.desiredSkills,
                    metadata: requestedMetadata,
                    agentId: agent.id,
                    requestedByAgentId: actor.actorType === "agent" ? actor.actorId : null,
                    requestedConfigurationSnapshot: {
                        adapterType: requestedAdapterType,
                        adapterConfig: requestedAdapterConfig,
                        runtimeConfig: requestedRuntimeConfig,
                        desiredSkills: desiredSkillAssignment.desiredSkills,
                    },
                },
                decisionNote: null,
                decidedByUserId: null,
                decidedAt: null,
                updatedAt: new Date(),
            });
            if (sourceIssueIds.length > 0) {
                await issueApprovalsSvc.linkManyForApproval(approval.id, sourceIssueIds, {
                    agentId: actor.actorType === "agent" ? actor.actorId : null,
                    userId: actor.actorType === "user" ? actor.actorId : null,
                });
            }
        }
        await logActivity(db, {
            companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.hire_created",
            entityType: "agent",
            entityId: agent.id,
            details: {
                name: agent.name,
                role: agent.role,
                requiresApproval,
                approvalId: approval?.id ?? null,
                issueIds: sourceIssueIds,
                desiredSkills: desiredSkillAssignment.desiredSkills,
            },
        });
        await applyDefaultAgentTaskAssignGrant(companyId, agent.id, actor.actorType === "user" ? actor.actorId : null);
        if (approval) {
            await logActivity(db, {
                companyId,
                actorType: actor.actorType,
                actorId: actor.actorId,
                agentId: actor.agentId,
                runId: actor.runId,
                action: "approval.created",
                entityType: "approval",
                entityId: approval.id,
                details: { type: approval.type, linkedAgentId: agent.id },
            });
        }
        res.status(201).json({ agent, approval });
    });
    // Hire from role template — simplified wizard flow
    router.post("/companies/:companyId/agents/hire-from-template", async (req, res) => {
        const { getRoleTemplate: getTemplate } = await import("../services/role-templates.js");
        const companyId = req.params.companyId;
        await assertCanCreateAgentsForCompany(req, companyId);
        const { roleTemplateId, name, tools, systemPrompt } = req.body;
        const template = getTemplate(roleTemplateId);
        if (!template) {
            res.status(400).json({ error: `Unknown role template: ${roleTemplateId}` });
            return;
        }
        const company = await db
            .select()
            .from(companies)
            .where(eq(companies.id, companyId))
            .then((rows) => rows[0] ?? null);
        if (!company) {
            res.status(404).json({ error: "Company not found" });
            return;
        }
        const agentName = (name && name.trim()) || template.defaultName;
        const resolvedPrompt = (systemPrompt || template.systemPrompt).replace(/\{company\}/g, company.name);
        const resolvedTools = tools && tools.length > 0 ? tools : template.defaultTools;
        const createdAgent = await svc.create(companyId, {
            name: agentName,
            role: "general",
            adapterType: "cloud",
            adapterConfig: {
                model: template.defaultModel,
                systemPrompt: resolvedPrompt,
                composioApps: resolvedTools,
            },
            runtimeConfig: {
                heartbeat: {
                    enabled: true,
                    intervalSec: 1800,
                    wakeOnDemand: true,
                    cooldownSec: 10,
                    maxConcurrentRuns: 1,
                },
            },
            status: "idle",
            spentMonthlyCents: 0,
            budgetMonthlyCents: 0,
            lastHeartbeatAt: null,
            metadata: {
                roleTemplateId: template.id,
                roleTemplateLabel: template.label,
            },
        });
        const actor = getActorInfo(req);
        await applyDefaultAgentTaskAssignGrant(companyId, createdAgent.id, actor.actorType === "user" ? actor.actorId : null);
        // Initialize employee brain files from role template defaults
        const { employeeFileService } = await import("../services/employee-files.js");
        const fileService = employeeFileService(db);
        const companyName = company.name;
        await fileService.initializeFiles(createdAgent.id, companyId, {
            soul: template.soulDefault.replace(/\{company\}/g, companyName).replace(/\{name\}/g, agentName),
            procedures: template.proceduresDefault.replace(/\{company\}/g, companyName).replace(/\{name\}/g, agentName),
            heartbeat: template.heartbeatDefault.replace(/\{company\}/g, companyName).replace(/\{name\}/g, agentName),
            memory: template.memoryDefault,
            tools: template.toolsDefault,
            learning: template.learningDefault.replace(/\{company\}/g, companyName).replace(/\{name\}/g, agentName),
        });
        await logActivity(db, {
            companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.hire_created",
            entityType: "agent",
            entityId: createdAgent.id,
            details: {
                name: createdAgent.name,
                role: createdAgent.role,
                roleTemplateId: template.id,
                requiresApproval: false,
                approvalId: null,
            },
        });
        res.status(201).json({ agent: createdAgent });
    });
    router.post("/companies/:companyId/agents", validate(createAgentSchema), async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        if (req.actor.type === "agent") {
            assertBoard(req);
        }
        const { desiredSkills: requestedDesiredSkills, ...createInput } = req.body;
        const requestedAdapterConfig = applyCreateDefaultsByAdapterType(createInput.adapterType, (createInput.adapterConfig ?? {}));
        const desiredSkillAssignment = await resolveDesiredSkillAssignment(companyId, createInput.adapterType, requestedAdapterConfig, Array.isArray(requestedDesiredSkills) ? requestedDesiredSkills : undefined);
        const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(companyId, desiredSkillAssignment.adapterConfig, { strictMode: strictSecretsMode });
        await assertAdapterConfigConstraints(companyId, createInput.adapterType, normalizedAdapterConfig);
        const createdAgent = await svc.create(companyId, {
            ...createInput,
            adapterConfig: normalizedAdapterConfig,
            status: "idle",
            spentMonthlyCents: 0,
            lastHeartbeatAt: null,
        });
        const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent);
        const actor = getActorInfo(req);
        await logActivity(db, {
            companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.created",
            entityType: "agent",
            entityId: agent.id,
            details: {
                name: agent.name,
                role: agent.role,
                desiredSkills: desiredSkillAssignment.desiredSkills,
            },
        });
        await applyDefaultAgentTaskAssignGrant(companyId, agent.id, req.actor.type === "board" ? (req.actor.userId ?? null) : null);
        if (agent.budgetMonthlyCents > 0) {
            await budgets.upsertPolicy(companyId, {
                scopeType: "agent",
                scopeId: agent.id,
                amount: agent.budgetMonthlyCents,
                windowKind: "calendar_month_utc",
            }, actor.actorType === "user" ? actor.actorId : null);
        }
        res.status(201).json(agent);
    });
    router.patch("/agents/:id/permissions", validate(updateAgentPermissionsSchema), async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, existing.companyId);
        if (req.actor.type === "agent") {
            const actorAgent = req.actor.agentId ? await svc.getById(req.actor.agentId) : null;
            if (!actorAgent || actorAgent.companyId !== existing.companyId) {
                res.status(403).json({ error: "Forbidden" });
                return;
            }
            if (actorAgent.role !== "ceo") {
                res.status(403).json({ error: "Only CEO can manage permissions" });
                return;
            }
        }
        const agent = await svc.updatePermissions(id, req.body);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        const effectiveCanAssignTasks = agent.role === "ceo" || Boolean(agent.permissions?.canCreateAgents) || req.body.canAssignTasks;
        await access.ensureMembership(agent.companyId, "agent", agent.id, "member", "active");
        await access.setPrincipalPermission(agent.companyId, "agent", agent.id, "tasks:assign", effectiveCanAssignTasks, req.actor.type === "board" ? (req.actor.userId ?? null) : null);
        const actor = getActorInfo(req);
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.permissions_updated",
            entityType: "agent",
            entityId: agent.id,
            details: {
                canCreateAgents: agent.permissions?.canCreateAgents ?? false,
                canAssignTasks: effectiveCanAssignTasks,
            },
        });
        res.json(await buildAgentDetail(agent));
    });
    router.patch("/agents/:id/instructions-path", validate(updateAgentInstructionsPathSchema), async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanManageInstructionsPath(req, existing);
        const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {};
        const explicitKey = asNonEmptyString(req.body.adapterConfigKey);
        const defaultKey = DEFAULT_INSTRUCTIONS_PATH_KEYS[existing.adapterType] ?? null;
        const adapterConfigKey = explicitKey ?? defaultKey;
        if (!adapterConfigKey) {
            res.status(422).json({
                error: `No default instructions path key for adapter type '${existing.adapterType}'. Provide adapterConfigKey.`,
            });
            return;
        }
        const nextAdapterConfig = { ...existingAdapterConfig };
        if (req.body.path === null) {
            delete nextAdapterConfig[adapterConfigKey];
        }
        else {
            nextAdapterConfig[adapterConfigKey] = resolveInstructionsFilePath(req.body.path, existingAdapterConfig);
        }
        const syncedAdapterConfig = syncInstructionsBundleConfigFromFilePath(existing, nextAdapterConfig);
        const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, syncedAdapterConfig, { strictMode: strictSecretsMode });
        const actor = getActorInfo(req);
        const agent = await svc.update(id, { adapterConfig: normalizedAdapterConfig }, {
            recordRevision: {
                createdByAgentId: actor.agentId,
                createdByUserId: actor.actorType === "user" ? actor.actorId : null,
                source: "instructions_path_patch",
            },
        });
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        const updatedAdapterConfig = asRecord(agent.adapterConfig) ?? {};
        const pathValue = asNonEmptyString(updatedAdapterConfig[adapterConfigKey]);
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.instructions_path_updated",
            entityType: "agent",
            entityId: agent.id,
            details: {
                adapterConfigKey,
                path: pathValue,
                cleared: req.body.path === null,
            },
        });
        res.json({
            agentId: agent.id,
            adapterType: agent.adapterType,
            adapterConfigKey,
            path: pathValue,
        });
    });
    router.get("/agents/:id/instructions-bundle", async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanReadAgent(req, existing);
        res.json(await instructions.getBundle(existing));
    });
    router.patch("/agents/:id/instructions-bundle", validate(updateAgentInstructionsBundleSchema), async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanManageInstructionsPath(req, existing);
        const actor = getActorInfo(req);
        const { bundle, adapterConfig } = await instructions.updateBundle(existing, req.body);
        const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, adapterConfig, { strictMode: strictSecretsMode });
        await svc.update(id, { adapterConfig: normalizedAdapterConfig }, {
            recordRevision: {
                createdByAgentId: actor.agentId,
                createdByUserId: actor.actorType === "user" ? actor.actorId : null,
                source: "instructions_bundle_patch",
            },
        });
        await logActivity(db, {
            companyId: existing.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.instructions_bundle_updated",
            entityType: "agent",
            entityId: existing.id,
            details: {
                mode: bundle.mode,
                rootPath: bundle.rootPath,
                entryFile: bundle.entryFile,
                clearLegacyPromptTemplate: req.body.clearLegacyPromptTemplate === true,
            },
        });
        res.json(bundle);
    });
    router.get("/agents/:id/instructions-bundle/file", async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanReadAgent(req, existing);
        const relativePath = typeof req.query.path === "string" ? req.query.path : "";
        if (!relativePath.trim()) {
            res.status(422).json({ error: "Query parameter 'path' is required" });
            return;
        }
        res.json(await instructions.readFile(existing, relativePath));
    });
    router.put("/agents/:id/instructions-bundle/file", validate(upsertAgentInstructionsFileSchema), async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanManageInstructionsPath(req, existing);
        const actor = getActorInfo(req);
        const result = await instructions.writeFile(existing, req.body.path, req.body.content, {
            clearLegacyPromptTemplate: req.body.clearLegacyPromptTemplate,
        });
        const normalizedAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, result.adapterConfig, { strictMode: strictSecretsMode });
        await svc.update(id, { adapterConfig: normalizedAdapterConfig }, {
            recordRevision: {
                createdByAgentId: actor.agentId,
                createdByUserId: actor.actorType === "user" ? actor.actorId : null,
                source: "instructions_bundle_file_put",
            },
        });
        await logActivity(db, {
            companyId: existing.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.instructions_file_updated",
            entityType: "agent",
            entityId: existing.id,
            details: {
                path: result.file.path,
                size: result.file.size,
                clearLegacyPromptTemplate: req.body.clearLegacyPromptTemplate === true,
            },
        });
        res.json(result.file);
    });
    router.delete("/agents/:id/instructions-bundle/file", async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanManageInstructionsPath(req, existing);
        const relativePath = typeof req.query.path === "string" ? req.query.path : "";
        if (!relativePath.trim()) {
            res.status(422).json({ error: "Query parameter 'path' is required" });
            return;
        }
        const actor = getActorInfo(req);
        const result = await instructions.deleteFile(existing, relativePath);
        await logActivity(db, {
            companyId: existing.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.instructions_file_deleted",
            entityType: "agent",
            entityId: existing.id,
            details: {
                path: relativePath,
            },
        });
        res.json(result.bundle);
    });
    router.patch("/agents/:id", validate(updateAgentSchema), async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await assertCanUpdateAgent(req, existing);
        if (Object.prototype.hasOwnProperty.call(req.body, "permissions")) {
            res.status(422).json({ error: "Use /api/agents/:id/permissions for permission changes" });
            return;
        }
        const patchData = { ...req.body };
        const replaceAdapterConfig = patchData.replaceAdapterConfig === true;
        delete patchData.replaceAdapterConfig;
        if (Object.prototype.hasOwnProperty.call(patchData, "adapterConfig")) {
            const adapterConfig = asRecord(patchData.adapterConfig);
            if (!adapterConfig) {
                res.status(422).json({ error: "adapterConfig must be an object" });
                return;
            }
            const changingInstructionsPath = Object.keys(adapterConfig).some((key) => KNOWN_INSTRUCTIONS_PATH_KEYS.has(key));
            if (changingInstructionsPath) {
                await assertCanManageInstructionsPath(req, existing);
            }
            patchData.adapterConfig = adapterConfig;
        }
        const requestedAdapterType = typeof patchData.adapterType === "string" ? patchData.adapterType : existing.adapterType;
        const touchesAdapterConfiguration = Object.prototype.hasOwnProperty.call(patchData, "adapterType") ||
            Object.prototype.hasOwnProperty.call(patchData, "adapterConfig");
        if (touchesAdapterConfiguration) {
            const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {};
            const changingAdapterType = typeof patchData.adapterType === "string" && patchData.adapterType !== existing.adapterType;
            const requestedAdapterConfig = Object.prototype.hasOwnProperty.call(patchData, "adapterConfig")
                ? (asRecord(patchData.adapterConfig) ?? {})
                : null;
            if (requestedAdapterConfig
                && replaceAdapterConfig
                && KNOWN_INSTRUCTIONS_BUNDLE_KEYS.some((key) => existingAdapterConfig[key] !== undefined && requestedAdapterConfig[key] === undefined)) {
                await assertCanManageInstructionsPath(req, existing);
            }
            let rawEffectiveAdapterConfig = requestedAdapterConfig ?? existingAdapterConfig;
            if (requestedAdapterConfig && !changingAdapterType && !replaceAdapterConfig) {
                rawEffectiveAdapterConfig = { ...existingAdapterConfig, ...requestedAdapterConfig };
            }
            if (changingAdapterType) {
                // Preserve adapter-agnostic keys (env, cwd, etc.) from the existing config
                // when the adapter type changes. Without this, a PATCH that includes
                // adapterConfig but omits these keys would silently drop them.
                const ADAPTER_AGNOSTIC_KEYS = [
                    "env", "cwd", "timeoutSec", "graceSec",
                    "promptTemplate", "bootstrapPromptTemplate",
                ];
                for (const key of ADAPTER_AGNOSTIC_KEYS) {
                    if (rawEffectiveAdapterConfig[key] === undefined && existingAdapterConfig[key] !== undefined) {
                        rawEffectiveAdapterConfig = { ...rawEffectiveAdapterConfig, [key]: existingAdapterConfig[key] };
                    }
                }
                rawEffectiveAdapterConfig = preserveInstructionsBundleConfig(existingAdapterConfig, rawEffectiveAdapterConfig);
            }
            const effectiveAdapterConfig = applyCreateDefaultsByAdapterType(requestedAdapterType, rawEffectiveAdapterConfig);
            const normalizedEffectiveAdapterConfig = await secretsSvc.normalizeAdapterConfigForPersistence(existing.companyId, effectiveAdapterConfig, { strictMode: strictSecretsMode });
            patchData.adapterConfig = syncInstructionsBundleConfigFromFilePath(existing, normalizedEffectiveAdapterConfig);
        }
        if (touchesAdapterConfiguration && requestedAdapterType === "opencode_local") {
            const effectiveAdapterConfig = asRecord(patchData.adapterConfig) ?? {};
            await assertAdapterConfigConstraints(existing.companyId, requestedAdapterType, effectiveAdapterConfig);
        }
        const actor = getActorInfo(req);
        const agent = await svc.update(id, patchData, {
            recordRevision: {
                createdByAgentId: actor.agentId,
                createdByUserId: actor.actorType === "user" ? actor.actorId : null,
                source: "patch",
            },
        });
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "agent.updated",
            entityType: "agent",
            entityId: agent.id,
            details: summarizeAgentUpdateDetails(patchData),
        });
        res.json(agent);
    });
    router.post("/agents/:id/pause", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.pause(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await heartbeat.cancelActiveForAgent(id);
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: "user",
            actorId: req.actor.userId ?? "board",
            action: "agent.paused",
            entityType: "agent",
            entityId: agent.id,
        });
        res.json(agent);
    });
    router.post("/agents/:id/resume", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.resume(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: "user",
            actorId: req.actor.userId ?? "board",
            action: "agent.resumed",
            entityType: "agent",
            entityId: agent.id,
        });
        res.json(agent);
    });
    router.post("/agents/:id/terminate", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.terminate(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await heartbeat.cancelActiveForAgent(id);
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: "user",
            actorId: req.actor.userId ?? "board",
            action: "agent.terminated",
            entityType: "agent",
            entityId: agent.id,
        });
        res.json(agent);
    });
    router.delete("/agents/:id", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.remove(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: "user",
            actorId: req.actor.userId ?? "board",
            action: "agent.deleted",
            entityType: "agent",
            entityId: agent.id,
        });
        res.json({ ok: true });
    });
    router.get("/agents/:id/keys", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const keys = await svc.listKeys(id);
        res.json(keys);
    });
    router.post("/agents/:id/keys", validate(createAgentKeySchema), async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const key = await svc.createApiKey(id, req.body.name);
        const agent = await svc.getById(id);
        if (agent) {
            await logActivity(db, {
                companyId: agent.companyId,
                actorType: "user",
                actorId: req.actor.userId ?? "board",
                action: "agent.key_created",
                entityType: "agent",
                entityId: agent.id,
                details: { keyId: key.id, name: key.name },
            });
        }
        res.status(201).json(key);
    });
    router.delete("/agents/:id/keys/:keyId", async (req, res) => {
        assertBoard(req);
        const keyId = req.params.keyId;
        const revoked = await svc.revokeKey(keyId);
        if (!revoked) {
            res.status(404).json({ error: "Key not found" });
            return;
        }
        res.json({ ok: true });
    });
    router.post("/agents/:id/wakeup", validate(wakeAgentSchema), async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        if (req.actor.type === "agent" && req.actor.agentId !== id) {
            res.status(403).json({ error: "Agent can only invoke itself" });
            return;
        }
        const run = await heartbeat.wakeup(id, {
            source: req.body.source,
            triggerDetail: req.body.triggerDetail ?? "manual",
            reason: req.body.reason ?? null,
            payload: req.body.payload ?? null,
            idempotencyKey: req.body.idempotencyKey ?? null,
            requestedByActorType: req.actor.type === "agent" ? "agent" : "user",
            requestedByActorId: req.actor.type === "agent" ? req.actor.agentId ?? null : req.actor.userId ?? null,
            contextSnapshot: {
                triggeredBy: req.actor.type,
                actorId: req.actor.type === "agent" ? req.actor.agentId : req.actor.userId,
                forceFreshSession: req.body.forceFreshSession === true,
            },
        });
        if (!run) {
            res.status(202).json(await buildSkippedWakeupResponse(agent, req.body.payload ?? null));
            return;
        }
        const actor = getActorInfo(req);
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "heartbeat.invoked",
            entityType: "heartbeat_run",
            entityId: run.id,
            details: { agentId: id },
        });
        res.status(202).json(run);
    });
    router.post("/agents/:id/heartbeat/invoke", async (req, res) => {
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        if (req.actor.type === "agent" && req.actor.agentId !== id) {
            res.status(403).json({ error: "Agent can only invoke itself" });
            return;
        }
        const run = await heartbeat.invoke(id, "on_demand", {
            triggeredBy: req.actor.type,
            actorId: req.actor.type === "agent" ? req.actor.agentId : req.actor.userId,
        }, "manual", {
            actorType: req.actor.type === "agent" ? "agent" : "user",
            actorId: req.actor.type === "agent" ? req.actor.agentId ?? null : req.actor.userId ?? null,
        });
        if (!run) {
            res.status(202).json({ status: "skipped" });
            return;
        }
        const actor = getActorInfo(req);
        await logActivity(db, {
            companyId: agent.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            runId: actor.runId,
            action: "heartbeat.invoked",
            entityType: "heartbeat_run",
            entityId: run.id,
            details: { agentId: id },
        });
        res.status(202).json(run);
    });
    router.post("/agents/:id/claude-login", async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        const agent = await svc.getById(id);
        if (!agent) {
            res.status(404).json({ error: "Agent not found" });
            return;
        }
        assertCompanyAccess(req, agent.companyId);
        if (agent.adapterType !== "claude_local") {
            res.status(400).json({ error: "Login is only supported for claude_local agents" });
            return;
        }
        const config = asRecord(agent.adapterConfig) ?? {};
        const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, config);
        const result = await runClaudeLogin({
            runId: `claude-login-${randomUUID()}`,
            agent: {
                id: agent.id,
                companyId: agent.companyId,
                name: agent.name,
                adapterType: agent.adapterType,
                adapterConfig: agent.adapterConfig,
            },
            config: runtimeConfig,
        });
        res.json(result);
    });
    router.get("/companies/:companyId/heartbeat-runs", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const agentId = req.query.agentId;
        const limitParam = req.query.limit;
        const limit = limitParam ? Math.max(1, Math.min(1000, parseInt(limitParam, 10) || 200)) : undefined;
        const runs = await heartbeat.list(companyId, agentId, limit);
        res.json(runs);
    });
    router.get("/companies/:companyId/live-runs", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        const minCountParam = req.query.minCount;
        const minCount = minCountParam ? Math.max(0, Math.min(20, parseInt(minCountParam, 10) || 0)) : 0;
        const columns = {
            id: heartbeatRuns.id,
            status: heartbeatRuns.status,
            invocationSource: heartbeatRuns.invocationSource,
            triggerDetail: heartbeatRuns.triggerDetail,
            startedAt: heartbeatRuns.startedAt,
            finishedAt: heartbeatRuns.finishedAt,
            createdAt: heartbeatRuns.createdAt,
            agentId: heartbeatRuns.agentId,
            agentName: agentsTable.name,
            adapterType: agentsTable.adapterType,
            issueId: sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`.as("issueId"),
        };
        const liveRuns = await db
            .select(columns)
            .from(heartbeatRuns)
            .innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
            .where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, ["queued", "running"])))
            .orderBy(desc(heartbeatRuns.createdAt));
        if (minCount > 0 && liveRuns.length < minCount) {
            const activeIds = liveRuns.map((r) => r.id);
            const recentRuns = await db
                .select(columns)
                .from(heartbeatRuns)
                .innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
                .where(and(eq(heartbeatRuns.companyId, companyId), not(inArray(heartbeatRuns.status, ["queued", "running"])), ...(activeIds.length > 0 ? [not(inArray(heartbeatRuns.id, activeIds))] : [])))
                .orderBy(desc(heartbeatRuns.createdAt))
                .limit(minCount - liveRuns.length);
            res.json([...liveRuns, ...recentRuns]);
            return;
        }
        res.json(liveRuns);
    });
    router.get("/heartbeat-runs/:runId", async (req, res) => {
        const runId = req.params.runId;
        const run = await heartbeat.getRun(runId);
        if (!run) {
            res.status(404).json({ error: "Heartbeat run not found" });
            return;
        }
        assertCompanyAccess(req, run.companyId);
        res.json(redactCurrentUserValue(run, await getCurrentUserRedactionOptions()));
    });
    router.post("/heartbeat-runs/:runId/cancel", async (req, res) => {
        assertBoard(req);
        const runId = req.params.runId;
        const run = await heartbeat.cancelRun(runId);
        if (run) {
            await logActivity(db, {
                companyId: run.companyId,
                actorType: "user",
                actorId: req.actor.userId ?? "board",
                action: "heartbeat.cancelled",
                entityType: "heartbeat_run",
                entityId: run.id,
                details: { agentId: run.agentId },
            });
        }
        res.json(run);
    });
    router.get("/heartbeat-runs/:runId/events", async (req, res) => {
        const runId = req.params.runId;
        const run = await heartbeat.getRun(runId);
        if (!run) {
            res.status(404).json({ error: "Heartbeat run not found" });
            return;
        }
        assertCompanyAccess(req, run.companyId);
        const afterSeq = Number(req.query.afterSeq ?? 0);
        const limit = Number(req.query.limit ?? 200);
        const events = await heartbeat.listEvents(runId, Number.isFinite(afterSeq) ? afterSeq : 0, Number.isFinite(limit) ? limit : 200);
        const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
        const redactedEvents = events.map((event) => redactCurrentUserValue({
            ...event,
            payload: redactEventPayload(event.payload),
        }, currentUserRedactionOptions));
        res.json(redactedEvents);
    });
    router.get("/heartbeat-runs/:runId/log", async (req, res) => {
        const runId = req.params.runId;
        const run = await heartbeat.getRun(runId);
        if (!run) {
            res.status(404).json({ error: "Heartbeat run not found" });
            return;
        }
        assertCompanyAccess(req, run.companyId);
        const offset = Number(req.query.offset ?? 0);
        const limitBytes = Number(req.query.limitBytes ?? 256000);
        const result = await heartbeat.readLog(runId, {
            offset: Number.isFinite(offset) ? offset : 0,
            limitBytes: Number.isFinite(limitBytes) ? limitBytes : 256000,
        });
        res.json(result);
    });
    router.get("/heartbeat-runs/:runId/workspace-operations", async (req, res) => {
        const runId = req.params.runId;
        const run = await heartbeat.getRun(runId);
        if (!run) {
            res.status(404).json({ error: "Heartbeat run not found" });
            return;
        }
        assertCompanyAccess(req, run.companyId);
        const context = asRecord(run.contextSnapshot);
        const executionWorkspaceId = asNonEmptyString(context?.executionWorkspaceId);
        const operations = await workspaceOperations.listForRun(runId, executionWorkspaceId);
        res.json(redactCurrentUserValue(operations, await getCurrentUserRedactionOptions()));
    });
    router.get("/workspace-operations/:operationId/log", async (req, res) => {
        const operationId = req.params.operationId;
        const operation = await workspaceOperations.getById(operationId);
        if (!operation) {
            res.status(404).json({ error: "Workspace operation not found" });
            return;
        }
        assertCompanyAccess(req, operation.companyId);
        const offset = Number(req.query.offset ?? 0);
        const limitBytes = Number(req.query.limitBytes ?? 256000);
        const result = await workspaceOperations.readLog(operationId, {
            offset: Number.isFinite(offset) ? offset : 0,
            limitBytes: Number.isFinite(limitBytes) ? limitBytes : 256000,
        });
        res.json(result);
    });
    router.get("/issues/:issueId/live-runs", async (req, res) => {
        const rawId = req.params.issueId;
        const issueSvc = issueService(db);
        const isIdentifier = /^[A-Z]+-\d+$/i.test(rawId);
        const issue = isIdentifier ? await issueSvc.getByIdentifier(rawId) : await issueSvc.getById(rawId);
        if (!issue) {
            res.status(404).json({ error: "Issue not found" });
            return;
        }
        assertCompanyAccess(req, issue.companyId);
        const liveRuns = await db
            .select({
            id: heartbeatRuns.id,
            status: heartbeatRuns.status,
            invocationSource: heartbeatRuns.invocationSource,
            triggerDetail: heartbeatRuns.triggerDetail,
            startedAt: heartbeatRuns.startedAt,
            finishedAt: heartbeatRuns.finishedAt,
            createdAt: heartbeatRuns.createdAt,
            agentId: heartbeatRuns.agentId,
            agentName: agentsTable.name,
            adapterType: agentsTable.adapterType,
        })
            .from(heartbeatRuns)
            .innerJoin(agentsTable, eq(heartbeatRuns.agentId, agentsTable.id))
            .where(and(eq(heartbeatRuns.companyId, issue.companyId), inArray(heartbeatRuns.status, ["queued", "running"]), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issue.id}`))
            .orderBy(desc(heartbeatRuns.createdAt));
        res.json(liveRuns);
    });
    router.get("/issues/:issueId/active-run", async (req, res) => {
        const rawId = req.params.issueId;
        const issueSvc = issueService(db);
        const isIdentifier = /^[A-Z]+-\d+$/i.test(rawId);
        const issue = isIdentifier ? await issueSvc.getByIdentifier(rawId) : await issueSvc.getById(rawId);
        if (!issue) {
            res.status(404).json({ error: "Issue not found" });
            return;
        }
        assertCompanyAccess(req, issue.companyId);
        let run = issue.executionRunId ? await heartbeat.getRun(issue.executionRunId) : null;
        if (run && run.status !== "queued" && run.status !== "running") {
            run = null;
        }
        if (!run && issue.assigneeAgentId && issue.status === "in_progress") {
            const candidateRun = await heartbeat.getActiveRunForAgent(issue.assigneeAgentId);
            const candidateContext = asRecord(candidateRun?.contextSnapshot);
            const candidateIssueId = asNonEmptyString(candidateContext?.issueId);
            if (candidateRun && candidateIssueId === issue.id) {
                run = candidateRun;
            }
        }
        if (!run) {
            res.json(null);
            return;
        }
        const agent = await svc.getById(run.agentId);
        if (!agent) {
            res.json(null);
            return;
        }
        res.json({
            ...redactCurrentUserValue(run, await getCurrentUserRedactionOptions()),
            agentId: agent.id,
            agentName: agent.name,
            adapterType: agent.adapterType,
        });
    });
    return router;
}
//# sourceMappingURL=agents.js.map