Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified api/vendor/agentfox-0.3.1-py3-none-any.whl
Binary file not shown.
9 changes: 7 additions & 2 deletions dashboard/app/(product)/app/agents/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,17 @@ export default async function AgentDetail({ params, searchParams }: { params: Pr
</>
}
actions={
<>
<Link className="k-btn-primary" href={`/app/agents/${encodeURIComponent(a.slug)}/protect`}>
Protect
</Link>
<form action={`/api/agents/${encodeURIComponent(a.slug)}/control`} method="POST">
<input type="hidden" name="action" value={state === "active" ? "quarantine" : "resume"} />
<button type="submit" className={state === "active" ? "k-btn-danger" : "k-btn-primary"}>
<button type="submit" className={state === "active" ? "k-btn-danger" : "k-btn"}>
{state === "active" ? "Pause agent" : "Resume agent"}
</button>
</form>
</>
}
/>
<Meta
Expand Down Expand Up @@ -437,7 +442,7 @@ function Settings({ a, state }: { a: any; state: string }) {
<form action={`/api/agents/${encodeURIComponent(a.slug)}/control`} method="POST" className="k-pills" style={{ gap: 8 }}>
<input type="hidden" name="action" value={state === "active" ? "quarantine" : "resume"} />
<input className="k-input" name="reason" placeholder="Reason (optional)" style={{ width: 240 }} />
<button type="submit" className={state === "active" ? "k-btn-danger" : "k-btn-primary"}>{state === "active" ? "Pause" : "Resume"}</button>
<button type="submit" className={state === "active" ? "k-btn-danger" : "k-btn"}>{state === "active" ? "Pause" : "Resume"}</button>
</form>
{state !== "killed" && (
<form action={`/api/agents/${encodeURIComponent(a.slug)}/control`} method="POST">
Expand Down
32 changes: 32 additions & 0 deletions dashboard/app/(product)/app/agents/[slug]/protect/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Metadata } from "next";
import { appPageMetadata } from "@/lib/site";
import { safeApi } from "@/lib/product/api";
import { Card, Empty, Header } from "@/components/kit";
import { ProtectWizard } from "@/components/product/agent/ProtectWizard";

export const metadata: Metadata = appPageMetadata("Protect agent");
export const dynamic = "force-dynamic";

export default async function Protect({ params }: { params: Promise<{ slug: string }> }) {
const { slug: raw } = await params;
const slug = decodeURIComponent(raw);
const [state, tools] = await Promise.all([
safeApi<any>(`/api/agents/${encodeURIComponent(slug)}/protection`, null),
safeApi<any>("/api/tools", { tools: [] }),
]);
return (
<>
<Header back={{ href: `/app/agents/${encodeURIComponent(slug)}`, label: slug }} title={`Protect ${slug}`} />
<Card>
{state ? (
<ProtectWizard
initial={state}
tools={(tools.tools || []).filter((t: any) => !t.key.startsWith("redteam.")).map((t: any) => ({ key: t.key, name: t.name }))}
/>
) : (
<Empty>This agent could not be loaded.</Empty>
)}
</Card>
</>
);
}
14 changes: 14 additions & 0 deletions dashboard/app/(product)/app/policies/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Detectors } from "@/components/product/policies/Detectors";
import { ImportGuard } from "@/components/product/policies/ImportGuard";
import { LibraryPacks } from "@/components/product/policies/LibraryPacks";
import { PackMode } from "@/components/product/policies/PackMode";
import { WorkspaceCode } from "@/components/product/policies/WorkspaceCode";
import { LegacyJudgmentTab, LegacyPacksTab, LegacyTuningTab } from "@/components/product/policies/legacy";
import { metricsQs, runsHref } from "@/lib/product/observe";
import { loadRules, ruleMode, type RuleInfo } from "@/lib/product/rules";
Expand All @@ -38,6 +39,7 @@ const TABS = [
{ key: "performance", label: "Performance" },
{ key: "library", label: "Library" },
{ key: "changes", label: "Changes" },
{ key: "code", label: "Code" },
{ key: "advanced", label: "Advanced" },
];

Expand Down Expand Up @@ -68,6 +70,7 @@ export default async function Policies({ searchParams }: { searchParams: Promise
{tab === "performance" && <PerformanceTab sp={sp} />}
{tab === "library" && <LibraryTab sec={sec} />}
{tab === "changes" && <ChangesTab />}
{tab === "code" && <CodeTab />}
{tab === "advanced" && <AdvancedTab sec={sec} agent={sp.agent} />}
</>
);
Expand Down Expand Up @@ -345,6 +348,17 @@ async function LibraryTab({ sec }: { sec?: string }) {
);
}

// --- Code --------------------------------------------------------------------

async function CodeTab() {
const exported = await safeApi<any>("/api/workspace/export", { source: "" });
return (
<Card>
<WorkspaceCode source={exported.source} />
</Card>
);
}

// --- Changes -----------------------------------------------------------------

async function ChangesTab() {
Expand Down
23 changes: 21 additions & 2 deletions dashboard/app/(product)/app/policies/rules/[ruleId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Metadata } from "next";
import Link from "next/link";
import { appPageMetadata } from "@/lib/site";
import { safeApi } from "@/lib/product/api";
import { post, safeApi } from "@/lib/product/api";
import { ActionPill, BarList, Card, Empty, Grid, Header, Kpi, ModePill, Pill, Tabs, ago, href, num } from "@/components/kit";
import { FilterBar } from "@/components/kit/FilterBar";
import { RunsTable } from "@/components/kit/RunsTable";
import { RuleTests } from "@/components/product/policies/RuleTests";
import { RuleTuner } from "@/components/product/policies/RuleTuner";
import { RANGE_DAYS, metricsQs, runsHref } from "@/lib/product/observe";
import { loadRules, ruleMode } from "@/lib/product/rules";
Expand All @@ -15,7 +16,8 @@ export const dynamic = "force-dynamic";

const TABS = [
{ key: "overview", label: "Overview" },
{ key: "examples", label: "Examples" },
{ key: "examples", label: "Hits" },
{ key: "tests", label: "Tests" },
{ key: "tune", label: "Tune" },
{ key: "history", label: "History" },
];
Expand Down Expand Up @@ -101,6 +103,12 @@ export default async function RulePage({ params, searchParams }: { params: Promi

{tab === "examples" && <Examples ruleId={ruleId} range={range} />}

{tab === "tests" && (
<Card>
{rule ? <Tests ruleId={ruleId} pack={rule.packs[0]?.key} agents={Object.keys(s?.agents || {})} /> : <Empty>Not in any installed pack.</Empty>}
</Card>
)}

{tab === "tune" && (
<Card title="What this rule does">
{rule ? <RuleTuner ruleId={ruleId} packs={rule.packs} exact={own ? own.kind !== "topic" : false} /> : <Empty>Not in any installed pack.</Empty>}
Expand Down Expand Up @@ -149,3 +157,14 @@ async function Examples({ ruleId, range }: { ruleId: string; range: keyof typeof
</>
);
}

async function Tests({ ruleId, pack, agents }: { ruleId: string; pack?: string; agents: string[] }) {
const [checked, all] = await Promise.all([
pack
? post<any>(`/api/rules/${encodeURIComponent(ruleId)}/examples/check`, { policy: pack }).catch(() => ({ results: [] }))
: Promise.resolve({ results: [] }),
safeApi<any>("/api/agents", { agents: [] }),
]);
const slugs = Array.from(new Set([...agents, ...(all.agents || []).filter((a: any) => a.status !== "draft").map((a: any) => a.slug)]));
return <RuleTests ruleId={ruleId} results={checked.results || []} agents={slugs} />;
}
11 changes: 11 additions & 0 deletions dashboard/app/api/agents/[slug]/protection/preview/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NextRequest } from "next/server";
import { proxyJson } from "@/lib/product/proxy";
import { protectionSetup } from "@/lib/product/protection";

export const dynamic = "force-dynamic";

/** Replay the agent's last week against a setup. Saves nothing. */
export async function POST(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return proxyJson(`/api/agents/${encodeURIComponent(slug)}/protection/preview`, "POST", protectionSetup(await req.json()));
}
12 changes: 12 additions & 0 deletions dashboard/app/api/agents/[slug]/protection/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NextRequest } from "next/server";
import { protectionSetup } from "@/lib/product/protection";
import { proxyJson } from "@/lib/product/proxy";

export const dynamic = "force-dynamic";

/** Save an agent's protections, words, topics and blocked message. */
export async function POST(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return proxyJson(`/api/agents/${encodeURIComponent(slug)}/protection`, "POST", protectionSetup(await req.json()));
}

5 changes: 4 additions & 1 deletion dashboard/app/api/policies/[key]/rules/[ruleId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ key
persist: Boolean(version.needs_simulation),
});
const simulation = sim.ok ? await sim.json() : null;
return NextResponse.json({ ...version, simulation });
// The rule's saved tests, re-checked against exactly this proposed version.
const checked = await proxyJson(`/api/rules/${encodeURIComponent(ruleId)}/examples/check`, "POST", { policy: key, body: version.body });
const tests = checked.ok ? await checked.json() : null;
return NextResponse.json({ ...version, simulation, tests });
}
9 changes: 9 additions & 0 deletions dashboard/app/api/rules/[ruleId]/examples/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { proxyJson } from "@/lib/product/proxy";

export const dynamic = "force-dynamic";

/** Delete one saved test from a rule. */
export async function DELETE(_req: Request, { params }: { params: Promise<{ ruleId: string; id: string }> }) {
const { ruleId, id } = await params;
return proxyJson(`/api/rules/${encodeURIComponent(ruleId)}/examples/${encodeURIComponent(id)}`, "DELETE");
}
28 changes: 28 additions & 0 deletions dashboard/app/api/rules/[ruleId]/examples/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NextRequest, NextResponse } from "next/server";
import { proxyJson } from "@/lib/product/proxy";

export const dynamic = "force-dynamic";

/**
* Save a test for a rule: run the text through the playground for the chosen agent
* (so it is recorded, but not counted as production traffic), then keep that decision
* with whether the rule should fire on it.
*/
export async function POST(req: NextRequest, { params }: { params: Promise<{ ruleId: string }> }) {
const { ruleId } = await params;
const { text, surface, agent, fires } = await req.json();
if (!agent || !String(text || "").trim()) return NextResponse.json({ detail: "choose an agent and enter text" }, { status: 400 });
const ran = await proxyJson(surface === "output" ? "/v1/guard/output" : "/v1/guard/input", "POST", {
agent: String(agent),
content: String(text),
surface: surface === "output" ? "output" : "input",
environment: "playground",
});
if (!ran.ok) return ran;
const { decision_id } = await ran.json();
return proxyJson(`/api/rules/${encodeURIComponent(ruleId)}/examples`, "POST", {
decision_id,
fires: Boolean(fires),
sample: String(text).slice(0, 2000),
});
}
10 changes: 10 additions & 0 deletions dashboard/app/api/workspace/apply/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextRequest } from "next/server";
import { proxyJson } from "@/lib/product/proxy";

export const dynamic = "force-dynamic";

/** Apply a workspace file. Anything it leaves out is left alone. */
export async function POST(req: NextRequest) {
const { source } = await req.json();
return proxyJson("/api/workspace/apply", "POST", { source: String(source || "") });
}
10 changes: 10 additions & 0 deletions dashboard/app/api/workspace/plan/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextRequest } from "next/server";
import { proxyJson } from "@/lib/product/proxy";

