diff --git a/packages/types/src/experiment.ts b/packages/types/src/experiment.ts index d7eb0b03d..5d511859b 100644 --- a/packages/types/src/experiment.ts +++ b/packages/types/src/experiment.ts @@ -6,7 +6,13 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js" * ExperimentId */ -export const experimentIds = ["preventFocusDisruption", "imageGeneration", "runSlashCommand", "customTools"] as const +export const experimentIds = [ + "preventFocusDisruption", + "imageGeneration", + "runSlashCommand", + "customTools", + "parallelToolExecution", +] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -21,6 +27,7 @@ export const experimentsSchema = z.object({ imageGeneration: z.boolean().optional(), runSlashCommand: z.boolean().optional(), customTools: z.boolean().optional(), + parallelToolExecution: z.boolean().optional(), }) export type Experiments = z.infer diff --git a/src/shared/__tests__/experiments.spec.ts b/src/shared/__tests__/experiments.spec.ts index 92a7d7604..f2261a5c0 100644 --- a/src/shared/__tests__/experiments.spec.ts +++ b/src/shared/__tests__/experiments.spec.ts @@ -21,6 +21,7 @@ describe("experiments", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + parallelToolExecution: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(false) }) @@ -31,6 +32,7 @@ describe("experiments", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + parallelToolExecution: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(true) }) @@ -41,8 +43,27 @@ describe("experiments", () => { imageGeneration: false, runSlashCommand: false, customTools: false, + parallelToolExecution: false, } expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION)).toBe(false) }) }) + + describe("PARALLEL_TOOL_EXECUTION", () => { + it("is configured correctly", () => { + expect(EXPERIMENT_IDS.PARALLEL_TOOL_EXECUTION).toBe("parallelToolExecution") + expect(experimentConfigsMap.PARALLEL_TOOL_EXECUTION).toMatchObject({ + enabled: false, + showInSettings: false, + }) + }) + + it("returns false by default", () => { + expect(Experiments.isEnabled({}, "parallelToolExecution")).toBe(false) + }) + + it("returns true when enabled", () => { + expect(Experiments.isEnabled({ parallelToolExecution: true }, "parallelToolExecution")).toBe(true) + }) + }) }) diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index e189f99e2..ae538b913 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -5,6 +5,7 @@ export const EXPERIMENT_IDS = { IMAGE_GENERATION: "imageGeneration", RUN_SLASH_COMMAND: "runSlashCommand", CUSTOM_TOOLS: "customTools", + PARALLEL_TOOL_EXECUTION: "parallelToolExecution", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -13,6 +14,8 @@ type ExperimentKey = Keys interface ExperimentConfig { enabled: boolean + /** Defaults to true; set to false to hide from the Settings panel. */ + showInSettings?: boolean } export const experimentConfigsMap: Record = { @@ -20,6 +23,8 @@ export const experimentConfigsMap: Record = { IMAGE_GENERATION: { enabled: false }, RUN_SLASH_COMMAND: { enabled: false }, CUSTOM_TOOLS: { enabled: false }, + // TODO: add i18n keys (settings:experimental.PARALLEL_TOOL_EXECUTION.name/.description) in the same PR that sets showInSettings: true + PARALLEL_TOOL_EXECUTION: { enabled: false, showInSettings: false }, } export const experimentDefault = Object.fromEntries( diff --git a/webview-ui/src/components/settings/ExperimentalSettings.tsx b/webview-ui/src/components/settings/ExperimentalSettings.tsx index 23786ce0b..d5c55297e 100644 --- a/webview-ui/src/components/settings/ExperimentalSettings.tsx +++ b/webview-ui/src/components/settings/ExperimentalSettings.tsx @@ -51,6 +51,7 @@ export const ExperimentalSettings = ({
{Object.entries(experimentConfigsMap) .filter(([key]) => key in EXPERIMENT_IDS) + .filter(([, config]) => config.showInSettings !== false) .map((config) => { // Use the same translation key pattern as ExperimentalFeature const experimentKey = config[0] diff --git a/webview-ui/src/components/settings/__tests__/ExperimentalSettings.spec.tsx b/webview-ui/src/components/settings/__tests__/ExperimentalSettings.spec.tsx new file mode 100644 index 000000000..b31f87dc7 --- /dev/null +++ b/webview-ui/src/components/settings/__tests__/ExperimentalSettings.spec.tsx @@ -0,0 +1,35 @@ +import { render, screen } from "@testing-library/react" + +import { experimentDefault } from "@roo/experiments" + +import { ExperimentalSettings } from "../ExperimentalSettings" + +vi.mock("@src/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ + t: (key: string) => key, + }), +})) + +describe("ExperimentalSettings", () => { + const defaultProps = { + experiments: experimentDefault, + setExperimentEnabled: vi.fn(), + setImageGenerationProvider: vi.fn(), + setOpenRouterImageApiKey: vi.fn(), + setImageGenerationSelectedModel: vi.fn(), + } + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("does not render internal-only experiment flags", () => { + render() + + expect(screen.getByText("settings:experimental.PREVENT_FOCUS_DISRUPTION.name")).toBeInTheDocument() + expect(screen.getByText("settings:experimental.RUN_SLASH_COMMAND.name")).toBeInTheDocument() + expect(screen.getByText("settings:experimental.IMAGE_GENERATION.name")).toBeInTheDocument() + expect(screen.getByText("settings:experimental.CUSTOM_TOOLS.name")).toBeInTheDocument() + expect(screen.queryByText("settings:experimental.PARALLEL_TOOL_EXECUTION.name")).not.toBeInTheDocument() + }) +})