import { useEffect, useState, type ReactNode } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertTriangle, FlaskConical, Lock, Play } from "lucide-react"; import type { InstanceExperimentalSettings, InstanceExperimentalSettingsWithManaged, InstanceFeatureKey, ManagedSettingMetadata, PatchInstanceExperimentalSettings, } from "@paperclipai/shared"; import { experimentalSettingKey } from "@paperclipai/shared"; import { instanceSettingsApi } from "@/api/instanceSettings"; import { useHiddenSettings } from "@/hooks/useHiddenSettings"; import { getWorktreeInstanceId, isWorktreeRuntime } from "../lib/worktree-branding"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { ToggleSwitch } from "@/components/ui/toggle-switch"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Link } from "@/lib/router"; import { CoordinatorWorkspaceSetting } from "@/components/CoordinatorWorkspaceSetting"; type WorktreeRunExecutionDisplayState = | { kind: "off" } | { kind: "armed"; activatedAt: string } | { kind: "fail_closed"; reason: "missing_cutoff" | "missing_instance_id" | "instance_mismatch" }; /** * Mirror of the server's `resolveWorktreeRunExecutionActivation` fail-closed * ladder (server/src/services/instance-settings.ts) so the card never claims a * copied/legacy row is arming execution. The derived fields are display-only — * the PATCH the toggle sends still writes just the boolean. */ function resolveWorktreeRunExecutionDisplayState( settings: | Pick< InstanceExperimentalSettings, | "enableWorktreeRunExecution" | "worktreeRunExecutionActivatedAt" | "worktreeRunExecutionActivationInstanceId" > | undefined, currentInstanceId: string | null, ): WorktreeRunExecutionDisplayState { if (settings?.enableWorktreeRunExecution !== true) return { kind: "off" }; if (!settings.worktreeRunExecutionActivatedAt) return { kind: "fail_closed", reason: "missing_cutoff" }; if (!currentInstanceId) return { kind: "fail_closed", reason: "missing_instance_id" }; if (settings.worktreeRunExecutionActivationInstanceId !== currentInstanceId) { return { kind: "fail_closed", reason: "instance_mismatch" }; } return { kind: "armed", activatedAt: settings.worktreeRunExecutionActivatedAt }; } function formatActivationTimestamp(iso: string): string { const parsed = new Date(iso); if (Number.isNaN(parsed.getTime())) return iso; return parsed.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }); } // PAP-11233: keep Conference Room code intact, but hide the user-facing opt-in for now. const SHOW_CONFERENCE_ROOM_EXPERIMENTAL_SETTING = false; function ManagedByCloudBadge() { return ( ); } function ExperimentalToggleCard({ title, description, footnote, checked, onCheckedChange, disabled, settingKey, managed, ariaLabel, }: { title: string; description: string; footnote?: ReactNode; checked: boolean; onCheckedChange: (checked: boolean) => void; disabled: boolean; /** Flag key backing this card; operator-hidden keys render nothing. */ settingKey: InstanceFeatureKey; managed?: ManagedSettingMetadata; ariaLabel: string; }) { const { hidden: hiddenSettings } = useHiddenSettings(); const isManaged = managed?.managed === true; if (hiddenSettings.has(experimentalSettingKey(settingKey))) return null; return (

{title}

{isManaged ? : null}

{description}

