diff --git a/doc/connections/AI-CONNECTIONS.md b/doc/connections/AI-CONNECTIONS.md
index 10fb86111d7..1c1df72f937 100644
--- a/doc/connections/AI-CONNECTIONS.md
+++ b/doc/connections/AI-CONNECTIONS.md
@@ -651,3 +651,14 @@ subscription combinations remain unqualified. Gemini CLI 0.58.0 has an upstream
ACP new-file error conversion defect. The provider-free filesystem probe exposes
that defect without modifying the installed CLI. Provider overloads and one
follow-up timeout also remain live qualification limits.
+
+### Gateway completion compatibility
+
+Provider-facing `paperclip_finish` accepts an omitted or `null` continuation for
+`done`, `completed`, and `needs_review`. This supports gateways that require all
+declared tool properties to be present. Normalization removes only `null`;
+non-yielding tool calls still reject a continuation object, and `yielded` still
+requires a complete `response_wake` object.
+
+Task-card account repair uses the provider reconnect form for routed accounts,
+retaining the saved endpoint, protocol, model aliases, and connection identity.
diff --git a/packages/paperclip-runner/README.md b/packages/paperclip-runner/README.md
index f1d28444d8c..5936d1c7b47 100644
--- a/packages/paperclip-runner/README.md
+++ b/packages/paperclip-runner/README.md
@@ -66,12 +66,6 @@ effects, audit, retries, conflicts, redaction, continuation, and terminal
decisions. The production adapter stays App-owned and invokes Paperclip's real
route/service authorities; it does not copy those rules into this package.
-Provider-facing `paperclip_finish` accepts an omitted or `null` continuation for
-`done`, `completed`, and `needs_review`. This supports gateways that require all
-declared tool properties to be present. Normalization removes only `null`;
-non-yielding tool calls still reject a continuation object, and `yielded` still
-requires a complete `response_wake` object.
-
## Quick start
Native provider debug-trace correlation uses an incremental index owned by its transport. Pending
diff --git a/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx b/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx
index 44562e7bcf2..efe20b64724 100644
--- a/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx
+++ b/ui/src/features/connections/ConnectionIntentInteractionBody.test.tsx
@@ -30,6 +30,7 @@ vi.mock("@/api/email", () => ({ emailApi: {
} }));
const credentialRender = vi.hoisted(() => vi.fn());
+const routedCredentialRender = vi.hoisted(() => vi.fn());
const setupOptionsMock = vi.hoisted(() => vi.fn());
const completeMock = vi.hoisted(() => vi.fn());
const declineMock = vi.hoisted(() => vi.fn());
@@ -68,6 +69,16 @@ vi.mock("@/components/ai-connections/AiConnectionCredentialStep", () => ({
; },
}));
+vi.mock("@/components/ai-connections/AiProviderSetup", () => ({
+ AiProviderSetup: (props: { reconnect: { id: string }; onComplete: (binding: { connectionId: string; grantId: string; method: "api_key" }) => void; onCancel: () => void }) => {
+ routedCredentialRender(props);
+ return
+
+
+
;
+ },
+}));
+
vi.mock("./ConnectionSetupFlow", () => ({
ConnectionSetupFlow: (props: {
requestedAgentId?: string;
@@ -477,6 +488,21 @@ describe("ConnectionIntentInteractionBody dialog behavior", () => {
describe("AI repair inside the card", () => {
const interaction: ConnectionIntentInteraction = { ...pendingConnectionIntentInteraction, payload: { ...pendingConnectionIntentInteraction.payload, purpose: "ai" } };
const connection = { id: "selected-account", name: "My Codex account", provider: "openai", method: "api_key", ownership: "personal", ownerName: "Dotta", status: "revoked" };
+ it.each(["openrouter", "bedrock", "gateway"] as const)("retains the selected %s route through task-card account repair", async kind => {
+ const routing = kind === "bedrock"
+ ? { kind, protocol: "bedrock", region: "us-east-1", auth: "bearer", models: [] }
+ : { kind, protocol: "responses", auth: "bearer", models: [], ...(kind === "gateway" ? { baseUrl: "https://gateway.example/v1" } : {}) };
+ const routedConnection = { ...connection, provider: kind === "bedrock" ? "anthropic" : kind === "openrouter" ? "openrouter" : "openai", routing };
+ setupOptionsMock.mockResolvedValue({ interaction, existingConnections: [], aiRepair: { connection: routedConnection, canReconnect: true } });
+ completeMock.mockResolvedValue({ ...interaction, status: "accepted" });
+ renderBody(interaction); await flush();
+ await act(() => button("Fix connection")!.click());
+ expect(document.querySelector('[data-testid="shared-ai-credentials"]')).toBeNull();
+ expect(routedCredentialRender.mock.lastCall![0]).toMatchObject({ companyId: interaction.companyId, agentId: interaction.payload.requestingAgentId, reconnect: routedConnection });
+ await act(() => button("Reconnect routed account")!.click());
+ expect(completeMock).toHaveBeenCalledWith(interaction.id, connection.id);
+ });
+
it.each([false, true])("requires atomic validated legacy adoption (validation fails: %s)", async (fails) => {
const binding = { provider: "openai", method: "subscription", mode: "responsible_user" };
setupOptionsMock.mockResolvedValue({ interaction, existingConnections: [], aiConnection: binding, aiConnectionRequiresAdoption: true });
diff --git a/ui/src/features/connections/ConnectionIntentInteractionBody.tsx b/ui/src/features/connections/ConnectionIntentInteractionBody.tsx
index dc7a6cc156d..2fe45ef46f6 100644
--- a/ui/src/features/connections/ConnectionIntentInteractionBody.tsx
+++ b/ui/src/features/connections/ConnectionIntentInteractionBody.tsx
@@ -1,4 +1,3 @@
-import { AiProviderSetup } from "@/components/ai-connections/AiProviderSetup";
import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
@@ -15,6 +14,7 @@ import { connectionIntentsApi } from "@/api/connection-intents";
import { aiConnectionsApi } from "@/api/ai-connections";
import { agentsApi } from "@/api/agents";
import { AiConnectionCredentialStep } from "@/components/ai-connections/AiConnectionCredentialStep";
+import { AiProviderSetup } from "@/components/ai-connections/AiProviderSetup";
import { defaultAiConnectionName } from "@/components/ai-connections/model";
import { AppLogo } from "@/pages/apps/AppLogo";
import { AgentAvatar } from "@/components/AgentAvatar";
@@ -394,7 +394,13 @@ export function ConnectionIntentInteractionBody({
{completeMutation.isPending ? "Continuing…" : "Continue task"}
- : repair ? repair.canReconnect ? repair.connection.routing ? selectAiAccountMutation.mutate({ ...result, generation })} onCancel={() => { closeSetup(); returnFocusToCard(); }} /> : selectAiAccountMutation.mutate({ connectionId: binding.connectionId, grantId: binding.grantId, method: binding.method ?? "api_key", generation })}
+ onCancel={() => { closeSetup(); returnFocusToCard(); }}
+ /> :