* Switch reasoning, mode, plan, and fast from the Command Center Extend the Command Center (Cmd-K) with selectors for a focused agent's Thinking (reasoning effort), access Mode, Plan mode, and Fast mode, mirroring the model switcher. Typing surfaces tiered "<Setting> › <Value>" breadcrumb rows with icons and a check on the current value, for both a running agent and a new draft tab. Rows only appear while typing, so the default palette is unchanged. - Thinking and Mode are multi-option selects; Plan mode and Fast mode are On/Off toggles. - Plan mode is dual-natured: a permission mode for Claude/Copilot/OpenCode (excluded from the Mode list, given its own toggle) and the plan_mode feature for Codex. Fast mode drives the fast_mode feature (model-gated). - All logic lives in command-center/agent-setting-contributions.ts as pure, unit-tested builders, with a small icon helper and a hook that registers the running-agent selectors; the draft registers from its composer tab. No changes to the command palette component and no protocol changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Unify Command Center agent controls * refactor: clarify agent control ownership * refactor(app): tighten command center agent controls --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
401 lines
12 KiB
TypeScript
401 lines
12 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { AgentFeature, AgentMode, AgentSelectOption } from "@getpaseo/protocol/agent-types";
|
|
import type { ProviderSelectorProvider } from "@/provider-selection/provider-selection";
|
|
import type { CommandCenterContribution } from "./contributions";
|
|
import {
|
|
buildAgentControlContributions,
|
|
type AgentControlContributionSource,
|
|
} from "./agent-control-contributions";
|
|
|
|
function TestIcon() {
|
|
return null;
|
|
}
|
|
|
|
const LABELS: AgentControlContributionSource["labels"] = {
|
|
model: "Model",
|
|
thinking: "Thinking",
|
|
mode: "Mode",
|
|
planMode: "Plan mode",
|
|
fast: "Fast",
|
|
on: "On",
|
|
off: "Off",
|
|
modelKeywords: "model switch",
|
|
thinkingKeywords: "reasoning effort",
|
|
modeKeywords: "access permission",
|
|
planKeywords: "plan planning",
|
|
fastKeywords: "fast speed",
|
|
};
|
|
|
|
const ICONS: AgentControlContributionSource["icons"] = {
|
|
provider: () => TestIcon,
|
|
thinking: TestIcon,
|
|
planMode: TestIcon,
|
|
mode: () => TestIcon,
|
|
feature: () => TestIcon,
|
|
};
|
|
|
|
function provider(input: {
|
|
id: string;
|
|
label: string;
|
|
state?: "models" | "error";
|
|
}): ProviderSelectorProvider {
|
|
if (input.state === "error") {
|
|
return {
|
|
id: input.id,
|
|
label: input.label,
|
|
modelSelection: { kind: "error", message: "Unavailable" },
|
|
};
|
|
}
|
|
return {
|
|
id: input.id,
|
|
label: input.label,
|
|
modelSelection: {
|
|
kind: "models",
|
|
rows: [
|
|
{
|
|
favoriteKey: `${input.id}:shared`,
|
|
provider: input.id,
|
|
providerLabel: input.label,
|
|
modelId: "shared",
|
|
modelLabel: `${input.label} model`,
|
|
description: undefined,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
function toggleFeature(id: string, value = false, icon?: string): AgentFeature {
|
|
return { type: "toggle", id, label: id, value, icon };
|
|
}
|
|
|
|
function makeSource(
|
|
overrides: {
|
|
provider?: string | null;
|
|
models?: Partial<AgentControlContributionSource["models"]>;
|
|
thinking?: Partial<AgentControlContributionSource["thinking"]>;
|
|
modes?: Partial<NonNullable<AgentControlContributionSource["modes"]>>;
|
|
features?: Partial<AgentControlContributionSource["features"]>;
|
|
} = {},
|
|
): AgentControlContributionSource {
|
|
return {
|
|
serverId: "host",
|
|
ownerKey: "agent",
|
|
provider: overrides.provider === undefined ? "claude" : overrides.provider,
|
|
labels: LABELS,
|
|
icons: ICONS,
|
|
models: {
|
|
providers: [],
|
|
selectedProvider: null,
|
|
selectedModelId: null,
|
|
select: () => undefined,
|
|
...overrides.models,
|
|
},
|
|
thinking: {
|
|
options: [],
|
|
selectedId: null,
|
|
select: () => undefined,
|
|
...overrides.thinking,
|
|
},
|
|
modes: {
|
|
options: [],
|
|
selectedId: null,
|
|
defaultModeId: null,
|
|
select: () => undefined,
|
|
...overrides.modes,
|
|
},
|
|
features: {
|
|
list: [],
|
|
set: () => undefined,
|
|
...overrides.features,
|
|
},
|
|
};
|
|
}
|
|
|
|
function selected(contribution: CommandCenterContribution): boolean {
|
|
return contribution.presentation.kind === "choice" ? contribution.presentation.selected : false;
|
|
}
|
|
|
|
const THINKING_OPTIONS: AgentSelectOption[] = [
|
|
{ id: "low", label: "Low" },
|
|
{ id: "high", label: "High" },
|
|
{ id: "xhigh", label: "Extra High" },
|
|
];
|
|
|
|
const CLAUDE_MODES: AgentMode[] = [
|
|
{ id: "plan", label: "Plan Mode" },
|
|
{ id: "default", label: "Always Ask" },
|
|
{ id: "acceptEdits", label: "Accept File Edits" },
|
|
];
|
|
|
|
describe("Command Center agent-control contributions", () => {
|
|
it("omits mode-derived choices when no mode control is available", () => {
|
|
const contributions = buildAgentControlContributions({
|
|
...makeSource({
|
|
models: { providers: [provider({ id: "claude", label: "Claude" })] },
|
|
features: { list: [toggleFeature("plan_mode", true)] },
|
|
}),
|
|
modes: undefined,
|
|
});
|
|
|
|
const groups = contributions.map((contribution) => contribution.group);
|
|
expect(groups).toContain("models");
|
|
expect(groups).toContain("plan-mode");
|
|
expect(groups).not.toContain("modes");
|
|
});
|
|
|
|
it("builds unique cross-provider model choices and skips unavailable or empty models", () => {
|
|
const selections: string[] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
provider: null,
|
|
models: {
|
|
providers: [
|
|
provider({ id: "claude", label: "Claude" }),
|
|
provider({ id: "codex", label: "Codex" }),
|
|
provider({ id: "unavailable", label: "Unavailable", state: "error" }),
|
|
{
|
|
id: "empty",
|
|
label: "Empty",
|
|
modelSelection: {
|
|
kind: "models",
|
|
rows: [
|
|
{
|
|
favoriteKey: "empty:",
|
|
provider: "empty",
|
|
providerLabel: "Empty",
|
|
modelId: "",
|
|
modelLabel: "Default",
|
|
description: undefined,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
selectedProvider: "codex",
|
|
selectedModelId: "shared",
|
|
select: (selectedProvider, modelId) => {
|
|
selections.push(`${selectedProvider}:${modelId}`);
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(
|
|
contributions.map((contribution) => ({
|
|
id: contribution.id,
|
|
selected: selected(contribution),
|
|
})),
|
|
).toEqual([
|
|
{ id: "models:claude:shared", selected: false },
|
|
{ id: "models:codex:shared", selected: true },
|
|
]);
|
|
expect(contributions[0].presentation).toMatchObject({
|
|
path: ["Model", "Claude", "Claude model"],
|
|
testId: "command-center-model-host:claude:shared",
|
|
});
|
|
|
|
contributions[0].run();
|
|
contributions[1].run();
|
|
expect(selections).toEqual(["claude:shared"]);
|
|
});
|
|
|
|
it("publishes thinking choices only when selection is meaningful", () => {
|
|
const selections: string[] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
thinking: {
|
|
options: THINKING_OPTIONS,
|
|
selectedId: "high",
|
|
select: (id) => {
|
|
selections.push(id);
|
|
},
|
|
},
|
|
}),
|
|
).filter((contribution) => contribution.group === "thinking");
|
|
|
|
expect(contributions.map((contribution) => contribution.id)).toEqual([
|
|
"thinking:low",
|
|
"thinking:high",
|
|
"thinking:xhigh",
|
|
]);
|
|
expect(contributions.map(selected)).toEqual([false, true, false]);
|
|
for (const contribution of contributions) contribution.run();
|
|
expect(selections).toEqual(["low", "xhigh"]);
|
|
|
|
const singleOption = buildAgentControlContributions(
|
|
makeSource({
|
|
thinking: {
|
|
options: [{ id: "high", label: "High" }],
|
|
selectedId: "high",
|
|
},
|
|
}),
|
|
);
|
|
expect(singleOption.filter((contribution) => contribution.group === "thinking")).toEqual([]);
|
|
});
|
|
|
|
it("formats access modes, excludes planning modes, and marks the current mode", () => {
|
|
const selections: string[] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
modes: {
|
|
options: CLAUDE_MODES,
|
|
selectedId: "default",
|
|
defaultModeId: "default",
|
|
select: (id) => {
|
|
selections.push(id);
|
|
},
|
|
},
|
|
}),
|
|
).filter((contribution) => contribution.group === "modes");
|
|
|
|
expect(
|
|
contributions.map((contribution) => ({
|
|
id: contribution.id,
|
|
path:
|
|
contribution.presentation.kind === "choice" ? contribution.presentation.path : undefined,
|
|
selected: selected(contribution),
|
|
})),
|
|
).toEqual([
|
|
{ id: "modes:default", path: ["Mode", "Always ask"], selected: true },
|
|
{ id: "modes:acceptEdits", path: ["Mode", "Accept file edits"], selected: false },
|
|
]);
|
|
contributions[1].run();
|
|
expect(selections).toEqual(["acceptEdits"]);
|
|
});
|
|
|
|
it("toggles mode-based Plan through the plan mode and provider default", () => {
|
|
const selections: string[] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
modes: {
|
|
options: CLAUDE_MODES,
|
|
selectedId: "plan",
|
|
defaultModeId: "default",
|
|
select: (id) => {
|
|
selections.push(id);
|
|
},
|
|
},
|
|
}),
|
|
).filter((contribution) => contribution.group === "plan-mode");
|
|
|
|
expect(
|
|
contributions.map((contribution) => ({
|
|
id: contribution.id,
|
|
selected: selected(contribution),
|
|
})),
|
|
).toEqual([
|
|
{ id: "plan-mode:on", selected: true },
|
|
{ id: "plan-mode:off", selected: false },
|
|
]);
|
|
contributions[1].run();
|
|
expect(selections).toEqual(["default"]);
|
|
});
|
|
|
|
it("does not use a planning provider default as the Plan off target", () => {
|
|
const selections: string[] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
modes: {
|
|
options: [
|
|
{ id: "research", label: "Research", colorTier: "planning" },
|
|
{ id: "default", label: "Default", colorTier: "safe" },
|
|
],
|
|
selectedId: "research",
|
|
defaultModeId: "research",
|
|
select: (id) => {
|
|
selections.push(id);
|
|
},
|
|
},
|
|
}),
|
|
).filter((contribution) => contribution.group === "plan-mode");
|
|
|
|
contributions[1].run();
|
|
expect(selections).toEqual(["default"]);
|
|
});
|
|
|
|
it("prefers the plan_mode feature and reads its canonical value", () => {
|
|
const calls: [string, unknown][] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
modes: {
|
|
options: CLAUDE_MODES,
|
|
selectedId: "default",
|
|
defaultModeId: "default",
|
|
},
|
|
features: {
|
|
list: [toggleFeature("plan_mode", true, "list-todo")],
|
|
set: (id, value) => {
|
|
calls.push([id, value]);
|
|
},
|
|
},
|
|
}),
|
|
).filter((contribution) => contribution.group === "plan-mode");
|
|
|
|
expect(contributions.map(selected)).toEqual([true, false]);
|
|
contributions[1].run();
|
|
expect(calls).toEqual([["plan_mode", false]]);
|
|
});
|
|
|
|
it("publishes Fast only for an available toggle and ignores unrelated features", () => {
|
|
const calls: [string, unknown][] = [];
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
features: {
|
|
list: [toggleFeature("fast_mode", false, "zap"), toggleFeature("auto_accept", true)],
|
|
set: (id, value) => {
|
|
calls.push([id, value]);
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
const fast = contributions.filter((contribution) => contribution.group === "fast-mode");
|
|
|
|
expect(fast.map(selected)).toEqual([false, true]);
|
|
expect(contributions.some((contribution) => contribution.id.includes("auto_accept"))).toBe(
|
|
false,
|
|
);
|
|
fast[0].run();
|
|
expect(calls).toEqual([["fast_mode", true]]);
|
|
|
|
const unavailable = buildAgentControlContributions(makeSource());
|
|
expect(unavailable.filter((contribution) => contribution.group === "fast-mode")).toEqual([]);
|
|
});
|
|
|
|
it("centralizes query-only group ordering between built-in result sections", () => {
|
|
const contributions = buildAgentControlContributions(
|
|
makeSource({
|
|
models: {
|
|
providers: [provider({ id: "claude", label: "Claude" })],
|
|
selectedProvider: "claude",
|
|
selectedModelId: "shared",
|
|
},
|
|
thinking: { options: THINKING_OPTIONS, selectedId: "high" },
|
|
modes: {
|
|
options: CLAUDE_MODES,
|
|
selectedId: "default",
|
|
defaultModeId: "default",
|
|
},
|
|
features: { list: [toggleFeature("fast_mode")] },
|
|
}),
|
|
);
|
|
const ranks = new Map<string, number>();
|
|
for (const contribution of contributions) {
|
|
ranks.set(contribution.group, contribution.groupRank);
|
|
expect(contribution.visibility).toBe("query");
|
|
}
|
|
|
|
expect([...ranks.entries()]).toEqual([
|
|
["models", 1],
|
|
["thinking", 1.1],
|
|
["modes", 1.2],
|
|
["plan-mode", 1.3],
|
|
["fast-mode", 1.4],
|
|
]);
|
|
for (const rank of ranks.values()) expect(rank).toBeLessThan(2);
|
|
expect(new Set(contributions.map((contribution) => contribution.id)).size).toBe(
|
|
contributions.length,
|
|
);
|
|
});
|
|
});
|