SuperAPI AI Directory

iblai-vibe-incognito

General & other

View repository

Updated 10-15 22:16

About this skill

/iblai-vibe-incognito

Add Incognito -- a conversation the platform does not save to chat history or use for memory. It is one feature across four surfaces, and this skill is the map plus the one piece no other skill mounts: the chat-header Incognito toggle (ChatPrivacyToggle) and its mid-conversation confirmation dialog.

Tier Who sets it Where Skill
Organization gate Admin Account → Advanced → Allow users to control chat privacy /iblai-vibe-account
Agent policy Agent admin Agent → Privacy → Incognito: Users Decide / Always Incognito / Never Incognito /iblai-vibe-agent-privacy
User default Each user Profile → Privacy: Normal / Anonymized / Incognito /iblai-vibe-profile
This conversation Each user The header toggle (this skill) —

Incognito used to be called Private Mode; the names in the UI, the toasts and the dialog all say Incognito now. The wire names did not change (disable_chathistory, chat_privacy_mode).

Mid-conversation — "Enable Incognito for this chat?"

Agent tier — Privacy › Incognito

User tier — Profile › Privacy

Common setup (brand, conventions, env files, verification): see docs/skill-setup.md.

How the tiers resolve

The backend answers one question per (org, user, agent, session): GET …/chat-privacy-effective/ → { mode, source, is_locked }.

  • mode is normal, anonymized or disabled (disabled = Incognito). Incognito withholds the message text: no chat-history row is written and nothing feeds memory. Each turn is still recorded against the user for usage, and the conversation's own turns are kept for 24 hours, refreshed on every turn, as the agent's context — so the agent follows the conversation. Anonymized strips the account identity from the history rows only; the per-turn usage record keeps the user and the text.
  • source says which tier won: mentor (the agent), tenant, user (profile default), session (this conversation), or default.
  • is_locked is true when the agent or the organization decided — the user cannot change it from the conversation.

Precedence, highest first:

  1. Agent — Always Incognito (disable_chathistory on the agent): every conversation is incognito; the pill is locked on.
  2. Agent — Never Incognito (disable_privacy_mode on the agent): the toggle disappears, and a user whose profile default is Incognito is saved anonymized instead.
  3. Organization gate off: no user control anywhere — no toggle, no Incognito sub-tab, no profile Privacy tab; every chat is saved (chat-privacy-effective answers mode: normal, source: tenant, is_locked: true).
  4. This session (disable_chathistory on the session): what the toggle writes.
  5. User default (chat_privacy_mode): Normal, Anonymized, or Incognito for every conversation unless a tier above overrides.
  6. Default: normal.

Prerequisites

  • Auth must be set up first (/iblai-vibe-auth)
  • MCP server + skills configured (@iblai/mcp in .mcp.json)
  • A chat surface to put the toggle on (/iblai-vibe-agent-chat) — the toggle reads the active session id and message list from the shared chat slice, so it only makes sense next to a mounted chat.
  • The organization gate on, or nothing renders (by design — see the render gate below).

Step 1: Check Environment

Before proceeding, check for an iblai.env in the project root. Look for PLATFORM, DOMAIN, and TOKEN variables. If the file does not exist or is missing these variables, 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 the toggle in the chat header

Place it in the header's right-hand cluster, next to the credit balance and the notification bell — all three are 40×40 icon buttons at rest; Incognito grows into a labelled pill when it is on.

// components/navbar/incognito-toggle.tsx
"use client";

import { ChatPrivacyToggle } from "@iblai/iblai-js/web-containers";

export function IncognitoToggle({
  tenantKey,
  username,
  mentorId,
}: {
  tenantKey: string;
  username: string;
  /** The agent of the open chat; omit off chat pages and the toggle hides. */
  mentorId?: string;
}) {
  return <ChatPrivacyToggle org={tenantKey} userId={username} mentor={mentorId} />;
}

Safe to render unconditionally: it returns null when identity is missing, when the organization gate is off, or when the agent is Never Incognito — except while the current conversation is already incognito, when the pill stays visible so the state is not lost.

Hosts on the SDK's PlatformNavbar pass the same three values as its privacyToggle config (PlatformNavbarPrivacyToggleConfig) and null off chat pages.

Step 3: Use MCP Tools for Customization

get_component_info("ChatPrivacyToggle")
get_hook_info("useChatPrivacy")
get_component_info("AgentPrivacyTab")

