--- name: iblai-vibe-agent-safety description: Add the agent Safety tab (the moderation check on user messages, the safety check on agent replies, the reply each one sends, and a host-provided list of flagged prompts) to your Next.js app. Use when the user mentions moderation, safety prompts, guardrails, inappropriate content, blocked messages, or flagged prompts. For the REST contract and the moderation logs see /iblai-api-agent-safety. globs: alwaysApply: false metadata: kind: ui --- # /iblai-vibe-agent-safety Add the agent **Safety tab** -- the two guardrails on an agent's chats. **Scope: per agent.** The **Moderation Prompt** checks each user message before the agent sees it; the **Safety Prompt** checks each reply before the user sees it. Each has an Active switch, and each has a response sent instead when it trips. This is one tab in the agent-settings family indexed by `/iblai-vibe-agent`; it shares the `AgentSettingsProvider` wrapper with every other tab. **Safety** -- four cards (Moderation Prompt, Safety Prompt, Moderation Response, Safety Response), each with **Edit** and **Copy**; the two prompt cards have the Active switch. **View Flagged Prompts** shows only when you pass a `FlaggedPromptsModal` and `showFlaggedPrompts` is true (Step 2). ![Safety tab -- overview](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/agents/iblai-vibe-agent-safety/iblai-vibe-agent-safety-1-overview.png) **Edit** -- a rich-text editor for the prompt or response; **Save** writes it to the agent. ![Safety tab -- Edit Safety Prompt](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/agents/iblai-vibe-agent-safety/iblai-vibe-agent-safety-2-edit-prompt.png) **Flagged prompts** -- the modal your app supplies. This one is the minimal modal from Step 2: the user messages the Moderation Prompt stopped, with who sent each, when, the message, and the reason. ![Safety tab -- flagged prompts](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/agents/iblai-vibe-agent-safety/iblai-vibe-agent-safety-3-flagged-prompts.png) > **Common setup (brand, conventions, env files, verification):** see [docs/skill-setup.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/docs/skill-setup.md). ## Prerequisites - Auth set up (`/iblai-vibe-auth`, or vibe-starter). - `AgentSettingsProvider` wraps the route (`/iblai-vibe-agent` §1). - `@iblai/iblai-js@^2.62.1` (resolves `@iblai/web-containers` 1.62.1). Check with `pnpm why @iblai/web-containers`. - A real agent UUID. Ask the user; never invent one. - To list flagged prompts, the user needs the agent's `#view_moderation_logs` grant (org admins have it). ## Step 1: Check Environment Look for `iblai.env` in the project root with `PLATFORM`, `DOMAIN`, and `TOKEN`. If it is missing, tell the user: "You need an `iblai.env` with your platform configuration. Download the template and fill in your values: `curl -o iblai.env https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/iblai.env`" ## Step 2: Mount `AgentSafetyTab` The four cards need nothing but the provider: ``. The flagged-prompts list is not part of the SDK tab -- the host injects it. These are the pieces a full mount wires: - `FlaggedPromptsModal` -- your modal; it gets `isOpen`, `onClose`, `mentorId`, `tenantKey`, `username`. - `showFlaggedPrompts` -- whether to show the button. Gate it on the `/mentors//#view_moderation_logs` grant. - `renderPromptContent` -- how the cards render prompt text (plain text by default, so line breaks and Markdown are lost). Pass a Markdown renderer to keep them. - `executeGatedAction` on the provider -- wraps the two Active switches, for a paywall or free-trial check. Prompt edits are not gated. A minimal flagged-prompts modal, built on the starter's `Dialog`: ```tsx // app/(app)/agents/[mentorId]/safety/flagged-prompts-modal.tsx "use client"; import { useGetModerationLogsQuery } from "@iblai/iblai-js/data-layer"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; type FlaggedPromptsModalProps = { isOpen: boolean; onClose: () => void; mentorId: string; tenantKey: string; username: string; }; type ModerationLog = { id: number; username: string; email: string | null; prompt: string; reason: string; target_system: string; date_created: string; }; export function FlaggedPromptsModal({ isOpen, onClose, mentorId, tenantKey, username, }: FlaggedPromptsModalProps) { const { data, isLoading } = useGetModerationLogsQuery( { org: tenantKey, // @ts-expect-error userId (the path's user) is read at runtime but missing from the type userId: username, mentor: mentorId, page: 1, pageSize: 20, ordering: "-date_created", }, { skip: !isOpen }, ); const logs = (data?.results ?? []) as unknown as ModerationLog[]; return ( !open && onClose()}> Flagged prompts {data?.count ?? 0} messages stopped by the Moderation Prompt.
{isLoading &&

Loading…

} {!isLoading && logs.length === 0 && (

No flagged prompts for this agent.

)} {logs.map((log) => (
{log.email ?? log.username} {log.target_system} · {new Date(log.date_created).toLocaleString()}

{log.prompt}

{log.reason}

))}
); } ``` `userId` is the signed-in user (the path segment). SDK 2.36.2 types the hook's argument without it, but the request needs it -- hence the `@ts-expect-error`; `username` in that type filters to the flagged user instead. `mentor` filters to this agent. Add search, a date range and paging the same way. This list holds Moderation System rows only: replies the Safety Prompt blocked are served by `…/safety-logs/` (same query and row shape), which has no data-layer hook in SDK 2.36.2 -- call it from a server route (`/iblai-api-agent-safety`). The page loads the agent's grants, gates the button, and passes the paywall gate through a nested provider: ```tsx // app/(app)/agents/[mentorId]/safety/page.tsx "use client"; import { useEffect, useState } from "react"; import { AgentSafetyTab, AgentSettingsProvider, useAgentSettings, } from "@iblai/iblai-js/web-containers/next"; import { useGetMentorSettingsQuery, useGetRbacPermissionsMutation, } from "@iblai/iblai-js/data-layer"; import { checkRbacPermission } from "@iblai/iblai-js/web-utils"; import { FlaggedPromptsModal } from "./flagged-prompts-modal"; // Module scope: the provider memoizes on this function, so an inline arrow // would rebuild the context on every render. const executeGatedAction = (action: () => unknown) => action(); export default function AgentSafetyPage() { const settings = useAgentSettings(); const { tenantKey, mentorId, username } = settings; const [grants, setGrants] = useState({}); const [getRbacPermissions] = useGetRbacPermissionsMutation(); const { data } = useGetMentorSettingsQuery({ mentor: mentorId, org: tenantKey, // @ts-expect-error userId is accepted at runtime userId: username, }); const mentorDbId = (data as { mentor_id?: number } | undefined)?.mentor_id; useEffect(() => { if (!mentorDbId) return; getRbacPermissions({ requestBody: { platform_key: tenantKey, resources: [`/mentors/${mentorDbId}/`] }, }) .unwrap() .then((permissions) => setGrants({ ...permissions })) .catch(() => {}); }, [mentorDbId, tenantKey, getRbacPermissions]); return (
(

{content}

)} />
); } ``` Replace `executeGatedAction` with your paywall or trial check (run `action` once the user may proceed), defined outside the component or memoized. The grant path uses the agent's numeric `mentor_id`, not the UUID. Without a `FlaggedPromptsModal` the button never shows, whatever `showFlaggedPrompts` says (it defaults to `true`). ## Step 3: Customize Labels (Optional) The tab renders with the default agent-facing copy (`AGENT_SAFETY_TAB_LABELS`, localized through the SDK's i18n). Pass a partial `labels` object to change any string: ```tsx import { AgentSafetyTab } from "@iblai/iblai-js/web-containers/next"; ; ``` Label groups (`SafetyTabLabels`): `header` (`title`, `description`), `prompts` -- `moderation` and `safety` (`title`, `tooltip`, `activeLabel`, `inactiveLabel`), `moderationResponse` and `safetyResponse` (`title`) -- `actions` (`edit`, `viewFlaggedPrompts`), and `toasts` (`updateSuccess`, `updateError`). ## Step 4: Use MCP Tools for Customization ``` get_component_info("AgentSafetyTab") get_component_info("AgentSettingsProvider") ``` ## `` Props Import from `@iblai/iblai-js/web-containers/next`. | Prop | Type | Required | Description | |------|------|----------|-------------| | `FlaggedPromptsModal` | `ComponentType<{ isOpen, onClose, mentorId, tenantKey, username }>` | No | Your flagged-prompts modal. Without it there is no button | | `showFlaggedPrompts` | `boolean` | No | Show **View Flagged Prompts**. Defaults to `true`; gate it on `#view_moderation_logs` | | `renderPromptContent` | `(content: string) => ReactNode` | No | Render card text (e.g. Markdown). Defaults to plain text | | `labels` | `DeepPartial` | No | Override user-visible strings | Identity, `enableRBAC` (per-field permissions on the cards) and `executeGatedAction` come from `AgentSettingsProvider`. ## Related Exports From `@iblai/iblai-js/web-containers/next`: - `AGENT_SAFETY_TAB_LABELS` -- the default label bundle. - `SafetySelectedPrompt`, `SafetyEditFormValues` -- the edit dialog's prompt and form types. - `AgentSafetyTabProps`, `SafetyTabLabels` -- types. ## How it saves Every change is one field on `PUT /api/ai-mentor/orgs/{org}/users/{username}/mentors/{uuid}/settings/`: | Control | Field | |---|---| | Moderation Prompt switch / **Save** | `enable_moderation` / `moderation_system_prompt` | | Safety Prompt switch / **Save** | `enable_safety_system` / `safety_system_prompt` | | Moderation Response **Save** | `moderation_response` | | Safety Response **Save** | `safety_response` | The flagged-prompts modal above reads `GET /api/ai-mentor/orgs/{org}/users/{username}/moderation-logs/?mentor=` (rows: `prompt`, `reason`, `target_system`, `date_created`, and who sent it). Safety Prompt catches are at `…/safety-logs/?mentor=` instead. ## Platform data | Hook | Purpose | |---|---| | `useGetMentorSettingsQuery` / `useEditMentorMutation` | Read and save the prompts, responses and switches | | `useGetModerationLogsQuery` / `useDeleteModerationLogMutation` | Flagged prompts, for your modal | | `useGetRbacPermissionsMutation` | Load the `#view_moderation_logs` grant (Step 2) | All from `@iblai/iblai-js/data-layer`. REST twin: [`/iblai-api-agent-safety`](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/agents/iblai-api-agent-safety/SKILL.md). ## Step 5: Verify Run `/iblai-vibe-ops-test` before telling the user the work is ready, and give every page, control and state this skill added its own checkpoint — one Playwright test titled ` · …` in `e2e/journeys/`, listed in `e2e/coverage.json`; `pnpm e2e:coverage` must pass: 1. `pnpm build` -- must pass with zero errors. 2. `pnpm test` -- vitest must pass. 3. Touch test `/agents//safety` in the browser as `/iblai-vibe-ops-test` says, signed in as an org admin: the four cards and **View Flagged Prompts** render; with moderation Active, send the agent a clearly harmful request in chat -- it shows up in the modal; read the console; check the phone width. ## Important Notes - **Switches save at once**: flipping Active changes the agent for everyone who chats with it. Try changes on a test agent. - **Moderation runs before the agent, safety after**: a moderated message never reaches the agent; a reply that fails the safety check is replaced by the Safety Response. - **Shared provider**: mount `AgentSettingsProvider` once at the layout level (`/iblai-vibe-agent` §1); the page above nests a second one only to add grants and the gate. - **Peer deps**: `sonner` and `@iblai/iblai-web-mentor` (`pnpm add sonner @iblai/iblai-web-mentor`). - **Brand guidelines**: [BRAND.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/BRAND.md)