diff --git a/src/features/chat/ui/AgentModelPicker.tsx b/src/features/chat/ui/AgentModelPicker.tsx
index 2adfbb91d..e76afafd1 100644
--- a/src/features/chat/ui/AgentModelPicker.tsx
+++ b/src/features/chat/ui/AgentModelPicker.tsx
@@ -80,6 +80,13 @@ type PopoverContentAlign = NonNullable<
>;
const REASONING_EFFORT_COLUMN_TRANSITION_MS = 240;
const PICKER_WIDTH_COMPACT_PX = 420;
+// Harness agents (pi-acp, codex-acp, etc.) can report long ACP model names
+// (e.g. provider/id names), so they get wider pickers than Goose, whose
+// curated model names are short. With a reasoning-effort column present,
+// the agent (11.75rem) + reasoning (11rem) columns alone consume ~23rem,
+// so the picker needs extra width for the model column to breathe.
+const PICKER_WIDTH_HARNESS_PX = 560;
+const PICKER_WIDTH_HARNESS_EXPANDED_PX = 736;
const PICKER_WIDTH_EXPANDED_PX = 596;
function toSentenceCaseLabel(value: string | undefined): string {
@@ -395,10 +402,19 @@ export function AgentModelPicker({
!modelBrowsing &&
(agents.length > 1 || hasAgentNeedingSetup);
const showReasoningEffortColumn = showReasoningEffort;
+ // Harness agents get wider pickers (and flexible model columns) whenever
+ // their agent column is visible, with or without a reasoning-effort
+ // column, so long ACP model names are not truncated. Goose is excluded
+ // because its curated model names are short.
+ const widenForHarnessAgent = showAgentColumn && selectedAgentId !== "goose";
const isWidePicker = showReasoningEffortColumn && showAgentColumn;
const pickerWidth = isWidePicker
- ? PICKER_WIDTH_EXPANDED_PX
- : PICKER_WIDTH_COMPACT_PX;
+ ? widenForHarnessAgent
+ ? PICKER_WIDTH_HARNESS_EXPANDED_PX
+ : PICKER_WIDTH_EXPANDED_PX
+ : widenForHarnessAgent
+ ? PICKER_WIDTH_HARNESS_PX
+ : PICKER_WIDTH_COMPACT_PX;
// Land keyboard focus in the revealed column, since the reveal button that
// held focus unmounts with it.
@@ -500,7 +516,13 @@ export function AgentModelPicker({
// gated single-column layout has no dead vertical space below the
// model list.
"flex max-h-[min(24rem,50vh)] flex-col overflow-hidden p-1 transition-[width] duration-[240ms] ease-[cubic-bezier(0.2,0,0,1)]",
- isWidePicker ? "w-[37.25rem]" : "w-[26.25rem]",
+ isWidePicker
+ ? widenForHarnessAgent
+ ? "w-[46rem]"
+ : "w-[37.25rem]"
+ : widenForHarnessAgent
+ ? "w-[35rem]"
+ : "w-[26.25rem]",
)}
onInteractOutside={(event) => {
classifyOutsideInteraction(event.target);
@@ -672,7 +694,11 @@ export function AgentModelPicker({
data-col="model"
className={cn(
"flex min-h-0 min-w-0 overflow-hidden p-1",
- showAgentColumn ? "ml-1 w-56 shrink-0" : "flex-1",
+ showAgentColumn
+ ? widenForHarnessAgent
+ ? "ml-1 flex-1"
+ : "ml-1 w-56 shrink-0"
+ : "flex-1",
)}
>
{modelsLoading ? (
diff --git a/src/features/chat/ui/AgentModelPickerLists.tsx b/src/features/chat/ui/AgentModelPickerLists.tsx
index 7ad2902bd..22a492462 100644
--- a/src/features/chat/ui/AgentModelPickerLists.tsx
+++ b/src/features/chat/ui/AgentModelPickerLists.tsx
@@ -399,7 +399,10 @@ export const RecommendedModelList = forwardRef<
{providerIcon}
) : null}
-
+
{getModelDisplayName(model)}
diff --git a/src/features/chat/ui/__tests__/AgentModelPicker.test.tsx b/src/features/chat/ui/__tests__/AgentModelPicker.test.tsx
index 056f71e6f..5c9c980aa 100644
--- a/src/features/chat/ui/__tests__/AgentModelPicker.test.tsx
+++ b/src/features/chat/ui/__tests__/AgentModelPicker.test.tsx
@@ -52,6 +52,67 @@ describe("AgentModelPicker", () => {
).toHaveTextContent("GPT-4o");
});
+ // Harness agents (pi-acp, codex-acp, etc.) get a wider picker in the
+ // new-chat composer because their ACP model names can be long; Goose keeps
+ // the compact layout.
+ it("widens the new-chat picker for a harness agent without reasoning effort", async () => {
+ const user = userEvent.setup();
+ render(
+ ,
+ );
+
+ await user.click(
+ screen.getByRole("button", { name: /choose agent and model/i }),
+ );
+
+ expect(screen.getByRole("dialog")).toHaveClass("w-[35rem]");
+ });
+
+ // With a reasoning-effort column present the harness picker grows further,
+ // because the agent (11.75rem) and reasoning (11rem) columns alone consume
+ // most of the Goose expanded width and the model column needs room for
+ // long ACP names (e.g. "databricks / databricks-glm-5-3").
+ it("widens the expanded picker for a harness agent with reasoning effort", async () => {
+ const user = userEvent.setup();
+ render(
+ ,
+ );
+
+ await user.click(
+ screen.getByRole("button", { name: /choose agent and model/i }),
+ );
+
+ expect(screen.getByRole("dialog")).toHaveClass("w-[46rem]");
+ });
+
it("routes not-ready Goose to Providers settings with a connect action", async () => {
const user = userEvent.setup();
const onAgentChange = vi.fn();
@@ -1418,6 +1479,35 @@ describe("AgentModelPicker", () => {
expect(content).toHaveClass("w-[37.25rem]");
});
+ // Without a reasoning-effort column the gated reveal widens to the
+ // harness width so long model names (e.g. pi-acp provider/id names) are
+ // not truncated. Goose is excluded, so use a harness agent here.
+ it("widens to the harness width when no reasoning-effort column is present", async () => {
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ await openPicker(user);
+
+ const content = document.querySelector('[data-slot="popover-content"]');
+ expect(content).toHaveClass("w-[26.25rem]");
+
+ await user.click(screen.getByRole("button", { name: /switch agent/i }));
+
+ expect(content).toHaveClass("w-[35rem]");
+ });
+
it("hides the switch-agent button when the only agent is ready", async () => {
const user = userEvent.setup();
renderGated({ agents: [{ id: "goose", label: "Goose" }] });
diff --git a/src/features/providers/curatedProviders.ts b/src/features/providers/curatedProviders.ts
index 7b4806910..edc480f39 100644
--- a/src/features/providers/curatedProviders.ts
+++ b/src/features/providers/curatedProviders.ts
@@ -80,22 +80,19 @@ export const CURATED_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
supportsAuth: true,
supportsAuthStatus: true,
},
- // full pi support in a future update
- // {
- // id: "pi-acp",
- // displayName: "Pi",
- // category: "agent",
- // description: "Pi ACP agent",
- // setupMethod: "cli_auth",
- // binaryName: "pi-acp",
- // group: "default",
- // aliases: ["pi-acp", "pi"],
- // supportsInstall: false,
- // supportsAuth: false,
- // supportsAuthStatus: false,
- // supportsModelList: false,
- // modelSelectionHint: "Use the Pi CLI to configure the model.",
- // },
+ {
+ id: "pi-acp",
+ displayName: "Pi",
+ category: "agent",
+ description: "Pi coding agent",
+ setupMethod: "cli_auth",
+ binaryName: "pi-acp",
+ group: "default",
+ aliases: ["pi-acp", "pi"],
+ supportsInstall: false,
+ supportsAuth: false,
+ supportsAuthStatus: false,
+ },
{
id: "databricks_v2",
displayName: "Databricks AI Gateway",
diff --git a/src/features/providers/hooks/useProviderModels.test.tsx b/src/features/providers/hooks/useProviderModels.test.tsx
index f0a1da316..06ead6080 100644
--- a/src/features/providers/hooks/useProviderModels.test.tsx
+++ b/src/features/providers/hooks/useProviderModels.test.tsx
@@ -127,6 +127,7 @@ describe("useProviderModels", () => {
"codex-acp",
"copilot-acp",
"cursor-agent",
+ "pi-acp",
]);
act(() => {
@@ -151,6 +152,7 @@ describe("useProviderModels", () => {
"codex-acp",
"copilot-acp",
"cursor-agent",
+ "pi-acp",
]);
act(() => {
diff --git a/src/features/providers/providerCatalog.test.ts b/src/features/providers/providerCatalog.test.ts
index 1214f3f7d..65426d45e 100644
--- a/src/features/providers/providerCatalog.test.ts
+++ b/src/features/providers/providerCatalog.test.ts
@@ -78,6 +78,7 @@ describe("provider catalog selectors", () => {
"copilot-acp",
"amp-acp",
"cursor-agent",
+ "pi-acp",
]);
expect(getModelProviders().map((provider) => provider.id)).toEqual([
"databricks_v2",
diff --git a/src/shared/api/__tests__/acp.test.ts b/src/shared/api/__tests__/acp.test.ts
index 8bed6a1d4..9e36ddf81 100644
--- a/src/shared/api/__tests__/acp.test.ts
+++ b/src/shared/api/__tests__/acp.test.ts
@@ -1094,6 +1094,7 @@ describe("acpCreateSession", () => {
"copilot-acp",
"amp-acp",
"cursor-agent",
+ "pi-acp",
])("keeps the %s harness outside Goose provider policy", async (harnessId) => {
await setRuntimeConfig(managedRuntimeConfig);
mockNewSession.mockResolvedValue({ sessionId: `session-${harnessId}` });
diff --git a/src/shared/ui/icons/ProviderIcons.tsx b/src/shared/ui/icons/ProviderIcons.tsx
index c3e70aaba..dfce90d13 100644
--- a/src/shared/ui/icons/ProviderIcons.tsx
+++ b/src/shared/ui/icons/ProviderIcons.tsx
@@ -420,6 +420,27 @@ export function CopilotIcon({ className = "" }: { className?: string }) {
);
}
+export function PiIcon({ className = "" }: { className?: string }) {
+ return (
+
+ );
+}
+
const PROVIDER_ICON_MAP: Record ReactNode> = {
goose: (className) => (
@@ -451,6 +472,8 @@ const PROVIDER_ICON_MAP: Record ReactNode> = {
snowflake: (className) => ,
xai: (className) => ,
lmstudio: (className) => ,
+ "pi-acp": (className) => ,
+ pi: (className) => ,
};
function normalizeProviderId(providerId: string) {