mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 02:00:52 +01:00
Gate built-in agents behind experimental flag
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
622e0cf799
commit
e341c878d6
13 files changed
+226
-5
No files matched your search
@@ -55,6 +55,7 @@ export interface InstanceExperimentalSettings {
|
||||
enableExperimentalFileViewer: boolean;
|
||||
enableCloudSync: boolean;
|
||||
enableExternalObjects: boolean;
|
||||
enableBuiltInAgents: boolean;
|
||||
enableGoalsSidebarLink: boolean;
|
||||
enableServerInfoDebugView: boolean;
|
||||
autoRestartDevServerWhenIdle: boolean;
|
||||
|
||||
@@ -29,6 +29,12 @@ describe("instance experimental settings validators", () => {
|
||||
expect(settings.enableWorktreeRunExecution).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults built-in agents off", () => {
|
||||
const settings = instanceExperimentalSettingsSchema.parse({});
|
||||
|
||||
expect(settings.enableBuiltInAgents).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts worktree run execution patches", () => {
|
||||
expect(
|
||||
patchInstanceExperimentalSettingsSchema.parse({
|
||||
@@ -68,4 +74,14 @@ describe("instance experimental settings validators", () => {
|
||||
enableGoalsSidebarLink: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts built-in agents patches", () => {
|
||||
expect(
|
||||
patchInstanceExperimentalSettingsSchema.parse({
|
||||
enableBuiltInAgents: true,
|
||||
}),
|
||||
).toEqual({
|
||||
enableBuiltInAgents: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -49,6 +49,7 @@ export const instanceExperimentalSettingsSchema = z.object({
|
||||
enableExperimentalFileViewer: z.boolean().default(false),
|
||||
enableCloudSync: z.boolean().default(false),
|
||||
enableExternalObjects: z.boolean().default(false),
|
||||
enableBuiltInAgents: z.boolean().default(false),
|
||||
enableGoalsSidebarLink: z.boolean().default(false),
|
||||
enableServerInfoDebugView: z.boolean().default(false),
|
||||
autoRestartDevServerWhenIdle: z.boolean().default(false),
|
||||
|
||||
@@ -9,6 +9,10 @@ const mockAccessService = vi.hoisted(() => ({
|
||||
decide: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockInstanceSettingsService = vi.hoisted(() => ({
|
||||
getExperimental: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockBuiltInAgentService = vi.hoisted(() => ({
|
||||
list: vi.fn(),
|
||||
ensure: vi.fn(),
|
||||
@@ -66,6 +70,7 @@ function builtInState(overrides: Record<string, unknown> = {}) {
|
||||
function registerModuleMocks() {
|
||||
vi.doMock("../services/index.js", () => ({
|
||||
accessService: () => mockAccessService,
|
||||
instanceSettingsService: () => mockInstanceSettingsService,
|
||||
logActivity: mockLogActivity,
|
||||
}));
|
||||
vi.doMock("../services/built-in-agents.js", () => ({
|
||||
@@ -95,6 +100,7 @@ describe("built-in agent routes", () => {
|
||||
registerModuleMocks();
|
||||
vi.clearAllMocks();
|
||||
mockAccessService.decide.mockResolvedValue(allowDecision());
|
||||
mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableBuiltInAgents: true });
|
||||
mockBuiltInAgentService.list.mockResolvedValue([builtInState()]);
|
||||
mockBuiltInAgentService.ensure.mockResolvedValue(builtInState());
|
||||
mockBuiltInAgentService.provision.mockResolvedValue({ state: builtInState(), approval: null });
|
||||
@@ -133,6 +139,23 @@ describe("built-in agent routes", () => {
|
||||
expect(mockBuiltInAgentService.list).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns 404 and does not load built-in state when the experimental flag is disabled", async () => {
|
||||
mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableBuiltInAgents: false });
|
||||
const app = await createApp({
|
||||
type: "board",
|
||||
userId: "board-user",
|
||||
companyIds: [companyId],
|
||||
source: "session",
|
||||
isInstanceAdmin: false,
|
||||
});
|
||||
|
||||
const res = await request(app).get(`/api/companies/${companyId}/built-in-agents`);
|
||||
|
||||
expect(res.status, JSON.stringify(res.body)).toBe(404);
|
||||
expect(res.body.error).toContain("Built-in agents are not enabled");
|
||||
expect(mockBuiltInAgentService.list).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("provisions through the agents:create gate and passes optional adapter overrides", async () => {
|
||||
const app = await createApp({
|
||||
type: "board",
|
||||
|
||||
@@ -82,6 +82,7 @@ describe("instance settings routes", () => {
|
||||
enableExperimentalFileViewer: false,
|
||||
enableCloudSync: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: false,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
@@ -105,6 +106,7 @@ describe("instance settings routes", () => {
|
||||
enableTaskWatchdogs: false,
|
||||
enableCloudSync: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: false,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
@@ -127,6 +129,7 @@ describe("instance settings routes", () => {
|
||||
enableExperimentalFileViewer: true,
|
||||
enableCloudSync: true,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: false,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
@@ -155,6 +158,7 @@ describe("instance settings routes", () => {
|
||||
enableTaskWatchdogs: true,
|
||||
enableCloudSync: true,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: true,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: true,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
@@ -211,6 +215,7 @@ describe("instance settings routes", () => {
|
||||
enableTaskWatchdogs: false,
|
||||
enableCloudSync: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: false,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
@@ -309,6 +314,24 @@ describe("instance settings routes", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("allows local board users to update built-in agents", async () => {
|
||||
const app = await createApp({
|
||||
type: "board",
|
||||
userId: "local-board",
|
||||
source: "local_implicit",
|
||||
isInstanceAdmin: true,
|
||||
});
|
||||
|
||||
await request(app)
|
||||
.patch("/api/instance/settings/experimental")
|
||||
.send({ enableBuiltInAgents: true })
|
||||
.expect(200);
|
||||
|
||||
expect(mockInstanceSettingsService.updateExperimental).toHaveBeenCalledWith({
|
||||
enableBuiltInAgents: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("allows local board users to update the goals sidebar link", async () => {
|
||||
const app = await createApp({
|
||||
type: "board",
|
||||
|
||||
@@ -10,6 +10,7 @@ describe("instance settings service", () => {
|
||||
enableExperimentalFileViewer: true,
|
||||
enableTaskWatchdogs: true,
|
||||
enableCloudSync: true,
|
||||
enableBuiltInAgents: true,
|
||||
enableGoalsSidebarLink: true,
|
||||
enableServerInfoDebugView: true,
|
||||
autoRestartDevServerWhenIdle: true,
|
||||
@@ -28,6 +29,7 @@ describe("instance settings service", () => {
|
||||
enableExperimentalFileViewer: true,
|
||||
enableTaskWatchdogs: true,
|
||||
enableCloudSync: true,
|
||||
enableBuiltInAgents: true,
|
||||
enableGoalsSidebarLink: true,
|
||||
enableServerInfoDebugView: true,
|
||||
autoRestartDevServerWhenIdle: true,
|
||||
@@ -98,4 +100,10 @@ describe("instance settings service", () => {
|
||||
normalizeExperimentalSettings({ enableConferenceRoomChat: "yes" }).enableConferenceRoomChat,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults enableBuiltInAgents to false for empty and legacy stored settings", () => {
|
||||
expect(normalizeExperimentalSettings(undefined).enableBuiltInAgents).toBe(false);
|
||||
expect(normalizeExperimentalSettings({}).enableBuiltInAgents).toBe(false);
|
||||
expect(normalizeExperimentalSettings({ enableExternalObjects: true }).enableBuiltInAgents).toBe(false);
|
||||
});
|
||||
});
|
||||
+6
-1
@@ -780,8 +780,13 @@ export async function startServer(): Promise<StartedServer> {
|
||||
logger.error({ err }, "startup reconciliation of codex_local managed homes failed");
|
||||
});
|
||||
|
||||
void reconcileBuiltInAgentsOnStartup(db as any)
|
||||
void instanceSettingsService(db as any).getExperimental()
|
||||
.then((experimental) => {
|
||||
if (experimental.enableBuiltInAgents !== true) return null;
|
||||
return reconcileBuiltInAgentsOnStartup(db as any);
|
||||
})
|
||||
.then((result) => {
|
||||
if (!result) return;
|
||||
if (result.reconciled > 0 || result.unknown > 0 || result.duplicates > 0) {
|
||||
logger.warn(
|
||||
result,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Router, type Request } from "express";
|
||||
import type { Db } from "@paperclipai/db";
|
||||
import { builtInAgentProvisionSchema } from "@paperclipai/shared";
|
||||
import { validate } from "../middleware/validate.js";
|
||||
import { forbidden } from "../errors.js";
|
||||
import { accessService, logActivity } from "../services/index.js";
|
||||
import { forbidden, notFound } from "../errors.js";
|
||||
import { accessService, instanceSettingsService, logActivity } from "../services/index.js";
|
||||
import { builtInAgentService } from "../services/built-in-agents.js";
|
||||
import { authorizationDeniedDetails } from "../services/authorization.js";
|
||||
import { assertCompanyAccess, getActorInfo } from "./authz.js";
|
||||
@@ -25,6 +25,14 @@ export function builtInAgentRoutes(db: Db) {
|
||||
const router = Router();
|
||||
const access = accessService(db);
|
||||
const svc = builtInAgentService(db);
|
||||
const settings = instanceSettingsService(db);
|
||||
|
||||
async function assertBuiltInAgentsEnabled() {
|
||||
const experimental = await settings.getExperimental();
|
||||
if (experimental.enableBuiltInAgents !== true) {
|
||||
throw notFound("Built-in agents are not enabled");
|
||||
}
|
||||
}
|
||||
|
||||
async function assertCanProvisionBuiltInAgents(req: Request, companyId: string) {
|
||||
assertCompanyAccess(req, companyId);
|
||||
@@ -69,6 +77,7 @@ export function builtInAgentRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/built-in-agents", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
await assertBuiltInAgentsEnabled();
|
||||
const states = await svc.list(companyId);
|
||||
res.json(states.map(redactBuiltInAgentListState));
|
||||
});
|
||||
@@ -79,6 +88,7 @@ export function builtInAgentRoutes(db: Db) {
|
||||
async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
const key = req.params.key as string;
|
||||
await assertBuiltInAgentsEnabled();
|
||||
await assertCanProvisionBuiltInAgents(req, companyId);
|
||||
const actor = getActorInfo(req);
|
||||
const result = await svc.provision(companyId, key, req.body, {
|
||||
@@ -110,6 +120,7 @@ export function builtInAgentRoutes(db: Db) {
|
||||
router.post("/companies/:companyId/built-in-agents/:key/reset", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
const key = req.params.key as string;
|
||||
await assertBuiltInAgentsEnabled();
|
||||
await assertCanProvisionBuiltInAgents(req, companyId);
|
||||
const state = await svc.reset(companyId, key);
|
||||
await logBuiltInAgentMutation(req, {
|
||||
|
||||
@@ -54,6 +54,7 @@ export function normalizeExperimentalSettings(raw: unknown): InstanceExperimenta
|
||||
enableTaskWatchdogs: parsed.data.enableTaskWatchdogs ?? false,
|
||||
enableCloudSync: parsed.data.enableCloudSync ?? false,
|
||||
enableExternalObjects: parsed.data.enableExternalObjects ?? false,
|
||||
enableBuiltInAgents: parsed.data.enableBuiltInAgents ?? false,
|
||||
enableGoalsSidebarLink: parsed.data.enableGoalsSidebarLink ?? false,
|
||||
enableServerInfoDebugView: parsed.data.enableServerInfoDebugView ?? false,
|
||||
autoRestartDevServerWhenIdle: parsed.data.autoRestartDevServerWhenIdle ?? false,
|
||||
@@ -76,6 +77,7 @@ export function normalizeExperimentalSettings(raw: unknown): InstanceExperimenta
|
||||
enableExperimentalFileViewer: false,
|
||||
enableCloudSync: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableServerInfoDebugView: false,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
|
||||
@@ -711,12 +711,20 @@ export function AgentDetail() {
|
||||
? resourceMembershipState(membershipsQuery.data, "agent", resolvedAgentId)
|
||||
: "joined";
|
||||
|
||||
const { data: experimentalSettings } = useQuery({
|
||||
queryKey: queryKeys.instance.experimentalSettings,
|
||||
queryFn: () => instanceSettingsApi.getExperimental(),
|
||||
enabled: !!resolvedCompanyId,
|
||||
});
|
||||
const builtInAgentsEnabled = experimentalSettings?.enableBuiltInAgents === true;
|
||||
const { data: builtInStates } = useQuery({
|
||||
queryKey: queryKeys.builtInAgents.list(resolvedCompanyId!),
|
||||
queryFn: () => builtInAgentsApi.list(resolvedCompanyId!),
|
||||
enabled: !!resolvedCompanyId,
|
||||
enabled: !!resolvedCompanyId && builtInAgentsEnabled,
|
||||
});
|
||||
const builtInState = builtInStates?.find((entry) => entry.agentId === resolvedAgentId) ?? null;
|
||||
const builtInState = builtInAgentsEnabled
|
||||
? builtInStates?.find((entry) => entry.agentId === resolvedAgentId) ?? null
|
||||
: null;
|
||||
const builtInFeatureLabel = builtInState
|
||||
? builtInState.definition.featureKeys
|
||||
.map((key) => key.charAt(0).toUpperCase() + key.slice(1))
|
||||
|
||||
@@ -214,9 +214,11 @@ const environmentCapabilities: EnvironmentCapabilities = {
|
||||
function makeInstanceSettings({
|
||||
defaultEnvironmentId = null,
|
||||
enableEnvironments = true,
|
||||
enableBuiltInAgents = false,
|
||||
}: {
|
||||
defaultEnvironmentId?: string | null;
|
||||
enableEnvironments?: boolean;
|
||||
enableBuiltInAgents?: boolean;
|
||||
} = {}) {
|
||||
return {
|
||||
id: "instance-settings-1",
|
||||
@@ -242,6 +244,7 @@ function makeInstanceSettings({
|
||||
enableExperimentalFileViewer: false,
|
||||
enableCloudSync: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents,
|
||||
autoRestartDevServerWhenIdle: false,
|
||||
enableIssueGraphLivenessAutoRecovery: false,
|
||||
issueGraphLivenessAutoRecoveryLookbackHours: 24,
|
||||
@@ -380,6 +383,7 @@ describe("Agents", () => {
|
||||
|
||||
it("uses the built-in agents route segment as the built-in filter", async () => {
|
||||
mockRouterState.pathname = "/agents/builtin";
|
||||
mockInstanceSettingsApi.get.mockResolvedValue(makeInstanceSettings({ enableBuiltInAgents: true }));
|
||||
const builtInAgent = makeAgent({
|
||||
id: "built-in-agent",
|
||||
name: "Briefs Agent",
|
||||
@@ -747,6 +751,82 @@ describe("Agents", () => {
|
||||
expect(container.querySelector('select[aria-label="Group agents"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("hides built-in agent surfaces while the experimental flag is disabled", async () => {
|
||||
mockRouterState.pathname = "/agents/builtin";
|
||||
|
||||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
root!.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ToastProvider>
|
||||
<Agents />
|
||||
</ToastProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
expect(mockBuiltInAgentsApi.list).not.toHaveBeenCalled();
|
||||
expect(container.textContent).not.toContain("Built-in");
|
||||
expect(mockRouterState.navigate).toHaveBeenCalledWith("/agents/all", { replace: true });
|
||||
});
|
||||
|
||||
it("shows and filters built-in agents when the experimental flag is enabled", async () => {
|
||||
mockRouterState.pathname = "/agents/builtin";
|
||||
mockInstanceSettingsApi.get.mockResolvedValue(makeInstanceSettings({ enableBuiltInAgents: true }));
|
||||
mockAgentsApi.list.mockResolvedValue([
|
||||
makeAgent({
|
||||
id: "built-in-agent",
|
||||
name: "Briefs Agent",
|
||||
urlKey: "briefs-agent",
|
||||
}),
|
||||
makeAgent({
|
||||
id: "regular-agent",
|
||||
name: "Regular Agent",
|
||||
urlKey: "regular-agent",
|
||||
}),
|
||||
]);
|
||||
mockAgentsApi.org.mockResolvedValue([
|
||||
{
|
||||
id: "built-in-agent",
|
||||
name: "Briefs Agent",
|
||||
role: "engineer",
|
||||
status: "active",
|
||||
reports: [],
|
||||
},
|
||||
{
|
||||
id: "regular-agent",
|
||||
name: "Regular Agent",
|
||||
role: "engineer",
|
||||
status: "active",
|
||||
reports: [],
|
||||
},
|
||||
]);
|
||||
mockBuiltInAgentsApi.list.mockResolvedValue([
|
||||
makeBuiltInAgentState({ agentId: "built-in-agent" }),
|
||||
]);
|
||||
|
||||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
root!.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ToastProvider>
|
||||
<Agents />
|
||||
</ToastProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flushReact();
|
||||
await flushReact();
|
||||
|
||||
expect(mockBuiltInAgentsApi.list).toHaveBeenCalledWith("company-1");
|
||||
expect(container.textContent).toContain("Built-in");
|
||||
expect(container.textContent).toContain("Briefs Agent");
|
||||
expect(container.textContent).not.toContain("Regular Agent");
|
||||
expect(mockRouterState.navigate).not.toHaveBeenCalledWith("/agents/all", { replace: true });
|
||||
});
|
||||
|
||||
it("gives list-view rows a fixed-width title so meta columns align (PAP-86)", async () => {
|
||||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
|
||||
@@ -48,6 +48,8 @@ const GOALS_SIDEBAR_LINK_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle goals sidebar link experimental setting"]';
|
||||
const SERVER_INFO_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle server info debug view experimental setting"]';
|
||||
const BUILT_IN_AGENTS_TOGGLE_SELECTOR =
|
||||
'button[aria-label="Toggle built-in agents experimental setting"]';
|
||||
|
||||
function defaultExperimentalSettings(): InstanceExperimentalSettingsPayload {
|
||||
return {
|
||||
@@ -59,6 +61,7 @@ function defaultExperimentalSettings(): InstanceExperimentalSettingsPayload {
|
||||
enableIssuePlanDecompositions: false,
|
||||
enableExperimentalFileViewer: false,
|
||||
enableExternalObjects: false,
|
||||
enableBuiltInAgents: false,
|
||||
enableGoalsSidebarLink: false,
|
||||
enableTaskWatchdogs: false,
|
||||
enableCloudSync: false,
|
||||
@@ -274,6 +277,26 @@ describe("InstanceExperimentalSettings — Conference Room Chat card (PAP-11233)
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
|
||||
it("renders and patches the Built-in Agents experimental toggle", async () => {
|
||||
await renderPage();
|
||||
|
||||
expect(container.textContent).toContain("Built-in Agents");
|
||||
expect(container.textContent).toContain("Show Paperclip-managed built-in agent surfaces");
|
||||
|
||||
const toggle = container.querySelector<HTMLButtonElement>(BUILT_IN_AGENTS_TOGGLE_SELECTOR);
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("false");
|
||||
|
||||
await act(async () => {
|
||||
toggle?.click();
|
||||
});
|
||||
await flushReact();
|
||||
|
||||
expect(mockInstanceSettingsApi.updateExperimental).toHaveBeenCalledWith({
|
||||
enableBuiltInAgents: true,
|
||||
});
|
||||
expect(toggle?.getAttribute("aria-checked")).toBe("true");
|
||||
});
|
||||
|
||||
it("renders and patches the Server Info Debug View experimental toggle", async () => {
|
||||
await renderPage();
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ export function InstanceExperimentalSettings() {
|
||||
queryClient.setQueryData(queryKeys.instance.experimentalSettings, updatedSettings);
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.instance.experimentalSettings }),
|
||||
queryClient.invalidateQueries({ queryKey: ["built-in-agents"] }),
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.health }),
|
||||
]);
|
||||
},
|
||||
@@ -246,6 +247,7 @@ export function InstanceExperimentalSettings() {
|
||||
const enableTaskWatchdogs = experimentalQuery.data?.enableTaskWatchdogs === true;
|
||||
const enableCloudSync = experimentalQuery.data?.enableCloudSync === true;
|
||||
const enableExternalObjects = experimentalQuery.data?.enableExternalObjects === true;
|
||||
const enableBuiltInAgents = experimentalQuery.data?.enableBuiltInAgents === true;
|
||||
const enableGoalsSidebarLink = experimentalQuery.data?.enableGoalsSidebarLink === true;
|
||||
const enableServerInfoDebugView = experimentalQuery.data?.enableServerInfoDebugView === true;
|
||||
const autoRestartDevServerWhenIdle = experimentalQuery.data?.autoRestartDevServerWhenIdle === true;
|
||||
@@ -363,6 +365,24 @@ export function InstanceExperimentalSettings() {
|
||||
</Card>
|
||||
|
||||
<Card className="block p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Built-in Agents</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Show Paperclip-managed built-in agent surfaces, including built-in roster badges, the Built-in agents
|
||||
tab, and built-in agent setup controls.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
checked={enableBuiltInAgents}
|
||||
onCheckedChange={() => toggleMutation.mutate({ enableBuiltInAgents: !enableBuiltInAgents })}
|
||||
disabled={toggleMutation.isPending}
|
||||
aria-label="Toggle built-in agents experimental setting"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="rounded-xl border border-border bg-card p-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Experimental File Viewer</h2>
|
||||
|
||||
Reference in new issue
Block a user