{footnote ?

{footnote}

: null}
{ if (isManaged) return; onCheckedChange(next); }} disabled={disabled || isManaged} aria-label={ariaLabel} />
); } export function InstanceExperimentalSettings() { const { setBreadcrumbs } = useBreadcrumbs(); const queryClient = useQueryClient(); const { hidden: hiddenSettings } = useHiddenSettings(); const [actionError, setActionError] = useState(null); useEffect(() => { setBreadcrumbs([ { label: "Settings", href: "/company/settings" }, { label: "Experimental" }, ]); }, [setBreadcrumbs]); const experimentalQuery = useQuery({ queryKey: queryKeys.instance.experimentalSettings, queryFn: () => instanceSettingsApi.getExperimental(), }); const toggleMutation = useMutation< InstanceExperimentalSettingsWithManaged, Error, PatchInstanceExperimentalSettings, { previousSettings?: InstanceExperimentalSettingsWithManaged } >({ mutationFn: async (patch: PatchInstanceExperimentalSettings) => instanceSettingsApi.updateExperimental(patch), onMutate: async (patch) => { await queryClient.cancelQueries({ queryKey: queryKeys.instance.experimentalSettings }); const previousSettings = queryClient.getQueryData( queryKeys.instance.experimentalSettings, ); if (previousSettings) { queryClient.setQueryData( queryKeys.instance.experimentalSettings, { ...previousSettings, ...patch }, ); } return { previousSettings }; }, onSuccess: async (updatedSettings) => { setActionError(null); queryClient.setQueryData(queryKeys.instance.experimentalSettings, updatedSettings); await Promise.all([ queryClient.invalidateQueries({ queryKey: queryKeys.instance.experimentalSettings }), queryClient.invalidateQueries({ queryKey: queryKeys.adapters.all }), queryClient.invalidateQueries({ queryKey: ["built-in-agents"] }), queryClient.invalidateQueries({ queryKey: queryKeys.health }), queryClient.invalidateQueries({ queryKey: ["apps"] }), ]); }, onError: (error, _patch, context) => { if (context?.previousSettings) { queryClient.setQueryData(queryKeys.instance.experimentalSettings, context.previousSettings); } setActionError(error instanceof Error ? error.message : "Failed to update experimental settings."); }, }); if (experimentalQuery.isLoading) { return (
Loading experimental settings...
); } if (experimentalQuery.error) { return (
{experimentalQuery.error instanceof Error ? experimentalQuery.error.message : "Failed to load experimental settings."}
); } const inWorktree = isWorktreeRuntime(); // Present only on cloud-managed instances: keys the managed overlay controls // render locked with the "Managed by Paperclip Cloud" badge. Self-hosted // responses carry no `managedKeys`, so every card stays editable. const managedKeys = experimentalQuery.data?.managedKeys ?? {}; const enableWorktreeRunExecution = experimentalQuery.data?.enableWorktreeRunExecution === true; const worktreeRunExecutionManaged = managedKeys.enableWorktreeRunExecution?.managed === true; const worktreeRunExecutionState = resolveWorktreeRunExecutionDisplayState( experimentalQuery.data, getWorktreeInstanceId(), ); const enableEnvironments = experimentalQuery.data?.enableEnvironments === true; const enableNativeRunner = experimentalQuery.data?.enableNativeRunner === true; const enableChatConnectors = experimentalQuery.data?.enableChatConnectors === true; const enableManagedSandboxOnly = experimentalQuery.data?.enableManagedSandboxOnly === true; const enableIsolatedWorkspaces = experimentalQuery.data?.enableIsolatedWorkspaces === true; const enableIsolatedWorkspacesByDefault = experimentalQuery.data?.enableIsolatedWorkspacesByDefault === true; // Streamlined left navigation is now the standard sidebar (PAP-12472); the // experimental opt-out was retired, so it no longer surfaces a toggle here. const enableStreamlinedUi = experimentalQuery.data?.enableStreamlinedUi !== false; const enableConferenceRoomChat = experimentalQuery.data?.enableConferenceRoomChat === true; const enableClassicTaskInterface = experimentalQuery.data?.enableClassicTaskInterface === true; const enableIssuePlanDecompositions = experimentalQuery.data?.enableIssuePlanDecompositions === true; const enableExperimentalFileViewer = experimentalQuery.data?.enableExperimentalFileViewer === true; const enableExternalObjects = experimentalQuery.data?.enableExternalObjects === true; const enableBuiltInAgents = experimentalQuery.data?.enableBuiltInAgents === true; const enableBetaSkills = experimentalQuery.data?.enableBetaSkills === true; const enableSummaries = experimentalQuery.data?.enableSummaries === true; const enableStatusCards = experimentalQuery.data?.enableStatusCards === true; const summariesManaged = managedKeys.enableSummaries?.managed === true; const statusCardsManaged = managedKeys.enableStatusCards?.managed === true; const statusCardsBlockedByManagedSummaries = summariesManaged && !enableSummaries; const summariesRequiredByManagedStatusCards = statusCardsManaged && enableStatusCards; const enableDecisions = experimentalQuery.data?.enableDecisions === true; const enableGoalsSidebarLink = experimentalQuery.data?.enableGoalsSidebarLink === true; const enableCases = experimentalQuery.data?.enableCases === true; const enableServerInfoDebugView = experimentalQuery.data?.enableServerInfoDebugView === true; const enablePaperclipDeveloperMode = experimentalQuery.data?.enablePaperclipDeveloperMode === true; const enableSimplifiedEnglishInteractions = experimentalQuery.data?.enableSimplifiedEnglishInteractions === true; const enableFirstTaskPlanProposal = experimentalQuery.data?.enableFirstTaskPlanProposal === true; const enableSmokeLab = experimentalQuery.data?.enableSmokeLab === true; const autoRestartDevServerWhenIdle = experimentalQuery.data?.autoRestartDevServerWhenIdle === true; const isVisible = (key: InstanceFeatureKey) => !hiddenSettings.has(experimentalSettingKey(key)); const showWorktreeRunExecution = inWorktree && isVisible("enableWorktreeRunExecution"); const showDeveloperSection = showWorktreeRunExecution || ([ "autoRestartDevServerWhenIdle", "enableManagedSandboxOnly", "enablePaperclipDeveloperMode", "enableServerInfoDebugView", "enableSmokeLab", "enableIssuePlanDecompositions", ] satisfies InstanceFeatureKey[]).some(isVisible); const showLegacySection = isVisible("enableClassicTaskInterface") || isVisible("enableGoalsSidebarLink"); return (

Experimental

Opt into features that are still being evaluated before they become default behavior.

Experimental features may break at any time.

These features are opt-in and come with no compatibility guarantees. They may change, break, or be removed without notice. Avoid relying on them for critical or production workflows.

{actionError && (
{actionError}
)}

Experimental features

Optional product features that are still being evaluated.

toggleMutation.mutate({ enableAgentChat: checked })} disabled={toggleMutation.isPending} settingKey="enableAgentChat" managed={managedKeys.enableAgentChat} ariaLabel="Toggle agent chat experimental setting" /> toggleMutation.mutate({ enableArtifactsV1: checked })} disabled={toggleMutation.isPending} settingKey="enableArtifactsV1" managed={managedKeys.enableArtifactsV1} ariaLabel="Toggle artifacts V1 experimental setting" /> Requires an authenticated instance with a configured public URL. Takes effect immediately. Turning this off blocks assistant calls and event delivery; work already delegated continues.{experimentalQuery.data?.enablePublicMcp && <> Set up an assistant connection}} checked={experimentalQuery.data?.enablePublicMcp === true} onCheckedChange={(checked) => toggleMutation.mutate({ enablePublicMcp: checked })} disabled={toggleMutation.isPending} settingKey="enablePublicMcp" managed={managedKeys.enablePublicMcp} ariaLabel="Toggle assistant connections experimental setting" /> toggleMutation.mutate({ enableBetaSkills: checked })} disabled={toggleMutation.isPending} settingKey="enableBetaSkills" managed={managedKeys.enableBetaSkills} ariaLabel="Toggle beta skills experimental setting" /> toggleMutation.mutate({ enableBuiltInAgents: checked })} disabled={toggleMutation.isPending} settingKey="enableBuiltInAgents" managed={managedKeys.enableBuiltInAgents} ariaLabel="Toggle built-in agents experimental setting" /> toggleMutation.mutate({ enableCases: checked })} disabled={toggleMutation.isPending} settingKey="enableCases" managed={managedKeys.enableCases} ariaLabel="Toggle cases experimental setting" /> toggleMutation.mutate({ enableChatConnectors: checked })} disabled={toggleMutation.isPending} settingKey="enableChatConnectors" managed={managedKeys.enableChatConnectors} ariaLabel="Toggle chat connectors experimental setting" /> toggleMutation.mutate({ enableCombinedInboxTasks: checked })} disabled={toggleMutation.isPending} settingKey="enableCombinedInboxTasks" managed={managedKeys.enableCombinedInboxTasks} ariaLabel="Toggle combined inbox and task list experimental setting" /> {SHOW_CONFERENCE_ROOM_EXPERIMENTAL_SETTING ? ( toggleMutation.mutate({ enableConferenceRoomChat: checked })} disabled={toggleMutation.isPending} settingKey="enableConferenceRoomChat" managed={managedKeys.enableConferenceRoomChat} ariaLabel="Toggle conference room chat experimental setting" /> ) : null} toggleMutation.mutate({ enableDecisions: checked })} disabled={toggleMutation.isPending} settingKey="enableDecisions" managed={managedKeys.enableDecisions} ariaLabel="Toggle decisions experimental setting" /> toggleMutation.mutate({ enableEnvironments: checked })} disabled={toggleMutation.isPending} settingKey="enableEnvironments" managed={managedKeys.enableEnvironments} ariaLabel="Toggle environments experimental setting" /> toggleMutation.mutate({ enableExternalObjects: checked })} disabled={toggleMutation.isPending} settingKey="enableExternalObjects" managed={managedKeys.enableExternalObjects} ariaLabel="Toggle external objects experimental setting" /> toggleMutation.mutate({ enableIsolatedWorkspaces: checked })} disabled={toggleMutation.isPending} settingKey="enableIsolatedWorkspaces" managed={managedKeys.enableIsolatedWorkspaces} ariaLabel="Toggle isolated workspaces experimental setting" /> toggleMutation.mutate({ enableFastResponses: checked })} disabled={toggleMutation.isPending} settingKey="enableFastResponses" managed={managedKeys.enableFastResponses} ariaLabel="Toggle fast responses experimental setting" /> toggleMutation.mutate({ enableExperimentalFileViewer: checked })} disabled={toggleMutation.isPending} settingKey="enableExperimentalFileViewer" managed={managedKeys.enableExperimentalFileViewer} ariaLabel="Toggle experimental file viewer setting" /> toggleMutation.mutate({ enableFirstTaskPlanProposal: checked }) } disabled={toggleMutation.isPending} settingKey="enableFirstTaskPlanProposal" managed={managedKeys.enableFirstTaskPlanProposal} ariaLabel="Toggle first task plan proposal experimental setting" /> toggleMutation.mutate({ enableGitHubReviewBots: checked })} disabled={toggleMutation.isPending} settingKey="enableGitHubReviewBots" managed={managedKeys.enableGitHubReviewBots} ariaLabel="Toggle GitHub review bots experimental setting" /> toggleMutation.mutate({ enableGrokBot: checked })} disabled={toggleMutation.isPending} settingKey="enableGrokBot" managed={managedKeys.enableGrokBot} ariaLabel="Toggle Grok Bot experimental setting" /> toggleMutation.mutate({ enableMemoryConnectors: checked })} disabled={toggleMutation.isPending} settingKey="enableMemoryConnectors" managed={managedKeys.enableMemoryConnectors} ariaLabel="Toggle memory connectors experimental setting" /> toggleMutation.mutate({ enableMuse: checked })} disabled={toggleMutation.isPending} settingKey="enableMuse" managed={managedKeys.enableMuse} ariaLabel="Toggle Muse experimental setting" /> toggleMutation.mutate({ enableOpenAiDot: checked })} disabled={toggleMutation.isPending} settingKey="enableOpenAiDot" managed={managedKeys.enableOpenAiDot} ariaLabel="Toggle OpenAI Dot experimental setting" /> toggleMutation.mutate({ enableNativeRunner: checked }) } disabled={toggleMutation.isPending} settingKey="enableNativeRunner" managed={managedKeys.enableNativeRunner} ariaLabel="Toggle Paperclip Runner experimental setting" /> toggleMutation.mutate({ enableSimplifiedEnglishInteractions: checked }) } disabled={toggleMutation.isPending} settingKey="enableSimplifiedEnglishInteractions" managed={managedKeys.enableSimplifiedEnglishInteractions} ariaLabel="Toggle simplified english interactions experimental setting" /> toggleMutation.mutate( checked ? { enableSummaries: true, enableStatusCards: true } : { enableStatusCards: false }, ) } disabled={toggleMutation.isPending || statusCardsBlockedByManagedSummaries} settingKey="enableStatusCards" managed={managedKeys.enableStatusCards} ariaLabel="Toggle status cards experimental setting" /> toggleMutation.mutate({ enableStreamlinedUi: checked })} disabled={toggleMutation.isPending} settingKey="enableStreamlinedUi" managed={managedKeys.enableStreamlinedUi} ariaLabel="Toggle Streamlined UI experimental setting" /> toggleMutation.mutate( checked || !enableStatusCards ? { enableSummaries: checked } : { enableSummaries: false, enableStatusCards: false }, ) } disabled={toggleMutation.isPending || summariesRequiredByManagedStatusCards} settingKey="enableSummaries" managed={managedKeys.enableSummaries} ariaLabel="Toggle summaries experimental setting" /> {enableIsolatedWorkspaces && ( toggleMutation.mutate({ enableIsolatedWorkspacesByDefault: checked }) } disabled={toggleMutation.isPending} settingKey="enableIsolatedWorkspacesByDefault" managed={managedKeys.enableIsolatedWorkspacesByDefault} ariaLabel="Toggle isolated workspaces by default experimental setting" /> )}
{showDeveloperSection ? (

Paperclip Developer Mode

Internal tools for developing, testing, and debugging Paperclip.

toggleMutation.mutate({ autoRestartDevServerWhenIdle: checked }) } disabled={toggleMutation.isPending} settingKey="autoRestartDevServerWhenIdle" managed={managedKeys.autoRestartDevServerWhenIdle} ariaLabel="Toggle guarded dev-server auto-restart" /> toggleMutation.mutate({ enableManagedSandboxOnly: checked }) } disabled={toggleMutation.isPending} settingKey="enableManagedSandboxOnly" managed={managedKeys.enableManagedSandboxOnly} ariaLabel="Toggle managed environment only experimental setting" /> toggleMutation.mutate({ enablePaperclipDeveloperMode: checked }) } disabled={toggleMutation.isPending} settingKey="enablePaperclipDeveloperMode" managed={managedKeys.enablePaperclipDeveloperMode} ariaLabel="Toggle Paperclip developer mode experimental setting" /> {showWorktreeRunExecution ? (

Run tasks in this worktree

{worktreeRunExecutionManaged ? : null}

This is an isolated git-worktree preview instance. Turn this on to let the scheduler execute runs here. Only tasks created after enabling will run automatically — copied/pre-existing tasks stay parked. Toggling off and on resets the cutoff.

{ if (worktreeRunExecutionManaged) return; toggleMutation.mutate({ enableWorktreeRunExecution: checked }); }} disabled={toggleMutation.isPending || worktreeRunExecutionManaged} aria-label="Toggle worktree run execution setting" />
{worktreeRunExecutionState.kind === "armed" ? (
Running tasks created after{" "} {formatActivationTimestamp(worktreeRunExecutionState.activatedAt)} .
) : null} {worktreeRunExecutionState.kind === "fail_closed" ? (

Execution is suppressed — effectively off.

{worktreeRunExecutionState.reason === "instance_mismatch" ? "This setting was armed in a different instance and copied here, so no tasks run automatically." : "This setting is missing its activation cutoff, so no tasks run automatically."}{" "} Toggle it off and back on to arm execution for tasks created here.

) : null}
) : null} toggleMutation.mutate({ enableServerInfoDebugView: checked }) } disabled={toggleMutation.isPending} settingKey="enableServerInfoDebugView" managed={managedKeys.enableServerInfoDebugView} ariaLabel="Toggle server info debug view experimental setting" /> toggleMutation.mutate({ enableSmokeLab: checked })} disabled={toggleMutation.isPending} settingKey="enableSmokeLab" managed={managedKeys.enableSmokeLab} ariaLabel="Toggle smoke lab experimental setting" /> toggleMutation.mutate({ enableIssuePlanDecompositions: checked }) } disabled={toggleMutation.isPending} settingKey="enableIssuePlanDecompositions" managed={managedKeys.enableIssuePlanDecompositions} ariaLabel="Toggle task plan decomposition panel experimental setting" />
) : null} {showLegacySection ? (

Legacy

These features are going to be removed.

toggleMutation.mutate({ enableClassicTaskInterface: checked }) } disabled={toggleMutation.isPending} settingKey="enableClassicTaskInterface" managed={managedKeys.enableClassicTaskInterface} ariaLabel="Toggle classic task interface experimental setting" /> toggleMutation.mutate({ enableGoalsSidebarLink: checked }) } disabled={toggleMutation.isPending} settingKey="enableGoalsSidebarLink" managed={managedKeys.enableGoalsSidebarLink} ariaLabel="Toggle goals sidebar link experimental setting" />
) : null}
); }