export const dynamic = "force-dynamic";

/** What applying a workspace file would change. Saves nothing. */
export async function POST(req: NextRequest) {
const { source } = await req.json();
return proxyJson("/api/workspace/plan", "POST", { source: String(source || "") });
}
9 changes: 9 additions & 0 deletions dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -3192,6 +3192,15 @@ textarea.k-input { height: auto; padding: 8px 11px; line-height: 1.5; resize: ve
.k-preview-nums { display: flex; gap: 18px; flex-wrap: wrap; font-size: var(--t-small); }
.k-preview-nums strong { font-variant-numeric: tabular-nums; }
.k-plain { margin: 0; padding-left: 18px; font-size: var(--t-small); display: flex; flex-direction: column; gap: 2px; }
.k-steps { display: flex; gap: 4px; list-style: none; margin: 0 0 6px; padding: 0; overflow-x: auto; scrollbar-width: none; }
.k-steps button { display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px 6px 6px; border: 1px solid var(--border); border-radius: var(--r-pill); background: var(--panel); color: var(--muted); font-size: var(--t-small); white-space: nowrap; cursor: pointer; }
.k-steps button:disabled { cursor: default; opacity: .5; }
.k-steps .k-step-n { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--border); color: var(--text); font-size: var(--t-micro); }
.k-steps button.active { color: var(--text); border-color: var(--text); }
.k-steps button.active .k-step-n { background: var(--text); color: var(--panel); }
.k-steps button.done .k-step-n { background: var(--viz-allowed); color: var(--panel); }
.k-form > .k-seg { align-self: flex-start; }
.k-code { margin: 0; max-height: 520px; overflow: auto; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--r-2); background: var(--panel-2); font-size: var(--t-small); line-height: 1.5; }
.k-details { margin-top: 14px; }
.k-details > summary { cursor: pointer; color: var(--muted); font-size: var(--t-small); margin-bottom: 10px; }
h2.k-section { font-size: var(--t-small); font-weight: 620; margin: 22px 0 10px; }
Expand Down
Loading
Loading