// MCP tool definitions and handlers for the venos MCP server.
import { z } from "zod";
import { scanPath, scanImage } from "./trivy.js";
// Render the control scorecard as markdown. Pure (no I/O) so it is unit-tested
// directly. An optional classFilter narrows the table to one class; the summary
// counts always reflect the full registry.
export function formatControls(data, classFilter) {
    const rows = classFilter
        ? data.controls.filter((ctrl) => ctrl.class === classFilter)
        : data.controls;
    const lines = [
        `## Venos control scorecard`,
        ``,
        `Each active control is classified per the Zero-Trust-for-AI-Agents design test: a **barrier** makes an attack impossible (structural/cryptographic) while **friction** merely raises attacker cost (detection/heuristics).`,
        ``,
        `- Barriers (impossible-class): ${data.summary.barrier}`,
        `- Friction (tedious): ${data.summary.friction}`,
        ``,
    ];
    if (rows.length === 0) {
        lines.push(classFilter ? `No ${classFilter}-class controls found.` : `No controls found.`);
        return lines.join("\n");
    }
    lines.push(`| Class | Control | Category | Enforced by |`, `|-------|---------|----------|-------------|`, ...rows.map((ctrl) => `| ${ctrl.class} | ${ctrl.name} | ${ctrl.category} | ${ctrl.enforcedBy} |`));
    return lines.join("\n");
}
// Render a budget cell as "used / ceiling" — or "used / -" when no ceiling is
// set on that dimension. Pure so it is unit-tested directly.
function budgetCell(used, ceiling, fixed = 0) {
    const u = fixed > 0 ? used.toFixed(fixed) : String(used);
    const c = ceiling === undefined ? "-" : fixed > 0 ? ceiling.toFixed(fixed) : String(ceiling);
    return `${u} / ${c}`;
}
// Render a loop's live status against its ceilings as markdown. Pure (no I/O)
// so it is unit-tested directly. `timeline` is the loop session's events; we
// report the count rather than dumping every row.
export function formatLoopStatus(data) {
    const loop = data.loop;
    const breached = loop.status === "budget_exceeded" ||
        loop.status === "terminated" ||
        loop.status === "paused";
    const lines = [
        `## Loop \`${loop.id}\` — ${loop.status}`,
        ``,
        `- **Session**: ${loop.sessionId}`,
        ...(loop.agentId ? [`- **Agent**: ${loop.agentId}`] : []),
        ...(loop.goal ? [`- **Goal**: ${loop.goal}`] : []),
        `- **Mode**: ${loop.mode}`,
        ``,
        `| Budget | Used / Ceiling |`,
        `|--------|----------------|`,
        `| Steps | ${budgetCell(loop.stepCount, loop.maxSteps)} |`,
        `| Failed steps | ${budgetCell(loop.failedSteps, loop.maxFailedSteps)} |`,
        `| Cost (USD) | ${budgetCell(loop.costUsd, loop.maxCostUsd, 4)} |`,
        ``,
        `- **Timeline steps recorded**: ${data.timeline.length}`,
    ];
    if (breached && loop.terminationReason) {
        lines.push(`- **Reason**: ${loop.terminationReason}`);
    }
    return lines.join("\n");
}
// Render a freshly opened loop, echoing back the loopId the agent must send as
// the x-venos-loop-id header on every subsequent hook call. Pure for testing.
export function formatOpenedLoop(loop) {
    return [
        `## Loop opened: \`${loop.id}\``,
        ``,
        `Echo this id as the \`x-venos-loop-id\` header on every tool call for this`,
        `run so the orchestrator steps and enforces the loop budget fail-closed.`,
        ``,
        `- **Session**: ${loop.sessionId}`,
        `- **Status**: ${loop.status}`,
        `- **Step ceiling**: ${loop.maxSteps ?? "none"}`,
        `- **Cost ceiling (USD)**: ${loop.maxCostUsd ?? "none"}`,
        `- **Failed-step ceiling**: ${loop.maxFailedSteps ?? "none"}`,
    ].join("\n");
}
const WINDOW_MS = {
    "24h": 24 * 60 * 60 * 1000,
    "7d": 7 * 24 * 60 * 60 * 1000,
    "30d": 30 * 24 * 60 * 60 * 1000,
};
// Resolve a window to the RFC3339 `from` lower bound the scoped endpoints take.
// "all" returns undefined (no bound). `now` is injected so this is pure/testable.
export function windowToFrom(window, now) {
    if (window === "all")
        return undefined;
    return new Date(now.getTime() - WINDOW_MS[window]).toISOString();
}
// Sum the per-rule would-block counts into the proxy-layer magnitude — the same
// total the dashboard hub's `sumBreakdown` computes. Pure for testing.
export function sumBreakdown(breakdown) {
    return breakdown.reduce((acc, b) => acc + b.count, 0);
}
// Render the dual-layer enforce-readiness answer as markdown: the proxy (Cedar)
// would-deny magnitude and the host (sensor) would-stop magnitude over one shared
// window, each with its top contributors. Pure (no I/O) so it is unit-tested
// directly. `topN` bounds each contributor list.
export function formatEnforceReadiness(window, breakdown, host, topN = 5) {
    const proxyTotal = sumBreakdown(breakdown);
    const hostTotal = host.total;
    const label = window === "all" ? "all time" : `the last ${window}`;
    const lines = [
        `## Enforce-readiness (${label})`,
        ``,
        `If both layers flipped from observe to enforce mode over ${label}, venos`,
        `would have stopped:`,
        ``,
        `| Layer | Would-enforce events |`,
        `|-------|----------------------|`,
        `| Proxy (Cedar policy) | ${proxyTotal.toLocaleString()} |`,
        `| Host (on-box sensor) | ${hostTotal.toLocaleString()} |`,
        ``,
    ];
    if (proxyTotal === 0 && hostTotal === 0) {
        lines.push(`Nothing would have been blocked in this window — no almost-blocked policy`, `decisions and no events from operator-blocked agents.`);
        return lines.join("\n");
    }
    const topRules = [...breakdown].sort((a, b) => b.count - a.count).slice(0, topN);
    if (topRules.length > 0) {
        lines.push(`### Top proxy rules (would-deny)`, ``, `| Rule | Count |`, `|------|-------|`, ...topRules.map((r) => `| ${r.rule} | ${r.count.toLocaleString()} |`), ``);
    }
    const topAgents = [...host.rows].sort((a, b) => b.count - a.count).slice(0, topN);
    if (topAgents.length > 0) {
        lines.push(`### Top host agents (would-stop)`, ``, `| Agent (source) | Count | Last seen |`, `|----------------|-------|-----------|`, ...topAgents.map((a) => `| ${a.source} | ${a.count.toLocaleString()} | ${a.lastSeen} |`));
    }
    return lines.join("\n");
}
// Render a cooperative step result: the updated budget plus the halt verdict the
// agent must honor. When halt is true the run has hit a ceiling and must stop.
// Pure (no I/O) so it is unit-tested directly.
export function formatStepResult(data) {
    const loop = data.loop;
    const lines = [
        data.halt
            ? `## Loop \`${loop.id}\` — HALT (${loop.status})`
            : `## Loop \`${loop.id}\` — step ${loop.stepCount} OK`,
        ``,
        `| Budget | Used / Ceiling |`,
        `|--------|----------------|`,
        `| Steps | ${budgetCell(loop.stepCount, loop.maxSteps)} |`,
        `| Failed steps | ${budgetCell(loop.failedSteps, loop.maxFailedSteps)} |`,
        `| Cost (USD) | ${budgetCell(loop.costUsd, loop.maxCostUsd, 4)} |`,
        ``,
    ];
    if (data.halt) {
        lines.push(`This loop has breached its budget — stop the run now. Do not take further steps.`);
        if (loop.terminationReason)
            lines.push(`- **Reason**: ${loop.terminationReason}`);
    }
    else {
        lines.push(`Within budget — continue.`);
    }
    return lines.join("\n");
}
// Render a cooperatively-closed loop. Pure for testing.
export function formatClosedLoop(loop) {
    const lines = [
        `## Loop \`${loop.id}\` — ${loop.status}`,
        ``,
        `- **Steps**: ${budgetCell(loop.stepCount, loop.maxSteps)}`,
        `- **Cost (USD)**: ${budgetCell(loop.costUsd, loop.maxCostUsd, 4)}`,
        ...(loop.closedAt ? [`- **Closed at**: ${loop.closedAt}`] : []),
    ];
    if (loop.terminationReason)
        lines.push(`- **Reason**: ${loop.terminationReason}`);
    return lines.join("\n");
}
export function registerTools(server, client) {
    // ── Trivy: scan a local filesystem path ─────────────────────────────────
    server.tool("trivy_scan_path", "Scan a local directory or file for vulnerabilities using Trivy. Returns a summary of CVEs grouped by severity.", {
        path: z.string().describe("Absolute or relative path to scan (e.g. /app, ./package.json)"),
        severity: z
            .enum(["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN", "CRITICAL,HIGH", "CRITICAL,HIGH,MEDIUM"])
            .optional()
            .describe("Filter by severity levels (comma-separated). Defaults to all severities."),
    }, async ({ path, severity }) => {
        const result = await scanPath(path, severity);
        if (!result.trivyInstalled) {
            return {
                content: [{ type: "text", text: `Error: ${result.errorMessage}` }],
                isError: true,
            };
        }
        if (result.errorMessage) {
            return {
                content: [{ type: "text", text: `Error scanning ${path}: ${result.errorMessage}` }],
                isError: true,
            };
        }
        const lines = [
            `## Trivy scan: \`${result.target}\``,
            ``,
            `| Severity | Count |`,
            `|----------|-------|`,
            `| Critical | ${result.critical} |`,
            `| High     | ${result.high} |`,
            `| Medium   | ${result.medium} |`,
            `| Low      | ${result.low} |`,
            `| Unknown  | ${result.unknown} |`,
            `| **Total**   | **${result.total}** |`,
            ``,
        ];
        if (result.topVulns.length > 0) {
            lines.push(`### Top vulnerabilities`);
            for (const v of result.topVulns) {
                lines.push(`- **${v.id}** (${v.severity}) in \`${v.pkg}\` — ${v.title}. Fix: ${v.fixedVersion}${v.url ? ` [→](${v.url})` : ""}`);
            }
        }
        else {
            lines.push("No vulnerabilities found.");
        }
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Trivy: scan a container image ────────────────────────────────────────
    server.tool("trivy_scan_image", "Scan a container image for OS and library vulnerabilities using Trivy.", {
        image: z.string().describe("Container image reference (e.g. nginx:latest, node:20-alpine)"),
        severity: z
            .enum(["CRITICAL", "HIGH", "MEDIUM", "LOW", "UNKNOWN", "CRITICAL,HIGH", "CRITICAL,HIGH,MEDIUM"])
            .optional()
            .describe("Filter by severity levels. Defaults to all severities."),
    }, async ({ image, severity }) => {
        const result = await scanImage(image, severity);
        if (!result.trivyInstalled) {
            return {
                content: [{ type: "text", text: `Error: ${result.errorMessage}` }],
                isError: true,
            };
        }
        if (result.errorMessage) {
            return {
                content: [{ type: "text", text: `Error scanning ${image}: ${result.errorMessage}` }],
                isError: true,
            };
        }
        const lines = [
            `## Trivy image scan: \`${result.target}\``,
            ``,
            `| Severity | Count |`,
            `|----------|-------|`,
            `| Critical | ${result.critical} |`,
            `| High     | ${result.high} |`,
            `| Medium   | ${result.medium} |`,
            `| Low      | ${result.low} |`,
            `| Unknown  | ${result.unknown} |`,
            `| **Total**   | **${result.total}** |`,
            ``,
        ];
        if (result.topVulns.length > 0) {
            lines.push(`### Top vulnerabilities`);
            for (const v of result.topVulns) {
                lines.push(`- **${v.id}** (${v.severity}) in \`${v.pkg}\` — ${v.title}. Fix: ${v.fixedVersion}${v.url ? ` [→](${v.url})` : ""}`);
            }
        }
        else {
            lines.push("No vulnerabilities found.");
        }
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: platform overview ─────────────────────────────────────────────
    server.tool("venos_overview", "Get a high-level summary of the venos platform: total events, blocked events, active agents, and open alerts.", {}, async () => {
        const ov = await client.overview();
        const text = [
            `## Venos platform overview`,
            ``,
            `- **Total events**: ${ov.totalEvents.toLocaleString()}`,
            `- **Blocked events**: ${ov.blockedEvents.toLocaleString()}`,
            `- **Active agents**: ${ov.activeAgents}`,
            `- **Open alerts**: ${ov.alertCount}`,
            ...(ov.topThreat ? [`- **Top threat**: ${ov.topThreat}`] : []),
        ].join("\n");
        return { content: [{ type: "text", text }] };
    });
    // ── Venos: list active agents ────────────────────────────────────────────
    server.tool("venos_agents", "List AI agents currently monitored by venos with their activity state and event counts.", {
        limit: z.number().int().min(1).max(100).optional().describe("Max number of agents to return (default 20)"),
    }, async ({ limit }) => {
        const agents = await client.agents(limit ?? 20);
        if (agents.length === 0) {
            return { content: [{ type: "text", text: "No agents found." }] };
        }
        const lines = [
            `## Active agents (${agents.length})`,
            ``,
            `| Agent | Source | State | Events | Last seen |`,
            `|-------|--------|-------|--------|-----------|`,
            ...agents.map((a) => `| ${a.name || a.id} | ${a.source} | ${a.state} | ${a.eventCount} | ${new Date(a.lastSeenAt).toLocaleString()} |`),
        ];
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: recent security events ────────────────────────────────────────
    server.tool("venos_events", "Fetch recent security events intercepted by venos, including blocked tool calls and policy violations.", {
        limit: z.number().int().min(1).max(100).optional().describe("Max number of events to return (default 20)"),
    }, async ({ limit }) => {
        const events = await client.recentEvents(limit ?? 20);
        if (events.length === 0) {
            return { content: [{ type: "text", text: "No recent events." }] };
        }
        const lines = [
            `## Recent events (${events.length})`,
            ``,
            `| Tool | Severity | Classification | Blocked | Time |`,
            `|------|----------|----------------|---------|------|`,
            ...events.map((e) => `| ${e.toolName} | ${e.severity} | ${e.classification} | ${e.blocked ? "Yes" : "No"} | ${new Date(e.createdAt).toLocaleString()} |`),
        ];
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: open alerts ───────────────────────────────────────────────────
    server.tool("venos_alerts", "List open security alerts from venos — anomalies, policy violations, and detection signals.", {
        limit: z.number().int().min(1).max(50).optional().describe("Max alerts to return (default 10)"),
    }, async ({ limit }) => {
        const alerts = await client.alerts(limit ?? 10);
        if (alerts.length === 0) {
            return { content: [{ type: "text", text: "No open alerts." }] };
        }
        const lines = [
            `## Open alerts (${alerts.length})`,
            ``,
            `| Title | Severity | Status | Created |`,
            `|-------|----------|--------|---------|`,
            ...alerts.map((a) => `| ${a.title} | ${a.severity} | ${a.status} | ${new Date(a.createdAt).toLocaleString()} |`),
        ];
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: enforcement mode ──────────────────────────────────────────────
    server.tool("venos_policy", "Check the current policy enforcement mode (observe / enforce / block) and block/allow counts.", {}, async () => {
        const policy = await client.enforceMode();
        const text = [
            `## Venos policy status`,
            ``,
            `- **Enforcement mode**: \`${policy.enforcementMode}\``,
            `- **Blocked requests**: ${policy.blockCount.toLocaleString()}`,
            `- **Allowed requests**: ${policy.allowCount.toLocaleString()}`,
        ].join("\n");
        return { content: [{ type: "text", text }] };
    });
    // ── Venos: dual-layer enforce-readiness ──────────────────────────────────
    server.tool("venos_enforce_readiness", "Answer 'can I flip venos to enforce mode?' by reporting, over one window, how many events EACH enforcement layer would have stopped if it were in enforce rather than observe mode: the proxy/Cedar layer (almost-blocked policy decisions that would have been denied) and the host/sensor layer (events from operator-blocked agents the on-box sensor would have terminated). Includes the top contributing rules and agents so an operator can gauge blast radius before flipping.", {
        window: z
            .enum(["24h", "7d", "30d", "all"])
            .optional()
            .describe("Time window for the magnitude (default 24h)."),
    }, async ({ window }) => {
        const w = window ?? "24h";
        const from = windowToFrom(w, new Date());
        const [breakdown, host] = await Promise.all([
            client.almostBlockedBreakdown(from),
            client.hostWouldBlock(from),
        ]);
        return {
            content: [{ type: "text", text: formatEnforceReadiness(w, breakdown, host) }],
        };
    });
    // ── Venos: classify text against the engine ──────────────────────────────
    server.tool("venos_classify", "Run a piece of text (a prompt, a shell command, a tool argument) through venos and get back the policy decision (allow/deny). Call this BEFORE executing anything destructive or sensitive (rm, curl to unknown hosts, writes outside the workspace) so the decision is logged as a Cursor event in the dashboard.", {
        text: z.string().describe("The prompt, command, or content to classify."),
    }, async ({ text }) => {
        const r = await client.classify(text);
        const lines = [
            `## Venos decision: \`${r.decision}\``,
            ``,
            `- **Source**: ${r.source}`,
            `- **Event ID**: ${r.eventId}`,
            ...(r.failedClosed ? [`- Engine failed; tenant fail-mode applied.`] : []),
        ];
        return {
            content: [{ type: "text", text: lines.join("\n") }],
            isError: r.decision === "deny",
        };
    });
    // ── Venos: cost / FinOps summary ─────────────────────────────────────────
    server.tool("venos_costs", "Show LLM cost and token usage tracked by venos, broken down by agent.", {}, async () => {
        const fin = await client.finops();
        const lines = [
            `## Venos Cost summary`,
            ``,
            `- **Total cost**: $${fin.totalCostUsd.toFixed(4)}`,
            `- **Total tokens**: ${fin.totalTokens.toLocaleString()}`,
            ``,
            `### By agent`,
            `| Agent | Cost (USD) | Tokens |`,
            `|-------|-----------|--------|`,
            ...(fin.agentBreakdown ?? []).map((b) => `| ${b.agentId} | $${b.costUsd.toFixed(4)} | ${b.tokens.toLocaleString()} |`),
        ];
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: control scorecard ─────────────────────────────────────────────
    server.tool("venos_controls", "List venos's active security controls from the control scorecard, each classified as a barrier (impossible-class: structural/cryptographic) or friction (tedious: detection/heuristics) per the Zero-Trust-for-AI-Agents design test.", {
        class: z
            .enum(["barrier", "friction"])
            .optional()
            .describe("Filter to only barrier or only friction controls. Defaults to all."),
    }, async ({ class: classFilter }) => {
        const data = await client.controls();
        return { content: [{ type: "text", text: formatControls(data, classFilter) }] };
    });
    // ── Venos: recent delegation chains ──────────────────────────────────────
    server.tool("venos_delegation", "List recent multi-agent delegation chains tracked by venos — each is one agent delegating to another (e.g. a Claude Code Task sub-agent), with the hop count and last activity. Use to see which agents are delegating work to which, and to find a chain (session) to inspect in the dashboard.", {
        limit: z.number().int().min(1).max(50).optional().describe("Max chains to return (default 20)"),
    }, async ({ limit }) => {
        const chains = await client.delegationChains(limit ?? 20);
        if (chains.length === 0) {
            return { content: [{ type: "text", text: "No delegation chains recorded yet." }] };
        }
        const lines = [
            `## Recent delegation chains (${chains.length})`,
            ``,
            `| Chain (session) | Hops | Originating user | Last activity |`,
            `|-----------------|------|------------------|---------------|`,
            ...chains.map((c) => `| ${c.correlationId} | ${c.hopCount} | ${c.originatingUserId ?? "—"} | ${new Date(c.lastChainedAt).toLocaleString()} |`),
        ];
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: ungrounded-value (fabrication) findings ───────────────────────
    server.tool("venos_grounding", "List recent ungrounded-value findings (fabrication / prompt-injection candidates) detected by venos — sensitive values an agent emitted with no read or prompt source in the same session. Each row shows the value kind, where it was emitted, whether it left the session (egress), its status, and the semantic judge's verdict (fabricated vs grounded).", {
        limit: z.number().int().min(1).max(50).optional().describe("Max findings to return (default 20)"),
    }, async ({ limit }) => {
        const findings = await client.grounding(limit ?? 20);
        if (findings.length === 0) {
            return { content: [{ type: "text", text: "✅ No ungrounded values detected." }] };
        }
        const lines = [
            `## Ungrounded values (${findings.length})`,
            ``,
            `| Value | Kind | Emitted to | Egress | Status | Judge |`,
            `|-------|------|------------|--------|--------|-------|`,
            ...findings.map((f) => `| ${f.maskedPreview} | ${f.entityKind} | ${f.emitTool} | ${f.isEgress ? "⚠️ yes" : "no"} | ${f.status} | ${f.judgeVerdict ?? "—"} |`),
        ];
        return { content: [{ type: "text", text: lines.join("\n") }] };
    });
    // ── Venos: open a governed loop ──────────────────────────────────────────
    server.tool("venos_loop_open", "Open a Closed-Loop Governance loop for an autonomous run and get back a loopId. Echo that loopId as the x-venos-loop-id header on every subsequent venos tool call so the orchestrator counts each step and HALTS the run fail-closed when it exceeds the step, cost, or failed-step ceiling. Call this once at the start of a long autonomous task. sessionId binds the loop to the run whose hook calls carry the same session.", {
        sessionId: z.string().describe("The run/session id this loop governs; every hook call for this run must carry the same session."),
        agentId: z.string().optional().describe("Optional agent identifier for the run."),
        goal: z.string().optional().describe("Optional human-readable goal for the loop."),
        maxSteps: z.number().int().min(1).optional().describe("Hard ceiling on tool-call steps before the run is halted."),
        maxCostUsd: z.number().positive().optional().describe("Hard ceiling on accumulated LLM cost (USD) before the run is halted."),
        maxFailedSteps: z.number().int().min(1).optional().describe("Hard ceiling on failed/blocked steps before the run is halted."),
    }, async ({ sessionId, agentId, goal, maxSteps, maxCostUsd, maxFailedSteps }) => {
        const loop = await client.openLoop({
            sessionId,
            ...(agentId !== undefined ? { agentId } : {}),
            ...(goal !== undefined ? { goal } : {}),
            ...(maxSteps !== undefined ? { maxSteps } : {}),
            ...(maxCostUsd !== undefined ? { maxCostUsd } : {}),
            ...(maxFailedSteps !== undefined ? { maxFailedSteps } : {}),
        });
        return { content: [{ type: "text", text: formatOpenedLoop(loop) }] };
    });
    // ── Venos: loop status vs ceilings ───────────────────────────────────────
    server.tool("venos_loop_status", "Report a governed loop's live step count, failed-step count, and cost against its ceilings, plus its status (running / budget_exceeded / terminated / paused / completed). Use to check remaining budget mid-run or to confirm why a run was halted. isError is set when the loop has breached its budget or been terminated/paused.", {
        id: z.string().describe("The loopId returned by venos_loop_open."),
    }, async ({ id }) => {
        const data = await client.loopStatus(id);
        const halted = data.loop.status === "budget_exceeded" ||
            data.loop.status === "terminated" ||
            data.loop.status === "paused";
        return {
            content: [{ type: "text", text: formatLoopStatus(data) }],
            isError: halted,
        };
    });
    // ── Venos: cooperative loop step ─────────────────────────────────────────
    server.tool("venos_loop_step", "Cooperatively record one step of a governed loop before each discover->plan->execute->verify cycle and get back the updated budget plus a halt verdict. When halt is true the loop has hit its step, cost, or failed-step ceiling and you MUST stop the run. Use this OR the x-venos-loop-id header auto-step, never both, or steps double-count. isError is set when the loop halts.", {
        id: z.string().describe("The loopId returned by venos_loop_open."),
        costUsd: z.number().min(0).optional().describe("LLM cost (USD) incurred by this step, added to the loop total."),
        failed: z.boolean().optional().describe("Set true when this step failed or was blocked, so it counts against the failed-step ceiling."),
    }, async ({ id, costUsd, failed }) => {
        const result = await client.stepLoop(id, {
            ...(costUsd !== undefined ? { costUsd } : {}),
            ...(failed !== undefined ? { failed } : {}),
        });
        return {
            content: [{ type: "text", text: formatStepResult(result) }],
            isError: result.halt,
        };
    });
    // ── Venos: cooperative loop close ────────────────────────────────────────
    server.tool("venos_loop_close", "Cooperatively close a governed loop on success, flipping it to the terminal 'completed' status (distinct from operator-terminated and budget_exceeded). Call this once when the autonomous run finishes its goal. Idempotent: closing an already-closed loop returns it unchanged.", {
        id: z.string().describe("The loopId returned by venos_loop_open."),
    }, async ({ id }) => {
        const loop = await client.completeLoop(id);
        return { content: [{ type: "text", text: formatClosedLoop(loop) }] };
    });
    // ── Venos: setup instructions per tool ──────────────────────────────────
    const SETUP_TEMPLATES = {
        cursor: `## venos setup for Cursor

**Coverage: best-effort** (no hook API — Cursor's AI calls venos_classify voluntarily)

### 1. Add the MCP server to ~/.cursor/mcp.json

\`\`\`json
{
  "mcpServers": {
    "venos": {
      "command": "npx",
      "args": ["@venos/mcp-server"],
      "env": {
        "VENOS_URL": "http://localhost:8788",
        "VENOS_ORG_ID": "your-org-id"
      }
    }
  }
}
\`\`\`

### 2. Add .cursorrules to your project

Create \`.cursorrules\` with:

> Before running shell commands that delete files, write outside the workspace,
> fetch from unrecognized hosts, install packages, or rewrite git history — call
> venos_classify with the exact command and abort if decision is deny.

### 3. Verify

In the Cursor agent panel: "list venos tools" → you should see venos_classify.
Events will appear in the venos dashboard under source: cursor.`,
        windsurf: `## venos setup for Windsurf

**Coverage: best-effort** (no hook API — Cascade calls venos_classify voluntarily)

### 1. Add the MCP server

In Windsurf → Settings → MCP Servers, add:

\`\`\`json
{
  "venos": {
    "command": "npx",
    "args": ["@venos/mcp-server"],
    "env": {
      "VENOS_URL": "http://localhost:8788",
      "VENOS_ORG_ID": "your-org-id"
    }
  }
}
\`\`\`

### 2. Add .windsurfrules to your project

Create \`.windsurfrules\` with the venos policy directive (see template in
packages/mcp-server/templates/windsurf-rules.md).

Events will appear in the venos dashboard under source: windsurf.`,
        cline: `## venos setup for Cline

**Coverage: best-effort** (no hook API — Cline's AI calls venos_classify voluntarily)

### 1. Add the MCP server in Cline settings

Add to Cline's MCP configuration:

\`\`\`json
{
  "venos": {
    "command": "npx",
    "args": ["@venos/mcp-server"],
    "env": {
      "VENOS_URL": "http://localhost:8788",
      "VENOS_ORG_ID": "your-org-id"
    }
  }
}
\`\`\`

### 2. Add .clinerules to your project

Create \`.clinerules\` with the venos policy directive (see
packages/mcp-server/templates/cline-rules.md).

Events will appear in the venos dashboard under source: vscode-mcp.`,
        vscode: `## venos setup for VS Code Copilot

**Coverage: best-effort** (no hook API — Copilot calls venos_classify voluntarily)

### 1. Add the MCP server in VS Code

In VS Code settings (settings.json):

\`\`\`json
{
  "mcp.servers": {
    "venos": {
      "command": "npx",
      "args": ["@venos/mcp-server"],
      "env": {
        "VENOS_URL": "http://localhost:8788",
        "VENOS_ORG_ID": "your-org-id"
      }
    }
  }
}
\`\`\`

### 2. Add Copilot instructions

Create \`.github/copilot-instructions.md\` with the venos policy directive (see
packages/mcp-server/templates/vscode-rules.md).

Events will appear in the venos dashboard under source: vscode-mcp.`,
        aider: `## venos setup for Aider

**Coverage: best-effort** (system-prompt injection — Aider's AI may skip the call)

### 1. Add to .aider.conf.yml

\`\`\`yaml
system-prompt: |
  Before running any shell command that deletes files, writes outside the workspace,
  fetches from an unrecognized host, installs packages, or rewrites git history,
  call venos_classify with the exact command string and abort if decision is deny.
\`\`\`

### 2. For mandatory coverage

Route Aider's LLM calls through the venos gateway (apps/mcp-proxy).
Set: OPENAI_API_BASE=http://localhost:8080/v1 (where mcp-proxy listens).

Events will appear in the venos dashboard under source: webhook.`,
    };
    server.tool("venos_setup", "Return venos setup instructions for a specific AI tool.", {
        tool: z.enum(["cursor", "windsurf", "cline", "vscode", "aider"]).describe("The AI tool to get setup instructions for."),
    }, async ({ tool }) => {
        const text = SETUP_TEMPLATES[tool] ?? `No setup instructions found for: ${tool}`;
        return { content: [{ type: "text", text }] };
    });
}
//# sourceMappingURL=tools.js.map