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
30 changes: 25 additions & 5 deletions agentos/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { LogsTab } from "./components/LogsTab.tsx";
import { WorkspaceTab } from "./components/WorkspaceTab.tsx";
import { SchedulesTab } from "./components/SchedulesTab.tsx";
import { HomePage } from "./components/HomePage.tsx";
import { PolicyTab } from "./components/PolicyTab.tsx";
import { PoliciesPage } from "./components/PoliciesPage.tsx";

type Tab = "chat" | "schedules" | "logs";
type View = "home" | "dashboard";
type Tab = "chat" | "schedules" | "policy" | "logs";
type View = "home" | "dashboard" | "policies";

function timeAgo(iso: string | null): string {
if (!iso) return "never";
Expand Down Expand Up @@ -91,7 +93,7 @@ export default function App() {
</div>
</div>
</div>
<div className="px-2 pt-2">
<div className="px-2 pt-2 space-y-1">
<button
onClick={() => setView("home")}
className={`w-full text-left rounded-lg px-3 py-2 text-sm transition flex items-center gap-2 ${
Expand All @@ -100,6 +102,14 @@ export default function App() {
>
<span>🏠</span> Home
</button>
<button
onClick={() => setView("policies")}
className={`w-full text-left rounded-lg px-3 py-2 text-sm transition flex items-center gap-2 ${
view === "policies" ? "bg-ink-600 ring-1 ring-accent/40" : "hover:bg-ink-700"
}`}
>
<span>🛡️</span> Policies
</button>
</div>
{/* Agents folder (file-system style) */}
<div className="px-2 pt-2">
Expand Down Expand Up @@ -145,7 +155,9 @@ export default function App() {

{/* Main */}
<main className="flex-1 flex flex-col min-w-0">
{view === "home" ? (
{view === "policies" ? (
<PoliciesPage />
) : view === "home" ? (
<HomePage onLaunch={launchFromHome} onOpenDashboard={() => agents[0] && openAgent(agents[0].name)} />
) : agent ? (
<>
Expand All @@ -161,7 +173,7 @@ export default function App() {
</div>
</div>
<nav className="ml-auto flex gap-1 bg-ink-800 rounded-lg p-1">
{(["chat", "schedules", "logs"] as Tab[]).map((t) => (
{(["chat", "schedules", "policy", "logs"] as Tab[]).map((t) => (
<button
key={t}
onClick={() => setTab(t)}
Expand All @@ -185,6 +197,14 @@ export default function App() {
/>
)}
{tab === "schedules" && <SchedulesTab key={agent.name} agent={agent.name} agentLabel={agent.label} />}
{tab === "policy" && (
<PolicyTab
key={agent.name}
agent={agent.name}
agentLabel={agent.label}
onManagePolicies={() => setView("policies")}
/>
)}
{tab === "logs" && <LogsTab key={agent.name} agent={agent.name} />}
</section>
</>
Expand Down
75 changes: 75 additions & 0 deletions agentos/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,59 @@ export interface SessionDetail {
entries: TranscriptEntry[];
}

// Policies — SRS-managed. The browser only sees the bits that matter for
// the runtime (name, description, cedar/opa subsections). Everything else
// is forwarded by the server-side proxy; we don't reshape it.
export interface CedarPolicyEntry {
id: string;
name?: string;
description?: string;
policy_text: string;
enabled?: boolean;
}
export interface CedarGuardrailConfig {
enabled: boolean;
policies: CedarPolicyEntry[];
fail_open?: boolean;
}
export interface OPAManagedBinding {
policy_id: string;
hooks?: string[];
}
export interface OPAGuardrailConfig {
enabled: boolean;
source: "managed" | "external";
managed_policies: OPAManagedBinding[];
server_url?: string | null;
policy_path?: string | null;
mode?: "audit" | "enforce" | "fail_open" | "fail_closed";
timeout_seconds?: number;
}
export interface PolicyDoc {
_id: string;
name: string;
description: string;
cedar_guardrail?: CedarGuardrailConfig | null;
opa_guardrail?: OPAGuardrailConfig | null;
created_at?: string;
updated_at?: string;
[k: string]: unknown;
}
export interface AgentPolicyBinding {
_id: string; // agent name
policyId: string;
updatedAt: string;
}

export interface OPAPolicyDoc {
_id: string;
name: string;
description?: string;
rego_content: string;
created_at?: string;
updated_at?: string;
}

export interface Schedule {
_id: string;
agentName: string;
Expand Down Expand Up @@ -122,4 +175,26 @@ export const api = {
reqJSON<{ schedule: Schedule }>("PATCH", `/schedules/${encodeURIComponent(id)}`, fields).then((d) => d.schedule),
deleteSchedule: (id: string) => reqJSON<{ ok: boolean }>("DELETE", `/schedules/${encodeURIComponent(id)}`),
runScheduleNow: (id: string) => postJSON<{ ok: boolean }>(`/schedules/${encodeURIComponent(id)}/run-now`, {}),
// Policies — SRS-proxied. Server injects x-api-key.
policies: () => getJSON<{ policies: PolicyDoc[] }>("/policies").then((d) => d.policies),
policy: (id: string) => getJSON<PolicyDoc>(`/policies/${encodeURIComponent(id)}`),
createPolicy: (body: Partial<PolicyDoc>) => postJSON<PolicyDoc>("/policies", body),
updatePolicy: (id: string, body: Partial<PolicyDoc>) =>
reqJSON<{ success?: boolean } | PolicyDoc>("PUT", `/policies/${encodeURIComponent(id)}`, body),
deletePolicy: (id: string) =>
reqJSON<{ success?: boolean }>("DELETE", `/policies/${encodeURIComponent(id)}`),
// Per-agent policy binding (Mongo, ours).
getAgentPolicy: (agent: string) =>
getJSON<{ binding: AgentPolicyBinding | null }>(`/agents/${encodeURIComponent(agent)}/policy`).then((d) => d.binding),
setAgentPolicy: (agent: string, policyId: string | null) =>
reqJSON<{ binding: AgentPolicyBinding | null }>("PUT", `/agents/${encodeURIComponent(agent)}/policy`, { policy_id: policyId }).then((d) => d.binding),
// OPA rego policies (managed by SRS, referenced from RAI policies' opa_guardrail).
opaPolicies: () => getJSON<{ policies: OPAPolicyDoc[] } | OPAPolicyDoc[]>("/opa-policies").then((d) => (Array.isArray(d) ? d : d.policies)),
opaPolicy: (id: string) => getJSON<OPAPolicyDoc>(`/opa-policies/${encodeURIComponent(id)}`),
createOpaPolicy: (body: { name: string; description?: string; rego_content: string }) =>
postJSON<OPAPolicyDoc>("/opa-policies", body),
updateOpaPolicy: (id: string, body: Partial<OPAPolicyDoc>) =>
reqJSON<OPAPolicyDoc | { success?: boolean }>("PUT", `/opa-policies/${encodeURIComponent(id)}`, body),
deleteOpaPolicy: (id: string) =>
reqJSON<{ success?: boolean }>("DELETE", `/opa-policies/${encodeURIComponent(id)}`),
};
Loading
Loading