<ChatPrivacyToggle> Props

Import from @iblai/iblai-js/web-containers.

Prop Type Required Description
org string Yes Organization key
userId string Yes Username of the signed-in user
mentor string No Agent UUID of the open chat. Without it the toggle renders nothing (it cannot start a session)
className string No Extra classes on the trigger button

What the toggle does

A hat-and-glasses icon button; when Incognito is on it turns into a blue gradient pill labelled Incognito. What a click does depends on the state:

State Click
Normal, no user message yet Starts a fresh session with disable_chathistory: true — no dialog. Once a message is sent, that conversation stays in Incognito
Incognito, no user message yet Starts a fresh normal session — the way out of an empty incognito chat
Normal, messages sent Opens "Enable Incognito for this chat?" (below); on confirm, flips this session's disable_chathistory to true
Incognito, messages sent Flips disable_chathistory back to false on the same session — history resumes, no dialog
Locked on No action; the tooltip says why
A reply is streaming Waits ("Wait for the reply to finish before changing Incognito.")

The mid-conversation dialog

Enable Incognito for this chat? From here on, new messages in this conversation won't be saved, and the assistant stops using your earlier messages. Your earlier messages stay saved in your history. You can turn Incognito off again anytime to resume saving.

Cancel · Enable Incognito

Only the mid-conversation enable asks. Turning it off again, and either direction on an empty chat, act immediately with a toast.

Locked states and their tooltips

Why Tooltip
Agent is Always Incognito (source: mentor) "This agent always runs in Incognito — no chat history or memory is kept for conversations with it."
Profile default is Incognito (source: user) "Incognito is on for all your chats from your profile's Privacy settings. To save history, change it there, then start a new chat."
Conversation has no owner (started in Incognito, or a resume was refused) "Incognito stays on for this conversation, so nothing in it is saved. Start a new chat to save history."
Profile default is Anonymized and messages were sent "Incognito can't be turned on for this conversation. Start a new chat and turn Incognito on before sending a message."

A refused action toasts the backend's own reason when it sent one (the 403 from an agent locked while the chat was open), otherwise the generic message for that action.

Custom UI: useChatPrivacy

The toggle is a thin view over one hook, exported from @iblai/iblai-js/web-containers, for hosts that want their own control:

import { useChatPrivacy } from "@iblai/iblai-js/web-containers";

const {
  featureEnabled,       // organization gate
  privacyModeDisabled,  // agent is Never Incognito (or a tier above locked a non-incognito mode)
  effective,            // { mode, source, is_locked }
  isEffectiveReady,     // don't act before the first resolution lands
  messageCount,         // user-authored messages in the open chat
  isTurnInFlight,
  canResumeHistory,     // false once this browser knows the session has no owner
  startPrivateChat,     // new session, disable_chathistory: true
  startNormalChat,      // new session, normal
  disableChatHistory,   // (true | false) on the current session
} = useChatPrivacy({ org, userId, mentor, onActionError });

onActionError receives { action, status?, message? } — action is startPrivateChat, startNormalChat, disableChatHistory or enableChatHistory; a refused resume arrives as enableChatHistory with 400 (admin) or 404 (own user).

REST API

All under ${dmUrl} where dmUrl is NEXT_PUBLIC_API_BASE_URL. Auth: Authorization: Token <token>.

Tier Method Path Body / notes
Organization GET / PATCH /api/ai-account/orgs/{org}/users/{username}/chat-privacy-config/ { "allow_user_chat_privacy_control": boolean } — PATCH is admin-only (403 otherwise); the {username} segment is the caller, the value is organization-wide
Agent PUT /api/ai-mentor/orgs/{org}/users/{username}/mentors/{mentor_unique_id}/settings/ { "disable_chathistory": boolean, "disable_privacy_mode": boolean } — see /iblai-vibe-agent-privacy
User GET / POST /api/ai-account/orgs/{org}/users/{username}/chat-privacy-settings/ { "chat_privacy_mode": "normal" | "anonymized" | "disabled" }
Resolved GET /api/ai-account/orgs/{org}/users/{username}/chat-privacy-effective/?mentor={uuid}&session={id} { mode, source, is_locked }
Session POST /api/ai-mentor/orgs/{org}/users/{username}/sessions/{session_id}/disable-chathistory/ { "disable_chathistory": boolean }. false is refused for a session with no owner (400 admin / 404 own user)

A new session starts incognito with POST …/sessions/ and { "mentor": uuid, "disable_chathistory": true }; the backend creates that session without an owner, which is why it can never leave Incognito once it has messages.

Related Exports

From @iblai/iblai-js/web-containers:

  • ChatPrivacyToggle, ChatPrivacyToggleProps — the header toggle.
  • useChatPrivacy, ChatPrivacyAction, ChatPrivacyActionError, toChatPrivacyActionError — the hook behind it.
  • PlatformNavbar, PlatformNavbarPrivacyToggleConfig — the SDK navbar and its privacyToggle slot.
  • Profile with targetTab="privacy" — the user tier (/iblai-vibe-profile).

From @iblai/iblai-js/web-containers/next:

  • AgentPrivacyTab — the agent tier (/iblai-vibe-agent-privacy).

From @iblai/iblai-js/data-layer (chatPrivacyApiSlice):

  • useGetTenantChatPrivacyConfigQuery, useUpdateTenantChatPrivacyConfigMutation — the organization gate.
  • useGetUserChatPrivacySettingsQuery, useUpdateUserChatPrivacySettingsMutation — the user default.
  • useGetChatPrivacyEffectiveQuery — the resolved mode.
  • useUpdateSessionDisableChathistoryMutation — the session flip.
  • ChatPrivacyModeEnum, CHAT_PRIVACY_MODES, and the types ChatPrivacyMode, ChatPrivacyEffective, TenantChatPrivacyConfig, UserChatPrivacySettings, MentorChatPrivacyPublicSettings.

From @iblai/iblai-js/playwright: getChatPrivacyToggle, expectChatPrivacyState, expectChatPrivacySource, expectChatPrivacyLocked, clickChatPrivacyToggle, confirmEnableChatPrivacyMidSession, turnOffChatPrivacyMidSession, cancelEnableChatPrivacyMidSession.

SDK source: packages/web-containers/src/components/chat-privacy/chat-privacy-toggle.tsx, hooks/use-chat-privacy.ts, the organization gate row at components/profile/advanced/chat-privacy.tsx, and the data layer under packages/data-layer/src/features/chat-privacy/.

Step 4: Verify

Run /iblai-vibe-ops-test before telling the user the work is ready:

  1. pnpm build -- must pass with zero errors
  2. pnpm test -- vitest must pass
  3. Start dev server and touch test:
    pnpm dev &
    npx playwright screenshot http://localhost:3000/ /tmp/incognito.png
    

Stable selectors: chat-privacy-toggle (data-state="on" | "off", data-source, aria-pressed, aria-disabled), chat-privacy-confirm-dialog, chat-privacy-confirm-cancel, chat-privacy-confirm-action.

Important Notes

  • Redux store: Must include mentorReducer and mentorMiddleware (they register chatPrivacyApiSlice and the shared chat slice the toggle reads)
  • initializeDataLayer(): 5 args (v1.2+)
  • @reduxjs/toolkit: Deduplicated via webpack aliases in next.config.ts
  • Peer deps: sonner and @iblai/iblai-web-mentor must be installed (pnpm add sonner @iblai/iblai-web-mentor)
  • Incognito is per conversation, not per message. Turning it on mid-conversation keeps the earlier messages in history and stops saving from that point; the assistant also stops using the earlier turns. Turning it off resumes saving on the same conversation — messages sent while it was on are gone. What Incognito withholds is the message text: no history row, nothing to memory. Usage is still metered per turn, and the incognito turns stay available to the agent as context for 24 hours.
  • An empty chat started in Incognito has no owner. The backend creates it that way, so once it has messages it cannot be turned back; the toggle locks and points the user to a new chat. This browser remembers such sessions in localStorage (iblai_chat_privacy_stays_incognito_sessions).
  • Anonymized is not Incognito. A profile default of Anonymized keeps the chat-history rows but strips the account identity from them; the per-turn usage record still carries the user and the text. The pill stays off and the agent still uses prior turns. Those conversations have no owner either, so Incognito cannot be enabled on them mid-way.
  • The user-facing name is Incognito everywhere — the toggle, the agent's Privacy tab, the profile Privacy tab, the toasts and the dialog. Wire fields keep their older names: disable_chathistory, disable_privacy_mode, chat_privacy_mode: "disabled", and the mentor query parameter.
  • Brand guidelines: BRAND.md