You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/08/31 12:41:00 UTC

(maka) branch main updated: feat(desktop): expose OAuth connection accounts (#4314)

This is an automated email from the ASF dual-hosted git repository.

me2seeks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new 064c3b299f feat(desktop): expose OAuth connection accounts (#4314)
064c3b299f is described below

commit 064c3b299f5853c08a0af85c5f798dbce3e8b922
Author: Zhang <96...@users.noreply.github.com>
AuthorDate: Mon Aug 31 20:40:56 2026 +0800

    feat(desktop): expose OAuth connection accounts (#4314)
    
    * feat(desktop): expose OAuth connection accounts
    
    * test(desktop): cover OAuth multi-account settings
    
    * fix(desktop): preserve exact OAuth account identity
    
    * chore(desktop): remove unused OAuth bridge alias
    
    * test(desktop): align OAuth generation stories
---
 apps/desktop/renderer-architecture.json            |   8 +-
 .../main/__tests__/provider-add-submission.test.ts |  11 +-
 .../runtime-host-connections-ipc-main.test.ts      |  49 ++++-
 .../__tests__/runtime-host-oauth-ipc-main.test.ts  |  99 +++++++---
 .../src/main/runtime-host-connections-ipc-main.ts  | 120 ++++++++++--
 .../src/main/runtime-host-oauth-ipc-main.ts        | 108 ++++++-----
 apps/desktop/src/preload/bridge-contract.d.ts      | 110 +++++++----
 apps/desktop/src/preload/preload.ts                |  74 +++----
 .../src/renderer/locales/settings-provider-copy.ts |  26 ++-
 .../renderer/settings/provider-add-submission.ts   |  10 +-
 .../renderer/settings/provider-catalog-page.tsx    |  35 +++-
 .../settings/provider-connection-detail.tsx        |  34 +++-
 .../renderer/settings/provider-oauth-section.tsx   | 138 ++++++--------
 .../src/renderer/settings/provider-panel-shared.ts |  24 ++-
 .../src/renderer/settings/providers-panel.tsx      | 171 +++++++++++++----
 .../settings/runtime-host-settings-bridge.ts       |  93 ++++++---
 .../src/renderer/settings/use-connection-detail.ts |  68 ++++---
 .../src/renderer/settings/use-oauth-login-flow.ts  | 105 ++++++----
 .../src/shared/desktop-connection-snapshot.ts      |   6 +
 .../stories/settings/provider-settings.stories.tsx | 212 ++++++++++++++++++---
 .../stories/settings/settings-pages.stories.tsx    |  50 ++---
 21 files changed, 1078 insertions(+), 473 deletions(-)

diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json
index 317c460660..3cad453abf 100644
--- a/apps/desktop/renderer-architecture.json
+++ b/apps/desktop/renderer-architecture.json
@@ -3674,9 +3674,7 @@
           "window.maka.githubCopilotSubscription.logout": 1,
           "window.maka.githubCopilotSubscription.refreshTokens": 1,
           "window.maka.openAiCodex": 1,
-          "window.maka.openAiCodex.getAccountState": 1,
-          "window.maka.xaiOAuth": 1,
-          "window.maka.xaiOAuth.getAccountState": 1
+          "window.maka.xaiOAuth": 1
         },
         "environmentCapabilities": {},
         "hookCalls": {
@@ -3983,9 +3981,7 @@
           "window.maka.connections.test": 1,
           "window.maka.connections.update": 1
         },
-        "environmentCapabilities": {
-          "window": 2
-        },
+        "environmentCapabilities": {},
         "hookCalls": {},
         "lifecycleMethods": {},
         "unresolvedDependencies": 0,
diff --git a/apps/desktop/src/main/__tests__/provider-add-submission.test.ts b/apps/desktop/src/main/__tests__/provider-add-submission.test.ts
index 8d0302037d..5bf2621988 100644
--- a/apps/desktop/src/main/__tests__/provider-add-submission.test.ts
+++ b/apps/desktop/src/main/__tests__/provider-add-submission.test.ts
@@ -29,7 +29,7 @@ import {
   PROVIDER_DEFAULTS,
   providerSupportsModelDiscovery,
   type CreateConnectionInput,
-  type LlmConnection,
+  type IdentifiedLlmConnection,
   type ProviderType,
 } from '@maka/core/llm-connections';
 
@@ -53,8 +53,9 @@ function draft(over: Partial<AddProviderDraft> = {}): AddProviderDraft {
   };
 }
 
-function connection(slug: string): LlmConnection {
+function connection(slug: string): IdentifiedLlmConnection {
   return {
+    connectionId: `connection-${slug}`,
     slug,
     name: slug,
     providerType: 'openai-compatible',
@@ -62,12 +63,12 @@ function connection(slug: string): LlmConnection {
     enabled: true,
     createdAt: 0,
     updatedAt: 0,
-  } as LlmConnection;
+  } as IdentifiedLlmConnection;
 }
 
 function bridge(over: {
-  create?: (input: CreateConnectionInput) => Promise<LlmConnection>;
-  fetchModels?: (slug: string) => Promise<unknown>;
+  create?: (input: CreateConnectionInput) => Promise<IdentifiedLlmConnection>;
+  fetchModels?: (connection: { readonly connectionId: string; readonly slug: string }) => Promise<unknown>;
 }) {
   return {
     create: over.create ?? (async (input) => connection(input.slug)),
diff --git a/apps/desktop/src/main/__tests__/runtime-host-connections-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-connections-ipc-main.test.ts
index a452275c24..96e730be8b 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-connections-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-connections-ipc-main.test.ts
@@ -93,7 +93,7 @@ test('retries connection delete after a stale revision instead of failing perman
     emitConnectionListChanged() {},
   });
 
-  await handlers.get('connections:delete')?.({}, 'openrouter');
+  await handlers.get('connections:delete')?.({}, connectionIdentity());
   assert.equal(removals, 2);
 });
 
@@ -123,12 +123,41 @@ test('treats a missing connection as a successful delete without calling remove'
     },
   });
 
-  await handlers.get('connections:delete')?.({}, 'already-gone');
+  await handlers.get('connections:delete')?.({}, {
+    connectionId: 'already-gone',
+    slug: 'already-gone',
+  });
   assert.equal(removals, 0);
   assert.equal(listChanged, 1);
 });
 
-test('rejects invalid connection slug input instead of treating it as already deleted', async () => {
+test('does not delete a replacement Connection that reused the stale detail slug', async () => {
+  const handlers = new Map<string, (...args: unknown[]) => unknown>();
+  let removals = 0;
+  registerRuntimeHostConnectionsIpc({
+    ipcMain: {
+      handle: (channel, handler) => {
+        handlers.set(channel, handler as (...args: unknown[]) => unknown);
+      },
+    },
+    client: {
+      loadConnectionCatalog: async (): Promise<ConnectionCatalogSnapshot> => ({
+        ...catalog(),
+        connections: [{ ...catalog().connections[0]!, connectionId: 'connection-2' }],
+      }),
+      removeConnection: async () => {
+        removals += 1;
+        return { kind: 'committed', catalogRevision: 8 };
+      },
+    } as never,
+    emitConnectionListChanged() {},
+  });
+
+  await handlers.get('connections:delete')?.({}, connectionIdentity());
+  assert.equal(removals, 0);
+});
+
+test('rejects invalid connection identity input instead of treating it as already deleted', async () => {
   const handlers = new Map<string, (...args: unknown[]) => unknown>();
   registerRuntimeHostConnectionsIpc({
     ipcMain: {
@@ -149,7 +178,7 @@ test('rejects invalid connection slug input instead of treating it as already de
 
   await assert.rejects(
     async () => handlers.get('connections:delete')?.({}, 42),
-    /Invalid connection slug|connection slug/i,
+    /Invalid Connection identity/i,
   );
 });
 
@@ -176,7 +205,7 @@ test('reports an existing but unconfigured credential as missing', async () => {
   });
 
   assert.equal(
-    await handlers.get('connections:hasSecret')?.({}, 'openrouter'),
+    await handlers.get('connections:hasSecret')?.({}, connectionIdentity()),
     false,
   );
 });
@@ -205,16 +234,16 @@ test('keeps saved custom header values out of the renderer and preserves them by
   });
 
   assert.deepEqual(
-    await handlers.get('connections:getRequestHeaders')?.({}, 'openrouter'),
+    await handlers.get('connections:getRequestHeaders')?.({}, connectionIdentity()),
     { names: ['HTTP-Referer'] },
   );
   assert.equal(
-    JSON.stringify(await handlers.get('connections:getRequestHeaders')?.({}, 'openrouter')).includes('private.example'),
+    JSON.stringify(await handlers.get('connections:getRequestHeaders')?.({}, connectionIdentity())).includes('private.example'),
     false,
   );
 
   assert.deepEqual(
-    await handlers.get('connections:setRequestHeaders')?.({}, 'openrouter', [
+    await handlers.get('connections:setRequestHeaders')?.({}, connectionIdentity(), [
       { name: 'HTTP-Referer' },
       { name: 'X-Title', value: 'Maka' },
     ]),
@@ -371,3 +400,7 @@ function catalog(): ConnectionCatalogSnapshot {
     ],
   };
 }
+
+function connectionIdentity() {
+  return { connectionId: 'connection-1', slug: 'openrouter' } as const;
+}
diff --git a/apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts
index 64f824e4be..84fa6dd386 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts
@@ -167,9 +167,18 @@ test('adapts every Host OAuth provider through one Desktop flow', async () => {
   const authorization = await invoke(
     handlers,
     'openai-codex:get-auth-url',
-    catalog.connections[0]?.connectionId,
+    { kind: 'existing', connectionId: catalog.connections[0]?.connectionId },
   );
-  assert.deepEqual(authorization, { authRequestId: attemptId, stateHint: 'STATE-HINT' });
+  const expectedConnection = {
+    connectionId: catalog.connections[0]?.connectionId,
+    slug: 'openai-codex',
+    providerType: provider,
+  };
+  assert.deepEqual(authorization, {
+    authRequestId: attemptId,
+    stateHint: 'STATE-HINT',
+    connection: expectedConnection,
+  });
   assert.deepEqual(opened, ['https://codex.example/authorize']);
   assert.deepEqual(
     await invoke(
@@ -178,7 +187,7 @@ test('adapts every Host OAuth provider through one Desktop flow', async () => {
       attemptId,
       'authorization-code#state',
     ),
-    { ok: true },
+    { ok: true, connection: expectedConnection },
   );
   assert.equal(changed, 1);
   assert.deepEqual(catalog.defaultTarget, {
@@ -187,7 +196,11 @@ test('adapts every Host OAuth provider through one Desktop flow', async () => {
   });
   // No quota: reporting it required the retired provider's own client identity,
   // so the account state carries the runtime state alone.
-  assert.deepEqual(await invoke(handlers, 'openai-codex:get-account-state'), {
+  assert.deepEqual(await invoke(
+    handlers,
+    'openai-codex:get-account-state',
+    catalog.connections[0]?.connectionId,
+  ), {
     provider,
     runtimeState: 'authenticated',
   });
@@ -231,7 +244,10 @@ test('provider-scoped OAuth IPC rejects a Connection ID owned by another provide
   });
 
   assert.deepEqual(
-    await invoke(handlers, 'openai-codex:get-auth-url', xaiConnection.connectionId),
+    await invoke(handlers, 'openai-codex:get-auth-url', {
+      kind: 'existing',
+      connectionId: xaiConnection.connectionId,
+    }),
     {
       ok: false,
       reason: 'unknown',
@@ -240,14 +256,18 @@ test('provider-scoped OAuth IPC rejects a Connection ID owned by another provide
   );
   assert.deepEqual(
     await invoke(handlers, 'openai-codex:get-account-state', xaiConnection.connectionId),
-    { provider: 'openai-codex', runtimeState: 'not_logged_in' },
+    {
+      ok: false,
+      reason: 'unknown',
+      message: 'OAuth account does not match this provider',
+    },
   );
   assert.deepEqual(
     await invoke(handlers, 'openai-codex:refresh-tokens', xaiConnection.connectionId),
     {
       ok: false,
       reason: 'refresh_failed',
-      message: 'OAuth account is not connected',
+      message: 'OAuth account does not match this provider',
     },
   );
   assert.deepEqual(await invoke(handlers, 'openai-codex:logout', xaiConnection.connectionId), {
@@ -271,7 +291,13 @@ test('malformed OAuth Connection IDs fail closed before catalog or credential ac
     isProviderEnabled: () => true,
   });
 
-  for (const malformed of [null, 7, {}]) {
+  for (const malformed of [
+    null,
+    7,
+    {},
+    { kind: 'create', connectionId: '00000000-0000-4000-8000-000000000001' },
+    { kind: 'existing', connectionId: '00000000-0000-4000-8000-000000000001', extra: true },
+  ]) {
     assert.deepEqual(await invoke(handlers, 'openai-codex:get-auth-url', malformed), {
       ok: false,
       reason: 'unknown',
@@ -395,9 +421,9 @@ test('a second OAuth start cannot replace or cancel a pending active attempt', a
     isProviderEnabled: () => true,
   });
 
-  const firstAuthorization = invoke(handlers, 'openai-codex:get-auth-url');
+  const firstAuthorization = invoke(handlers, 'openai-codex:get-auth-url', { kind: 'create' });
   await firstPresentationPoll;
-  assert.deepEqual(await invoke(handlers, 'openai-codex:get-auth-url'), {
+  assert.deepEqual(await invoke(handlers, 'openai-codex:get-auth-url', { kind: 'create' }), {
     ok: false,
     reason: 'unknown',
     message: 'Another OAuth login is already in progress',
@@ -413,6 +439,11 @@ test('a second OAuth start cannot replace or cancel a pending active attempt', a
   assert.deepEqual(await firstAuthorization, {
     authRequestId: firstAttemptId,
     stateHint: 'FIRST',
+    connection: {
+      connectionId,
+      slug: 'openai-codex',
+      providerType: provider,
+    },
   });
   assert.deepEqual(
     await invoke(handlers, 'openai-codex:logout', foreignConnection.connectionId),
@@ -425,7 +456,7 @@ test('a second OAuth start cannot replace or cancel a pending active attempt', a
   assert.deepEqual(await invoke(handlers, 'openai-codex:logout'), {
     ok: false,
     reason: 'unknown',
-    message: 'Select a specific OAuth account to log out',
+    message: 'Invalid OAuth Connection identity',
   });
   assert.equal(cancels, 0);
   assert.deepEqual(
@@ -435,7 +466,14 @@ test('a second OAuth start cannot replace or cancel a pending active attempt', a
   assert.equal(cancels, 0);
   assert.deepEqual(
     await invoke(handlers, 'openai-codex:complete-authorization', firstAttemptId),
-    { ok: true },
+    {
+      ok: true,
+      connection: {
+        connectionId,
+        slug: 'openai-codex',
+        providerType: provider,
+      },
+    },
   );
   assert.equal(cancels, 0);
   assertNoUnexpectedClientCalls();
@@ -475,7 +513,7 @@ test('completion rejects a terminal projection that changes Connection identity'
     isProviderEnabled: () => true,
   });
 
-  await invoke(handlers, 'openai-codex:get-auth-url');
+  await invoke(handlers, 'openai-codex:get-auth-url', { kind: 'create' });
   assert.deepEqual(await invoke(handlers, 'openai-codex:complete-authorization', attemptId), {
     ok: false,
     reason: 'unknown',
@@ -486,7 +524,7 @@ test('completion rejects a terminal projection that changes Connection identity'
   assertNoUnexpectedClientCalls();
 });
 
-test('keeps a committed OAuth login successful when model discovery fails', async () => {
+test('keeps a committed OAuth login successful when model discovery fails without replacing the existing default', async () => {
   const provider = 'openai-codex' as const;
   const modelId = PROVIDER_DEFAULTS[provider].fallbackModels[0];
   assert.ok(modelId);
@@ -507,7 +545,7 @@ test('keeps a committed OAuth login successful when model discovery fails', asyn
   };
   let catalog: ConnectionCatalogSnapshot = {
     revision: 1,
-    defaultTarget: null,
+    defaultTarget: { connectionId: existing.connectionId, modelId },
     connections: [existing],
   };
   const presentation = new RuntimeHostOAuthPresentation(async () => undefined);
@@ -550,11 +588,6 @@ test('keeps a committed OAuth login successful when model discovery fails', asyn
       fetchedConnectionIds.push(connectionId);
       throw new Error('provider temporarily unavailable');
     },
-    setDefaultConnectionTarget: async (expectedCatalogRevision, target) => {
-      assert.equal(expectedCatalogRevision, catalog.revision);
-      catalog = { ...catalog, revision: catalog.revision + 1, defaultTarget: target };
-      return { kind: 'committed' as const, catalogRevision: catalog.revision };
-    },
     queryCredential: async (locator) =>
       locator.scope === 'connection' && locator.connectionId === created.connectionId
         ? {
@@ -575,18 +608,34 @@ test('keeps a committed OAuth login successful when model discovery fails', asyn
     isProviderEnabled: () => true,
   });
 
-  assert.deepEqual(await invoke(handlers, 'openai-codex:get-auth-url'), {
+  assert.deepEqual(await invoke(handlers, 'openai-codex:get-auth-url', { kind: 'create' }), {
     authRequestId: attemptId,
     stateHint: 'DEVICE-CODE',
+    connection: {
+      connectionId: created.connectionId,
+      slug: created.slug,
+      providerType: provider,
+    },
   });
   assert.deepEqual(
     await invoke(handlers, 'openai-codex:complete-authorization', attemptId, undefined),
-    { ok: true },
+    {
+      ok: true,
+      connection: {
+        connectionId: created.connectionId,
+        slug: created.slug,
+        providerType: provider,
+      },
+    },
   );
   assert.equal(changed, 1);
   assert.deepEqual(fetchedConnectionIds, [created.connectionId]);
-  assert.deepEqual(catalog.defaultTarget, { connectionId: created.connectionId, modelId });
-  assert.deepEqual(await invoke(handlers, 'openai-codex:get-account-state'), {
+  assert.deepEqual(catalog.defaultTarget, { connectionId: existing.connectionId, modelId });
+  assert.deepEqual(await invoke(
+    handlers,
+    'openai-codex:get-account-state',
+    created.connectionId,
+  ), {
     provider,
     runtimeState: 'authenticated',
   });
@@ -655,7 +704,7 @@ function oauthProjection(
     attemptId,
     connection: {
       connectionId,
-      slug: 'codex-subscription',
+      slug: 'openai-codex',
       providerType: 'openai-codex' as const,
     },
     phase,
diff --git a/apps/desktop/src/main/runtime-host-connections-ipc-main.ts b/apps/desktop/src/main/runtime-host-connections-ipc-main.ts
index 78951d41c4..64ef266438 100644
--- a/apps/desktop/src/main/runtime-host-connections-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-connections-ipc-main.ts
@@ -51,7 +51,10 @@ import {
   normalizeConnectionSlugForIpc,
   normalizeCreateConnectionInputForIpc,
 } from './connections-ipc-validation.js';
-import type { DesktopConnectionSnapshot } from '../shared/desktop-connection-snapshot.js';
+import type {
+  DesktopConnectionIdentity,
+  DesktopConnectionSnapshot,
+} from '../shared/desktop-connection-snapshot.js';
 
 type HostConnectionsClient = Pick<
   DesktopRuntimeHostClient,
@@ -89,9 +92,9 @@ export function registerRuntimeHostConnectionsIpc(
       chatModelChoices: buildChatModelChoices(connections),
     } satisfies DesktopConnectionSnapshot;
   });
-  handleReconnectableRead(deps.ipcMain, 'connections:hasSecret', async (_event, slug: unknown) => {
+  handleReconnectableRead(deps.ipcMain, 'connections:hasSecret', async (_event, identity: unknown) => {
     const catalog = await snapshot();
-    const connection = requireConnection(catalog, slug);
+    const connection = requireConnectionIdentity(catalog, identity);
     if (!providerAuthRequiresSecret(connection.providerType)) return true;
     return (
       (await deps.client.queryCredential(connectionCredential(connection)))
@@ -101,8 +104,8 @@ export function registerRuntimeHostConnectionsIpc(
   handleReconnectableRead(
     deps.ipcMain,
     'connections:getRequestHeaders',
-    async (_event, slug: unknown) => {
-      const connection = requireConnection(await snapshot(), slug);
+    async (_event, identity: unknown) => {
+      const connection = requireConnectionIdentity(await snapshot(), identity);
       const result = await deps.client.getConnectionRequestHeaders(connection.connectionId);
       if (result.kind !== 'found') throw new Error('Connection no longer exists');
       return { names: result.names } satisfies SavedRequestHeaders;
@@ -110,8 +113,8 @@ export function registerRuntimeHostConnectionsIpc(
   );
   deps.ipcMain.handle(
     'connections:setRequestHeaders',
-    async (_event, slug: unknown, rawUpdates: unknown) => {
-      const connection = requireConnection(await snapshot(), slug);
+    async (_event, identity: unknown, rawUpdates: unknown) => {
+      const connection = requireConnectionIdentity(await snapshot(), identity);
       const result = await deps.client.replaceConnectionRequestHeaders(
         connection.connectionId,
         normalizeRequestHeaderUpdates(rawUpdates),
@@ -121,17 +124,28 @@ export function registerRuntimeHostConnectionsIpc(
       return { names: result.names } satisfies SavedRequestHeaders;
     },
   );
-  deps.ipcMain.handle('connections:setDefault', async (_event, slug: unknown) => {
+  deps.ipcMain.handle('connections:setDefault', async (_event, identity: unknown) => {
     const catalog = await snapshot();
-    const target = slug === null
+    const target = identity === null
       ? null
-      : defaultTargetForConnection(requireConnection(catalog, slug));
+      : defaultTargetForConnection(requireConnectionIdentity(catalog, identity));
     requireCommitted(
       await deps.client.setDefaultConnectionTarget(catalog.revision, target),
       'set default Connection',
     );
     deps.emitConnectionListChanged();
   });
+  deps.ipcMain.handle('connections:setDefaultBySlug', async (_event, slug: unknown) => {
+    const catalog = await snapshot();
+    requireCommitted(
+      await deps.client.setDefaultConnectionTarget(
+        catalog.revision,
+        defaultTargetForConnection(requireConnection(catalog, slug)),
+      ),
+      'set default Connection',
+    );
+    deps.emitConnectionListChanged();
+  });
   deps.ipcMain.handle('connections:setDefaultModel', async (_event, input: unknown) => {
     const catalog = await snapshot();
     const target = input === null ? null : explicitDefaultTarget(catalog, input);
@@ -193,9 +207,9 @@ export function registerRuntimeHostConnectionsIpc(
     deps.emitConnectionListChanged();
     return requireProjectedConnection(await snapshot(), input.slug);
   });
-  deps.ipcMain.handle('connections:update', async (_event, rawSlug: unknown, rawPatch: unknown) => {
+  deps.ipcMain.handle('connections:update', async (_event, rawIdentity: unknown, rawPatch: unknown) => {
     const catalog = await snapshot();
-    const current = requireConnection(catalog, rawSlug);
+    const current = requireConnectionIdentity(catalog, rawIdentity);
     const patch = normalizeUpdateInput(current, rawPatch);
     const updated = await deps.client.updateConnection(
       { connectionId: current.connectionId, revision: current.revision },
@@ -227,7 +241,7 @@ export function registerRuntimeHostConnectionsIpc(
     if (patch.apiKey !== undefined) await updateCredential(deps.client, current, patch.apiKey);
     if (patch.defaultModel !== undefined) {
       const latest = await snapshot();
-      const entry = requireConnection(latest, current.slug);
+      const entry = requireConnectionIdentity(latest, connectionIdentity(current));
       const target = patch.defaultModel
         ? { connectionId: entry.connectionId, modelId: patch.defaultModel }
         : latest.defaultTarget?.connectionId === entry.connectionId
@@ -239,9 +253,10 @@ export function registerRuntimeHostConnectionsIpc(
       );
     }
     deps.emitConnectionListChanged();
-    return requireProjectedConnection(await snapshot(), current.slug);
+    return requireProjectedConnectionIdentity(await snapshot(), connectionIdentity(current));
   });
-  deps.ipcMain.handle('connections:delete', async (_event, slug: unknown) => {
+  deps.ipcMain.handle('connections:delete', async (_event, rawIdentity: unknown) => {
+    const identity = normalizeConnectionIdentity(rawIdentity);
     // OAuth/model-fetch can bump the connection revision under the UI. Retry
     // on connection_stale with a fresh snapshot so delete does not fail with a
     // opaque "service unavailable" after the user already confirmed.
@@ -250,10 +265,11 @@ export function registerRuntimeHostConnectionsIpc(
       const catalog = await snapshot();
       let current: ReturnType<typeof requireConnection>;
       try {
-        current = requireConnection(catalog, slug);
+        current = requireConnectionIdentity(catalog, identity);
       } catch (error) {
-        // Only treat a missing slug as success. Invalid input must still fail.
-        if (error instanceof Error && error.message.startsWith('No such Connection:')) {
+        // The exact entity is already gone. A new entity may reuse its slug;
+        // deleting that replacement would violate the detail route's binding.
+        if (error instanceof Error && error.message.startsWith('No such Connection identity:')) {
           deps.emitConnectionListChanged();
           return;
         }
@@ -275,14 +291,14 @@ export function registerRuntimeHostConnectionsIpc(
       throw new Error('Unable to delete Connection: connection_stale');
     }
   });
-  deps.ipcMain.handle('connections:fetchModels', async (_event, slug: unknown) => {
-    const current = requireConnection(await snapshot(), slug);
+  deps.ipcMain.handle('connections:fetchModels', async (_event, identity: unknown) => {
+    const current = requireConnectionIdentity(await snapshot(), identity);
     const result = await deps.client.fetchConnectionModels(current.connectionId);
     if (result.kind !== 'committed') {
       throw new Error(`Unable to fetch Connection models: ${result.kind}`);
     }
     deps.emitConnectionListChanged();
-    const latest = requireConnection(await snapshot(), current.slug);
+    const latest = requireConnectionIdentity(await snapshot(), connectionIdentity(current));
     return {
       models: [...latest.models],
       source: result.source,
@@ -291,6 +307,19 @@ export function registerRuntimeHostConnectionsIpc(
   });
   deps.ipcMain.handle(
     'connections:test',
+    async (_event, identity: unknown, options?: { model?: unknown }) => {
+      const current = requireConnectionIdentity(await snapshot(), identity);
+      const model = options?.model;
+      if (model !== undefined && (typeof model !== 'string' || model.length === 0)) {
+        throw new Error('Invalid Connection test model');
+      }
+      const result = await deps.client.testConnection(current.connectionId, model);
+      deps.emitConnectionListChanged();
+      return projectHostConnectionTest(result);
+    },
+  );
+  deps.ipcMain.handle(
+    'connections:testBySlug',
     async (_event, slug: unknown, options?: { model?: unknown }) => {
       const current = requireConnection(await snapshot(), slug);
       const model = options?.model;
@@ -426,6 +455,43 @@ function requireConnection(
   return connection;
 }
 
+function normalizeConnectionIdentity(value: unknown): DesktopConnectionIdentity {
+  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
+    throw new Error('Invalid Connection identity');
+  }
+  const record = value as Record<string, unknown>;
+  const keys = Object.keys(record).sort();
+  if (keys.length !== 2 || keys[0] !== 'connectionId' || keys[1] !== 'slug') {
+    throw new Error('Invalid Connection identity');
+  }
+  if (typeof record.connectionId !== 'string' || record.connectionId.length === 0) {
+    throw new Error('Connection identity id is required');
+  }
+  return {
+    connectionId: record.connectionId,
+    slug: normalizeConnectionSlugForIpc(record.slug, 'connection identity slug'),
+  };
+}
+
+function requireConnectionIdentity(
+  catalog: ConnectionCatalogSnapshot,
+  value: unknown,
+): ConnectionCatalogEntry {
+  const identity = normalizeConnectionIdentity(value);
+  const connection = catalog.connections.find(
+    (candidate) => candidate.connectionId === identity.connectionId,
+  );
+  if (!connection) throw new Error(`No such Connection identity: ${identity.connectionId}`);
+  if (connection.slug !== identity.slug) {
+    throw new Error('Connection identity no longer matches its slug');
+  }
+  return connection;
+}
+
+function connectionIdentity(connection: ConnectionCatalogEntry): DesktopConnectionIdentity {
+  return { connectionId: connection.connectionId, slug: connection.slug };
+}
+
 function requireProjectedConnection(
   catalog: ConnectionCatalogSnapshot,
   slug: string,
@@ -435,6 +501,18 @@ function requireProjectedConnection(
   return connection;
 }
 
+function requireProjectedConnectionIdentity(
+  catalog: ConnectionCatalogSnapshot,
+  identity: DesktopConnectionIdentity,
+): IdentifiedLlmConnection {
+  const connection = requireConnectionIdentity(catalog, identity);
+  const projected = projectHostConnections(catalog).find(
+    (candidate) => candidate.connectionId === connection.connectionId,
+  );
+  if (!projected) throw new Error(`No such Connection identity: ${identity.connectionId}`);
+  return projected;
+}
+
 function defaultTargetForConnection(connection: ConnectionCatalogEntry) {
   const modelId = connection.enabledModelIds[0];
   if (!modelId) throw new Error(`Connection has no enabled model: ${connection.slug}`);
diff --git a/apps/desktop/src/main/runtime-host-oauth-ipc-main.ts b/apps/desktop/src/main/runtime-host-oauth-ipc-main.ts
index e151449c56..6fd139b3b3 100644
--- a/apps/desktop/src/main/runtime-host-oauth-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-oauth-ipc-main.ts
@@ -94,9 +94,9 @@ export function registerRuntimeHostOAuthIpc(deps: RuntimeHostOAuthIpcDeps): void
     if (provider !== 'xai-oauth') {
       deps.ipcMain.handle(channel('is-experimental-enabled'), () => providerEnabled(provider));
     }
-    deps.ipcMain.handle(channel('get-auth-url'), async (_event, rawConnectionId: unknown) => {
+    deps.ipcMain.handle(channel('get-auth-url'), async (_event, rawTarget: unknown) => {
       if (!providerEnabled(provider)) return providerDisabled();
-      const selection = decodeOAuthConnectionSelection(rawConnectionId);
+      const selection = decodeOAuthLoginSelection(rawTarget);
       if (selection.kind === 'invalid') return invalidConnectionIdentity();
       const connectionId = selection.kind === 'exact' ? selection.connectionId : undefined;
       if (connectionId) {
@@ -129,7 +129,11 @@ export function registerRuntimeHostOAuthIpc(deps: RuntimeHostOAuthIpcDeps): void
           provider,
           connection: started.connection,
         });
-        return { authRequestId: attemptId, stateHint: presented.stateHint };
+        return {
+          authRequestId: attemptId,
+          stateHint: presented.stateHint,
+          connection: started.connection,
+        };
       } catch (error) {
         expectation?.cancel(error);
         if (startedOnHost) {
@@ -176,7 +180,7 @@ export function registerRuntimeHostOAuthIpc(deps: RuntimeHostOAuthIpcDeps): void
             terminal.connection.connectionId,
           ).catch(() => undefined);
           deps.emitConnectionListChanged();
-          return { ok: true as const };
+          return { ok: true as const, connection: terminal.connection };
         } catch {
           activeAttempts.delete(attemptId);
           return actionFailure('Unable to complete OAuth authorization');
@@ -192,45 +196,44 @@ export function registerRuntimeHostOAuthIpc(deps: RuntimeHostOAuthIpcDeps): void
       return { ok: true as const };
     });
     handleReconnectableRead(deps.ipcMain, channel('get-account-state'), async (_event, rawConnectionId: unknown) => {
-      const selection = decodeOAuthConnectionSelection(rawConnectionId);
-      if (selection.kind === 'invalid') return invalidConnectionIdentity();
-      const connectionId = selection.kind === 'exact' ? selection.connectionId : undefined;
+      const connectionId = decodeExactOAuthConnectionId(rawConnectionId);
+      if (!connectionId) return invalidConnectionIdentity();
       const candidates = oauthAccountCandidates(
         await deps.client.loadConnectionCatalog(),
         provider,
         connectionId,
       );
+      if (candidates.length === 0) {
+        return actionFailure('OAuth account does not match this provider');
+      }
       const authorizing = [...activeAttempts.values()].some(
         (attempt) =>
           attempt.provider === provider &&
-          (connectionId === undefined || attempt.connection.connectionId === connectionId),
+          attempt.connection.connectionId === connectionId,
       );
-      if (candidates.length === 0) {
-        return accountState(provider, authorizing ? 'authorizing' : 'not_logged_in');
-      }
       if ((await configuredOAuthAccountConnections(deps.client, candidates)).length > 0) {
         return accountState(provider, 'authenticated');
       }
       return accountState(provider, authorizing ? 'authorizing' : 'not_logged_in');
     });
     deps.ipcMain.handle(channel('refresh-tokens'), async (_event, rawConnectionId: unknown) => {
-      const selection = decodeOAuthConnectionSelection(rawConnectionId);
-      if (selection.kind === 'invalid') return invalidConnectionIdentity('refresh_failed');
-      const connectionId = selection.kind === 'exact' ? selection.connectionId : undefined;
+      const connectionId = decodeExactOAuthConnectionId(rawConnectionId);
+      if (!connectionId) return invalidConnectionIdentity('refresh_failed');
+      const candidates = oauthAccountCandidates(
+        await deps.client.loadConnectionCatalog(),
+        provider,
+        connectionId,
+      );
+      if (candidates.length === 0) {
+        return actionFailure('OAuth account does not match this provider', 'refresh_failed');
+      }
       const connections = await configuredOAuthAccountConnections(
         deps.client,
-        oauthAccountCandidates(
-          await deps.client.loadConnectionCatalog(),
-          provider,
-          connectionId,
-        ),
+        candidates,
       );
       if (connections.length === 0) {
         return actionFailure('OAuth account is not connected', 'refresh_failed');
       }
-      if (connectionId === undefined && connections.length > 1) {
-        return actionFailure('Select a specific OAuth account to refresh', 'refresh_failed');
-      }
       const connection = connections[0]!;
       const refreshed = await deps.client.fetchConnectionModels(connection.connectionId);
       return refreshed.kind === 'committed'
@@ -238,34 +241,25 @@ export function registerRuntimeHostOAuthIpc(deps: RuntimeHostOAuthIpcDeps): void
         : actionFailure('Unable to refresh OAuth account', 'refresh_failed');
     });
     deps.ipcMain.handle(channel('logout'), async (_event, rawConnectionId: unknown) => {
-      const selection = decodeOAuthConnectionSelection(rawConnectionId);
-      if (selection.kind === 'invalid') return invalidConnectionIdentity();
-      const connectionId = selection.kind === 'exact' ? selection.connectionId : undefined;
+      const connectionId = decodeExactOAuthConnectionId(rawConnectionId);
+      if (!connectionId) return invalidConnectionIdentity();
       try {
         const candidates = oauthAccountCandidates(
           await deps.client.loadConnectionCatalog(),
           provider,
           connectionId,
         );
-        if (connectionId !== undefined && candidates.length === 0) {
+        if (candidates.length === 0) {
           return actionFailure('OAuth account does not match this provider');
         }
-        const connections =
-          connectionId !== undefined
-            ? candidates
-            : await configuredOAuthAccountConnections(deps.client, candidates);
-        if (connectionId === undefined && connections.length > 1) {
-          return actionFailure('Select a specific OAuth account to log out');
-        }
-        const connection = connections[0];
+        const connection = candidates[0]!;
         await cancelProviderAttempts(
           deps,
           activeAttempts,
           provider,
-          connection?.connectionId,
+          connection.connectionId,
         );
-        if (connection)
-          await disableRuntimeHostAccountConnectionById(deps.client, connection.connectionId);
+        await disableRuntimeHostAccountConnectionById(deps.client, connection.connectionId);
       } catch {
         return actionFailure('Unable to remove OAuth account');
       }
@@ -370,30 +364,46 @@ function accountState(
 function oauthAccountCandidates(
   catalog: Awaited<ReturnType<OAuthClient['loadConnectionCatalog']>>,
   provider: OAuthLoginProvider,
-  connectionId: string | undefined,
+  connectionId: string,
 ): ConnectionCatalogEntry[] {
-  if (connectionId !== undefined) {
-    const connection = findRuntimeHostAccountConnectionById(catalog, connectionId);
-    return connection?.providerType === provider ? [connection] : [];
-  }
-  return catalog.connections.filter((connection) => connection.providerType === provider);
+  const connection = findRuntimeHostAccountConnectionById(catalog, connectionId);
+  return connection?.providerType === provider ? [connection] : [];
 }
 
-type OAuthConnectionSelection =
-  | { readonly kind: 'aggregate' }
+type OAuthLoginSelection =
+  | { readonly kind: 'create' }
   | { readonly kind: 'exact'; readonly connectionId: string }
   | { readonly kind: 'invalid' };
 
-function decodeOAuthConnectionSelection(value: unknown): OAuthConnectionSelection {
-  if (value === undefined) return { kind: 'aggregate' };
-  if (typeof value !== 'string') return { kind: 'invalid' };
+function decodeOAuthLoginSelection(value: unknown): OAuthLoginSelection {
+  if (!value || typeof value !== 'object' || Array.isArray(value)) return { kind: 'invalid' };
+  const candidate = value as { readonly kind?: unknown; readonly connectionId?: unknown };
+  const keys = Object.keys(value).sort();
+  if (candidate.kind === 'create') {
+    return keys.length === 1 && keys[0] === 'kind' ? { kind: 'create' } : { kind: 'invalid' };
+  }
+  if (candidate.kind !== 'existing' || typeof candidate.connectionId !== 'string') {
+    return { kind: 'invalid' };
+  }
+  if (keys.length !== 2 || keys[0] !== 'connectionId' || keys[1] !== 'kind') {
+    return { kind: 'invalid' };
+  }
   try {
-    return { kind: 'exact', connectionId: decodeRuntimePolicyEntityId(value) };
+    return { kind: 'exact', connectionId: decodeRuntimePolicyEntityId(candidate.connectionId) };
   } catch {
     return { kind: 'invalid' };
   }
 }
 
+function decodeExactOAuthConnectionId(value: unknown): string | undefined {
+  if (typeof value !== 'string') return undefined;
+  try {
+    return decodeRuntimePolicyEntityId(value);
+  } catch {
+    return undefined;
+  }
+}
+
 function sameOAuthConnectionIdentity(
   left: OAuthConnectionIdentity,
   right: OAuthConnectionIdentity,
diff --git a/apps/desktop/src/preload/bridge-contract.d.ts b/apps/desktop/src/preload/bridge-contract.d.ts
index 99b2c77a63..1525a4c34e 100644
--- a/apps/desktop/src/preload/bridge-contract.d.ts
+++ b/apps/desktop/src/preload/bridge-contract.d.ts
@@ -352,6 +352,26 @@ export interface DesktopRuntimeHostRef {
   readonly hostId: string;
 }
 
+/** Desktop-local OAuth intent. Runtime Host remains the identity allocator. */
+export type DesktopOAuthLoginTarget =
+  | { readonly kind: 'create' }
+  | { readonly kind: 'existing'; readonly connectionId: string };
+
+/** Secret-free canonical identity returned by the Runtime Host OAuth flow. */
+export interface DesktopOAuthConnectionIdentity {
+  readonly connectionId: string;
+  readonly slug: string;
+  readonly providerType: 'openai-codex' | 'xai-oauth';
+}
+
+export type DesktopOAuthAuthorizationStartResult =
+  | (AuthorizationUrlPayload & { readonly connection: DesktopOAuthConnectionIdentity })
+  | Exclude<SubscriptionActionResult, { readonly ok: true }>;
+
+export type DesktopOAuthAuthorizationResult =
+  | { readonly ok: true; readonly connection: DesktopOAuthConnectionIdentity }
+  | Exclude<SubscriptionActionResult, { readonly ok: true }>;
+
 export type DesktopNewTaskHostRef = DesktopRuntimeHostRef;
 
 export interface DesktopNewTaskTarget extends DesktopRuntimeHostRef {
@@ -1315,17 +1335,17 @@ export interface MakaBridge {
   };
   connections: {
     getSnapshot(sessionId?: string, host?: DesktopRuntimeHostRef): Promise<DesktopConnectionSnapshot>;
-    setDefault(slug: string | null, host?: DesktopRuntimeHostRef): Promise<void>;
+    setDefault(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity | string | null, host?: DesktopRuntimeHostRef): Promise<void>;
     setDefaultModel(input: { slug: string; model: string } | null, host?: DesktopRuntimeHostRef): Promise<void>;
-    create(input: CreateConnectionInput, host?: DesktopRuntimeHostRef): Promise<LlmConnection>;
-    update(slug: string, patch: UpdateConnectionInput, host?: DesktopRuntimeHostRef): Promise<LlmConnection>;
-    delete(slug: string, host?: DesktopRuntimeHostRef): Promise<void>;
-    test(slug: string, opts?: { model?: string }, host?: DesktopRuntimeHostRef): Promise<ConnectionTestResult>;
-    fetchModels(slug: string, host?: DesktopRuntimeHostRef): Promise<ModelDiscoveryResult>;
-    hasSecret(slug: string, host?: DesktopRuntimeHostRef): Promise<boolean>;
-    getRequestHeaders(slug: string, host?: DesktopRuntimeHostRef): Promise<import('@maka/core/llm-connections').SavedRequestHeaders>;
+    create(input: CreateConnectionInput, host?: DesktopRuntimeHostRef): Promise<import('@maka/core/llm-connections').IdentifiedLlmConnection>;
+    update(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity, patch: UpdateConnectionInput, host?: DesktopRuntimeHostRef): Promise<LlmConnection>;
+    delete(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<void>;
+    test(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity | string, opts?: { model?: string }, host?: DesktopRuntimeHostRef): Promise<ConnectionTestResult>;
+    fetchModels(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<ModelDiscoveryResult>;
+    hasSecret(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<boolean>;
+    getRequestHeaders(connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<import('@maka/core/llm-connections').SavedRequestHeaders>;
     setRequestHeaders(
-      slug: string,
+      connection: import('../shared/desktop-connection-snapshot').DesktopConnectionIdentity,
       headers: readonly import('@maka/core/llm-connections').RequestHeaderUpdate[],
       host?: DesktopRuntimeHostRef,
     ): Promise<import('@maka/core/llm-connections').SavedRequestHeaders>;
@@ -1457,45 +1477,51 @@ export interface MakaBridge {
   };
   openAiCodex: {
     isExperimentalEnabled(host?: DesktopRuntimeHostRef): Promise<boolean>;
-    getAuthUrl(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<AuthorizationUrlPayload | SubscriptionActionResult>;
+    getAuthUrl(host: DesktopRuntimeHostRef | undefined, target: DesktopOAuthLoginTarget): Promise<DesktopOAuthAuthorizationStartResult>;
     openAuthUrl(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult>;
-    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult>;
+    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<DesktopOAuthAuthorizationResult>;
     cancelAuthorization(authRequestId?: string, host?: DesktopRuntimeHostRef): Promise<{ ok: true }>;
-    getAccountState(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<{
-      provider: 'openai-codex';
-      runtimeState:
-        | 'not_logged_in'
-        | 'authorizing'
-        | 'authenticated'
-        | 'refreshing'
-        | 'refresh_failed';
-      accountId?: string;
-      email?: string;
-      plan?: string;
-      picture?: string;
-      errorMessage?: string;
-    }>;
-    refreshTokens(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult>;
-    logout(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult>;
+    getAccountState(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<
+      | {
+          provider: 'openai-codex';
+          runtimeState:
+            | 'not_logged_in'
+            | 'authorizing'
+            | 'authenticated'
+            | 'refreshing'
+            | 'refresh_failed';
+          accountId?: string;
+          email?: string;
+          plan?: string;
+          picture?: string;
+          errorMessage?: string;
+        }
+      | Exclude<SubscriptionActionResult, { readonly ok: true }>
+    >;
+    refreshTokens(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult>;
+    logout(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult>;
   };
   xaiOAuth: {
-    getAuthUrl(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<AuthorizationUrlPayload | SubscriptionActionResult>;
+    getAuthUrl(host: DesktopRuntimeHostRef | undefined, target: DesktopOAuthLoginTarget): Promise<DesktopOAuthAuthorizationStartResult>;
     openAuthUrl(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult>;
-    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult>;
+    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<DesktopOAuthAuthorizationResult>;
     cancelAuthorization(authRequestId?: string, host?: DesktopRuntimeHostRef): Promise<{ ok: true }>;
-    getAccountState(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<{
-      provider: 'xai-oauth';
-      runtimeState:
-        | 'not_logged_in'
-        | 'authorizing'
-        | 'authenticated'
-        | 'refreshing'
-        | 'refresh_failed'
-        | 'storage_failed';
-      errorMessage?: string;
-    }>;
-    refreshTokens(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult>;
-    logout(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult>;
+    getAccountState(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<
+      | {
+          provider: 'xai-oauth';
+          runtimeState:
+            | 'not_logged_in'
+            | 'authorizing'
+            | 'authenticated'
+            | 'refreshing'
+            | 'refresh_failed'
+            | 'storage_failed';
+          errorMessage?: string;
+        }
+      | Exclude<SubscriptionActionResult, { readonly ok: true }>
+    >;
+    refreshTokens(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult>;
+    logout(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult>;
   };
   githubCopilotSubscription: {
     connectExistingLogin(host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult>;
diff --git a/apps/desktop/src/preload/preload.ts b/apps/desktop/src/preload/preload.ts
index 48503d5716..5a64dd317a 100644
--- a/apps/desktop/src/preload/preload.ts
+++ b/apps/desktop/src/preload/preload.ts
@@ -62,6 +62,8 @@ import type {
   DesktopNewTaskHostRef,
   DesktopNewTaskTarget,
   DesktopRuntimeHostRef,
+  DesktopOAuthLoginTarget,
+  DesktopOAuthAuthorizationResult,
   DesktopProjectSnapshot,
   DesktopAppInfo,
   DesktopSessionTracePage,
@@ -159,10 +161,7 @@ import type {
 } from '@maka/core/artifacts';
 import type { CapabilitySnapshotCollection, PermissionSnapshot } from '@maka/core/capabilities';
 import type { LocalMemoryState } from '@maka/core/local-memory';
-import type {
-  AuthorizationUrlPayload,
-  SubscriptionActionResult,
-} from '@maka/core/oauth-subscription';
+import type { SubscriptionActionResult } from '@maka/core/oauth-subscription';
 import type { CreateScheduledTaskInput, ScheduledTask, UpdateScheduledTaskInput } from '@maka/core/scheduled-task';
 import type { ProjectRecord } from '@maka/core/project';
 import type {
@@ -2584,39 +2583,48 @@ const makaBridge = {
         ? invokeRuntimeHostForSession('connections:getSnapshot', sessionId)
         : invokeSelectedRuntimeHost(host, 'connections:getSnapshot');
     },
-    setDefault(slug: string | null, host?: DesktopRuntimeHostRef): Promise<void> {
-      return invokeSelectedRuntimeHost(host, 'connections:setDefault', slug);
+    setDefault(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity | string | null, host?: DesktopRuntimeHostRef): Promise<void> {
+      return invokeSelectedRuntimeHost(
+        host,
+        typeof connection === 'string' ? 'connections:setDefaultBySlug' : 'connections:setDefault',
+        connection,
+      );
     },
     setDefaultModel(input: { slug: string; model: string } | null, host?: DesktopRuntimeHostRef): Promise<void> {
       return invokeSelectedRuntimeHost(host, 'connections:setDefaultModel', input);
     },
-    create(input: CreateConnectionInput, host?: DesktopRuntimeHostRef): Promise<LlmConnection> {
+    create(input: CreateConnectionInput, host?: DesktopRuntimeHostRef): Promise<import('@maka/core/llm-connections').IdentifiedLlmConnection> {
       return invokeSelectedRuntimeHost(host, 'connections:create', input);
     },
-    update(slug: string, patch: UpdateConnectionInput, host?: DesktopRuntimeHostRef): Promise<LlmConnection> {
-      return invokeSelectedRuntimeHost(host, 'connections:update', slug, patch);
+    update(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity, patch: UpdateConnectionInput, host?: DesktopRuntimeHostRef): Promise<LlmConnection> {
+      return invokeSelectedRuntimeHost(host, 'connections:update', connection, patch);
     },
-    delete(slug: string, host?: DesktopRuntimeHostRef): Promise<void> {
-      return invokeSelectedRuntimeHost(host, 'connections:delete', slug);
+    delete(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<void> {
+      return invokeSelectedRuntimeHost(host, 'connections:delete', connection);
     },
-    test(slug: string, opts?: { model?: string }, host?: DesktopRuntimeHostRef): Promise<ConnectionTestResult> {
-      return invokeSelectedRuntimeHost(host, 'connections:test', slug, opts);
+    test(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity | string, opts?: { model?: string }, host?: DesktopRuntimeHostRef): Promise<ConnectionTestResult> {
+      return invokeSelectedRuntimeHost(
+        host,
+        typeof connection === 'string' ? 'connections:testBySlug' : 'connections:test',
+        connection,
+        opts,
+      );
     },
-    fetchModels(slug: string, host?: DesktopRuntimeHostRef): Promise<ModelDiscoveryResult> {
-      return invokeSelectedRuntimeHost(host, 'connections:fetchModels', slug);
+    fetchModels(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<ModelDiscoveryResult> {
+      return invokeSelectedRuntimeHost(host, 'connections:fetchModels', connection);
     },
-    hasSecret(slug: string, host?: DesktopRuntimeHostRef): Promise<boolean> {
-      return invokeSelectedRuntimeHost(host, 'connections:hasSecret', slug);
+    hasSecret(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<boolean> {
+      return invokeSelectedRuntimeHost(host, 'connections:hasSecret', connection);
     },
-    getRequestHeaders(slug: string, host?: DesktopRuntimeHostRef): Promise<import('@maka/core/llm-connections').SavedRequestHeaders> {
-      return invokeSelectedRuntimeHost(host, 'connections:getRequestHeaders', slug);
+    getRequestHeaders(connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity, host?: DesktopRuntimeHostRef): Promise<import('@maka/core/llm-connections').SavedRequestHeaders> {
+      return invokeSelectedRuntimeHost(host, 'connections:getRequestHeaders', connection);
     },
     setRequestHeaders(
-      slug: string,
+      connection: import('../shared/desktop-connection-snapshot.js').DesktopConnectionIdentity,
       headers: readonly import('@maka/core/llm-connections').RequestHeaderUpdate[],
       host?: DesktopRuntimeHostRef,
     ): Promise<import('@maka/core/llm-connections').SavedRequestHeaders> {
-      return invokeSelectedRuntimeHost(host, 'connections:setRequestHeaders', slug, headers);
+      return invokeSelectedRuntimeHost(host, 'connections:setRequestHeaders', connection, headers);
     },
     subscribeEvents(handler: (event: ConnectionEvent) => void, host?: DesktopRuntimeHostRef): () => void {
       return host
@@ -2822,19 +2830,19 @@ const makaBridge = {
     isExperimentalEnabled(host?: DesktopRuntimeHostRef): Promise<boolean> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:is-experimental-enabled');
     },
-    getAuthUrl(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<AuthorizationUrlPayload | SubscriptionActionResult> {
-      return invokeSelectedRuntimeHost(host, 'openai-codex:get-auth-url', connectionId);
+    getAuthUrl(host: DesktopRuntimeHostRef | undefined, target: DesktopOAuthLoginTarget) {
+      return invokeSelectedRuntimeHost(host, 'openai-codex:get-auth-url', target);
     },
     openAuthUrl(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:open-auth-url', authRequestId);
     },
-    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult> {
+    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<DesktopOAuthAuthorizationResult> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:complete-authorization', authRequestId);
     },
     cancelAuthorization(authRequestId?: string, host?: DesktopRuntimeHostRef): Promise<{ ok: true }> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:cancel-authorization', authRequestId);
     },
-    getAccountState(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<{
+    getAccountState(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<{
       provider: 'openai-codex';
       runtimeState: 'not_logged_in' | 'authorizing' | 'authenticated' | 'refreshing' | 'refresh_failed';
       accountId?: string;
@@ -2845,27 +2853,27 @@ const makaBridge = {
     }> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:get-account-state', connectionId);
     },
-    refreshTokens(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult> {
+    refreshTokens(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:refresh-tokens', connectionId);
     },
-    logout(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult> {
+    logout(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult> {
       return invokeSelectedRuntimeHost(host, 'openai-codex:logout', connectionId);
     },
   },
   xaiOAuth: {
-    getAuthUrl(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<AuthorizationUrlPayload | SubscriptionActionResult> {
-      return invokeSelectedRuntimeHost(host, 'xai-oauth:get-auth-url', connectionId);
+    getAuthUrl(host: DesktopRuntimeHostRef | undefined, target: DesktopOAuthLoginTarget) {
+      return invokeSelectedRuntimeHost(host, 'xai-oauth:get-auth-url', target);
     },
     openAuthUrl(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult> {
       return invokeSelectedRuntimeHost(host, 'xai-oauth:open-auth-url', authRequestId);
     },
-    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<SubscriptionActionResult> {
+    completeAuthorization(authRequestId: string, host?: DesktopRuntimeHostRef): Promise<DesktopOAuthAuthorizationResult> {
       return invokeSelectedRuntimeHost(host, 'xai-oauth:complete-authorization', authRequestId);
     },
     cancelAuthorization(authRequestId?: string, host?: DesktopRuntimeHostRef): Promise<{ ok: true }> {
       return invokeSelectedRuntimeHost(host, 'xai-oauth:cancel-authorization', authRequestId);
     },
-    getAccountState(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<{
+    getAccountState(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<{
       provider: 'xai-oauth';
       runtimeState:
         | 'not_logged_in'
@@ -2878,10 +2886,10 @@ const makaBridge = {
     }> {
       return invokeSelectedRuntimeHost(host, 'xai-oauth:get-account-state', connectionId);
     },
-    refreshTokens(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult> {
+    refreshTokens(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult> {
       return invokeSelectedRuntimeHost(host, 'xai-oauth:refresh-tokens', connectionId);
     },
-    logout(host?: DesktopRuntimeHostRef, connectionId?: string): Promise<SubscriptionActionResult> {
+    logout(host: DesktopRuntimeHostRef | undefined, connectionId: string): Promise<SubscriptionActionResult> {
       return invokeSelectedRuntimeHost(host, 'xai-oauth:logout', connectionId);
     },
   },
diff --git a/apps/desktop/src/renderer/locales/settings-provider-copy.ts b/apps/desktop/src/renderer/locales/settings-provider-copy.ts
index 6a2633bd39..c2ad504ded 100644
--- a/apps/desktop/src/renderer/locales/settings-provider-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-provider-copy.ts
@@ -176,7 +176,9 @@ const zhCopy = {
   panel: {
     tabs: { all: '全部', recommended: '推荐', accounts: '账号', plans: '模型计划', api: 'API', aggregators: '聚合服务', local: '本地' },
     loadFailed: '载入模型连接失败', loadingAria: '正在加载模型供应商', connections: '模型连接',
-    retry: '点击重试。', empty: '还没有模型连接',
+    retry: '点击重试。', empty: '还没有模型连接', connectionRemoved: '原连接已被删除或移除,已返回模型连接列表。',
+    connectedLoading: '账号已连接,正在载入新的模型连接…', connectedLoadFailed: '账号已连接,但暂时无法载入新的模型连接。',
+    connectionIdentityChanged: '新连接的身份与登录结果不一致,请返回连接列表后重试。',
     emptyHelp: '从下方选择一种连接方式开始。', default: '默认', setDefault: '设为默认', setDefaultTitle: '让新任务默认使用这个连接', setDefaultPending: '设置中…', setDefaultFailed: '设为默认失败', addHelp: '选择账号登录、模型计划、API、聚合服务或本地运行时。',
     categoriesAria: '模型供应商分类', searchPlaceholder: '搜索服务商', searchAria: '搜索模型服务商', noMatch: '没有匹配的服务商', clearSearch: '清除搜索',
     createSubtitle: '完成必要配置后,连接会出现在模型页上方。', connection: '模型连接',
@@ -214,18 +216,19 @@ const zhCopy = {
     logoutTitle: (name: string) => `退出 ${name} 登录?`,
   },
   oauthSection: {
-    signedIn: '已登录', codexDescription: 'ChatGPT Plus / Pro 订阅账号登录。', xaiDescription: 'SuperGrok / X Premium 账号登录。',
+    signedIn: '已登录', codexDescription: '使用 ChatGPT Plus / Pro 账号添加连接。', xaiDescription: '使用 SuperGrok / X Premium 账号添加连接。',
+    configuredConnections: (count: number) => `已有 ${count} 个连接 · 添加另一个账号`,
     copilotDescription: '导入兼容 GitHub 凭据连接 Copilot 订阅。', serviceUnavailable: '登录服务暂时不可用,请检查网络后重试。',
     aria: 'OAuth 登录',
     staleState: 'OAuth 登录状态暂时没刷新成功,已保留上一次状态。',
     codexDetail: '点击下方按钮打开设备授权页,并在页面中输入这里显示的登录码。', xaiDetail: '点击下方按钮打开浏览器登录,授权完成后会自动回写。', deviceCode: '登录码:',
-    openingBrowser: '打开浏览器…', logout: '退出登录', loggingOut: '退出中…',
+    openingBrowser: '打开浏览器…', waitingAuthorization: '等待浏览器授权…', logout: '退出登录', loggingOut: '退出中…',
     copilotSubtitle: '导入兼容的 GitHub 登录;token 不会暴露给渲染进程。', copilotImported: '已导入 GitHub Copilot 订阅账号。',
     copilotSetup: '请配置具有 Copilot Requests 权限的 fine-grained PAT;普通 gh auth login 可能不包含该权限。', importing: '导入中…',
     reimport: '重新导入', importCredential: '导入兼容凭据', verifying: '验证中…', reverify: '重新验证', removing: '移除中…', removeLocal: '移除本地登录',
     loadingAccount: '正在加载账号状态…', authorizing: '请在弹出的浏览器窗口完成登录。', refreshing: '正在刷新访问令牌…', refreshTokenFailed: '令牌刷新失败,请重新登录。',
-    cardAria: (name: string, status: string | undefined, description: string) => `打开 OAuth 登录:${name}${status ? `,状态:${status}` : ''},${description.replace(/[。.!!??]+$/u, '')}`,
-    connectTitle: (name: string) => `连接 ${name}`, login: (name: string) => `登录 ${name}`, signedOut: (name: string) => `${name} 尚未登录。`,
+    cardAria: (intent: 'add' | 'import' | 'manage', name: string, status: string | undefined, description: string) => `${intent === 'add' ? '添加' : intent === 'import' ? '导入' : '管理'}账号连接:${name}${status ? `,状态:${status}` : ''},${description.replace(/[。.!!??]+$/u, '')}`,
+    connectTitle: (name: string) => `连接 ${name}`, addAccountTitle: (name: string) => `添加 ${name} 账号`, login: (name: string) => `登录 ${name}`, loginAndAdd: '登录并添加', signedOut: (name: string) => `${name} 尚未登录。`,
     storageFailed: (name: string) => `${name} 本地凭据读取失败,请重新登录。`, providerUnavailable: (name: string) => `${name} 已登录,但当前 provider 状态不可用。`,
   },
 } as const;
@@ -329,7 +332,9 @@ const enCopy: ProviderSettingsCopy = {
   panel: {
     tabs: { all: 'All', recommended: 'Recommended', accounts: 'Accounts', plans: 'Model plans', api: 'API', aggregators: 'Aggregators', local: 'Local' },
     loadFailed: 'Failed to load model connections', loadingAria: 'Loading model providers', connections: 'Connections',
-    retry: 'Select to retry.', empty: 'No model connections yet',
+    retry: 'Select to retry.', empty: 'No model connections yet', connectionRemoved: 'The original connection was deleted or removed. Returned to the connection list.',
+    connectedLoading: 'Account connected. Loading the new model connection…', connectedLoadFailed: 'Account connected, but the new model connection could not be loaded yet.',
+    connectionIdentityChanged: 'The new connection identity did not match the sign-in result. Return to the connection list and try again.',
     emptyHelp: 'Choose a connection method below to begin.', default: 'Default', setDefault: 'Set as default', setDefaultTitle: 'New chats will use this connection', setDefaultPending: 'Setting…', setDefaultFailed: 'Could not set as default', addHelp: 'Choose account sign-in, a model plan, API, aggregator, or local runtime.',
     categoriesAria: 'Model provider categories', searchPlaceholder: 'Search providers', searchAria: 'Search model providers', noMatch: 'No matching providers', clearSearch: 'Clear search',
     createSubtitle: 'After required setup, the connection appears above on the Models page.', connection: 'Model connection',
@@ -367,18 +372,19 @@ const enCopy: ProviderSettingsCopy = {
     logoutTitle: (name: string) => `Sign out of ${name}?`,
   },
   oauthSection: {
-    signedIn: 'Signed in', codexDescription: 'Sign in with a ChatGPT Plus / Pro subscription.', xaiDescription: 'Sign in with SuperGrok or X Premium.',
+    signedIn: 'Signed in', codexDescription: 'Use a ChatGPT Plus / Pro account to add a connection.', xaiDescription: 'Use a SuperGrok or X Premium account to add a connection.',
+    configuredConnections: (count: number) => `${count} ${count === 1 ? 'connection' : 'connections'} configured · Add another account`,
     copilotDescription: 'Import compatible GitHub credentials to connect a Copilot subscription.', serviceUnavailable: 'The sign-in service is temporarily unavailable. Check the network and try again.',
     aria: 'OAuth sign-in',
     staleState: 'OAuth sign-in status could not be refreshed. The last known state is preserved. ',
     codexDetail: 'Open the device page below and enter the sign-in code shown here.', xaiDetail: 'Open the browser below to sign in. Authorization is written back automatically.', deviceCode: 'Sign-in code:',
-    openingBrowser: 'Opening browser…', logout: 'Sign out', loggingOut: 'Signing out…',
+    openingBrowser: 'Opening browser…', waitingAuthorization: 'Waiting for browser authorization…', logout: 'Sign out', loggingOut: 'Signing out…',
     copilotSubtitle: 'Import a compatible GitHub sign-in. The token is never exposed to the renderer.', copilotImported: 'GitHub Copilot subscription account imported.',
     copilotSetup: 'Configure a fine-grained PAT with Copilot Requests permission. A normal gh auth login may not include it.', importing: 'Importing…',
     reimport: 'Reimport', importCredential: 'Import compatible credentials', verifying: 'Verifying…', reverify: 'Verify again', removing: 'Removing…', removeLocal: 'Remove local sign-in',
     loadingAccount: 'Loading account status…', authorizing: 'Complete sign-in in the browser window.', refreshing: 'Refreshing access token…', refreshTokenFailed: 'Token refresh failed. Sign in again.',
-    cardAria: (name: string, status: string | undefined, description: string) => `Open OAuth sign-in: ${name}${status ? `; status: ${status}` : ''}; ${description.replace(/[。.!!??]+$/u, '')}`,
-    connectTitle: (name: string) => `Connect ${name}`, login: (name: string) => `Sign in to ${name}`, signedOut: (name: string) => `${name} is signed out.`,
+    cardAria: (intent: 'add' | 'import' | 'manage', name: string, status: string | undefined, description: string) => `${intent === 'add' ? 'Add' : intent === 'import' ? 'Import' : 'Manage'} account connection: ${name}${status ? `; status: ${status}` : ''}; ${description.replace(/[。.!!??]+$/u, '')}`,
+    connectTitle: (name: string) => `Connect ${name}`, addAccountTitle: (name: string) => `Add ${name} account`, login: (name: string) => `Sign in to ${name}`, loginAndAdd: 'Sign in and add', signedOut: (name: string) => `${name} is signed out.`,
     storageFailed: (name: string) => `Could not read local credentials for ${name}. Sign in again.`, providerUnavailable: (name: string) => `${name} is signed in, but the provider status is currently unavailable.`,
   },
 };
diff --git a/apps/desktop/src/renderer/settings/provider-add-submission.ts b/apps/desktop/src/renderer/settings/provider-add-submission.ts
index 0b14d65eab..8b3b8ec734 100644
--- a/apps/desktop/src/renderer/settings/provider-add-submission.ts
+++ b/apps/desktop/src/renderer/settings/provider-add-submission.ts
@@ -25,7 +25,7 @@ import {
   validateSlug,
   type ProviderType,
 } from '@maka/core/llm-connections';
-import type { CreateConnectionInput, LlmConnection } from '@maka/core/llm-connections';
+import type { CreateConnectionInput, IdentifiedLlmConnection } from '@maka/core/llm-connections';
 
 /**
  * The two decisions 添加连接 makes that are not layout: which fields a provider
@@ -93,7 +93,7 @@ export function validateAddProviderDraft(draft: AddProviderDraft): AddProviderIs
 }
 
 export interface CreatedProvider {
-  readonly connection: LlmConnection;
+  readonly connection: IdentifiedLlmConnection;
   /**
    * Present when the catalog fetch that follows creation threw. The connection
    * exists either way — discovery is a convenience on top of a successful
@@ -104,8 +104,8 @@ export interface CreatedProvider {
 }
 
 export interface ProviderCreationBridge {
-  create(input: CreateConnectionInput): Promise<LlmConnection>;
-  fetchModels(slug: string): Promise<unknown>;
+  create(input: CreateConnectionInput): Promise<IdentifiedLlmConnection>;
+  fetchModels(connection: { readonly connectionId: string; readonly slug: string }): Promise<unknown>;
 }
 
 /**
@@ -123,7 +123,7 @@ export async function createProviderWithDiscovery(
   const connection = await bridge.create(input);
   if (!providerSupportsModelDiscovery(input.providerType)) return { connection };
   try {
-    await bridge.fetchModels(connection.slug);
+    await bridge.fetchModels({ connectionId: connection.connectionId, slug: connection.slug });
   } catch (modelDiscoveryError) {
     return { connection, modelDiscoveryError };
   }
diff --git a/apps/desktop/src/renderer/settings/provider-catalog-page.tsx b/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
index 364407fcfa..b1d3c8ecad 100644
--- a/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
+++ b/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
@@ -34,7 +34,7 @@ import {
   type ProviderCatalogGroup,
   type ProviderType,
 } from '@maka/core/provider-registry';
-import { PROVIDER_DEFAULTS } from '@maka/core/llm-connections';
+import { PROVIDER_DEFAULTS, type LlmConnection } from '@maka/core/llm-connections';
 import { Button, TextInput, useUiLocale } from '@maka/ui';
 import { AddProviderForm } from './provider-add-form';
 import { ProviderLogo, providerDisplay } from './provider-display';
@@ -66,6 +66,12 @@ export interface CatalogFilter {
 
 export const CATALOG_INITIAL_FILTER: CatalogFilter = { query: '', category: 'all' };
 
+export interface CreatedOAuthConnectionIdentity {
+  connectionId: string;
+  slug: string;
+  providerType: 'openai-codex' | 'xai-oauth';
+}
+
 /**
  * One provider being set up. `credentials` is a form, `account` is a browser
  * login — two bodies of one level, not two levels: they are reached the same
@@ -87,6 +93,7 @@ export type SetupTarget =
  */
 export function ProviderCatalogPage(props: {
   filter: CatalogFilter;
+  connections: readonly LlmConnection[];
   onFilterChange(filter: CatalogFilter): void;
   onPick(target: SetupTarget): void;
 }) {
@@ -96,7 +103,10 @@ export function ProviderCatalogPage(props: {
   const catalogCopy = providerCopy.catalog;
   const { query, category } = props.filter;
   const showsOAuth = category === 'all' || category === 'recommended' || category === 'accounts';
-  const oauth = useOAuthCards({ query: showsOAuth ? query : undefined });
+  const oauth = useOAuthCards({
+    query: showsOAuth ? query : undefined,
+    connections: props.connections,
+  });
 
   // Category is a Selector, not a second TabList: the page header already owns
   // one level of navigation, and six tabs beside a search field made the
@@ -161,7 +171,12 @@ export function ProviderCatalogPage(props: {
               data-status="ready"
               data-logged-in={card.isLoggedIn ? 'true' : undefined}
               startContent={<ProviderLogo type={card.providerType} />}
-              label={/* a11y-allow: this label names the ROW, not the span. Astryx's Item puts consumer props on its outer wrapper and renders a separate invisible <button> for the click target, so an aria-label on the Item never reaches that button — measured. The button is named from its content, and this span is how the status reaches that name. Removing it drops the runtime error from the row's accessible name (settings.spec:226).*/ <span aria-label={providerCopy.oauthSection.cardAri [...]
+              label={/* a11y-allow: this label names the ROW, not the span. Astryx's Item puts consumer props on its outer wrapper and renders a separate invisible <button> for the click target, so an aria-label on the Item never reaches that button — measured. The button is named from its content, and this span is how the status reaches that name. Removing it drops the runtime error from the row's accessible name (settings.spec:226).*/ <span aria-label={providerCopy.oauthSection.cardAria(
+                card.id === 'github-copilot' ? card.isLoggedIn ? 'manage' : 'import' : 'add',
+                card.name,
+                card.status,
+                card.description,
+              )}>{card.name}</span>}
               description={card.description}
               endContent={(
                 <HStack gap={2} vAlign="center">
@@ -205,9 +220,8 @@ export function ProviderCatalogPage(props: {
 }
 
 /**
- * Level 3: one provider being set up — its credential form, or its account
- * login. Named `setup` rather than `create` because the account body also
- * signs out and re-authorizes; it is not a one-way create.
+ * Level 3: one provider being set up — its credential form, or a new account
+ * enrollment. Existing-account actions live on that Connection's detail page.
  */
 export function ProviderSetupPage(props: {
   bridge: ConnectionsBridge;
@@ -215,17 +229,18 @@ export function ProviderSetupPage(props: {
   existingSlugs: string[];
   onCancel(): void;
   onCreated(slug: string, modelDiscoveryError?: unknown): Promise<void>;
-  onAccountChanged(): Promise<void>;
+  onAccountCreated(connection?: CreatedOAuthConnectionIdentity): Promise<void>;
+  labelledBy?: string;
 }) {
   if (props.target.method === 'account') {
     return (
-      <div tabIndex={-1} className="settingsRouteLevel" data-maka-contract="provider-setup">
-        <OAuthLoginPanel cardId={props.target.cardId} onLoginSuccess={props.onAccountChanged} />
+      <div tabIndex={-1} role="region" aria-labelledby={props.labelledBy} className="settingsRouteLevel" data-maka-contract="provider-setup">
+        <OAuthLoginPanel cardId={props.target.cardId} onLoginSuccess={props.onAccountCreated} />
       </div>
     );
   }
   return (
-    <div tabIndex={-1} className="settingsRouteLevel" data-maka-contract="provider-setup">
+    <div tabIndex={-1} role="region" aria-labelledby={props.labelledBy} className="settingsRouteLevel" data-maka-contract="provider-setup">
       <AddProviderForm
         key={props.target.providerType}
         bridge={props.bridge}
diff --git a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
index d54149911e..c3d69a8627 100644
--- a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
+++ b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
@@ -116,7 +116,7 @@ function UnknownConnectionDetail({ props }: { props: ConnectionDetailProps }) {
     if (!mounted.current || !ok) return;
     setDeleting(true);
     try {
-      await props.bridge.delete(connection.slug);
+      await props.bridge.delete({ connectionId: connection.connectionId, slug: connection.slug });
       if (!mounted.current) return;
       await props.onDeleted();
     } catch (error) {
@@ -257,7 +257,7 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
     setHeaderDrafts([]);
     setBodyDraft(formatRequestBodyOverlay(connection.requestBodyOverlay));
     void props.bridge
-      .getRequestHeaders(connection.slug)
+      .getRequestHeaders({ connectionId: connection.connectionId, slug: connection.slug })
       .then(({ names }) => {
         if (!current) return;
         setSavedHeaderNames(names);
@@ -301,7 +301,10 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
     }
     setRequestCustomizationBusy(true);
     try {
-      const saved = await props.bridge.setRequestHeaders(connection.slug, updates);
+      const saved = await props.bridge.setRequestHeaders(
+        { connectionId: connection.connectionId, slug: connection.slug },
+        updates,
+      );
       if (!mounted.current) return true;
       setSavedHeaderNames(saved.names);
       setHeaderDrafts(savedRequestHeaderDrafts(saved.names));
@@ -330,7 +333,10 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
     }
     setRequestCustomizationBusy(true);
     try {
-      await props.bridge.update(connection.slug, { requestBodyOverlay: overlay ?? null });
+      await props.bridge.update(
+        { connectionId: connection.connectionId, slug: connection.slug },
+        { requestBodyOverlay: overlay ?? null },
+      );
       await props.onChanged();
       return true;
     } catch (error) {
@@ -1104,9 +1110,12 @@ function OAuthReloginNoticeForCurrentGeneration(props: {
   const providerCopy = getProviderSettingsCopy(useUiLocale());
   const copy = providerCopy.detail;
   const flow = useOAuthLoginFlow({
-    bridge: props.service.bridge,
+    mode: 'existing',
+    authorizationBridge: props.service.authorizationBridge,
+    accountBridge: props.service.accountBridge,
     display: props.service.display,
-    onLoginSuccess: props.onRelogin,
+    onLoginSuccess: () => props.onRelogin(),
+    onAccountChanged: props.onRelogin,
   });
   const { hasSecret } = props;
   const loggedIn = hasSecret === true;
@@ -1139,6 +1148,7 @@ function OAuthReloginNoticeForCurrentGeneration(props: {
         </>
       ) : detail}
       endContent={!loading ? (
+        <HStack gap={2}>
           <Button
             variant="primary"
             size="sm"
@@ -1146,6 +1156,18 @@ function OAuthReloginNoticeForCurrentGeneration(props: {
             onClick={() => void flow.startLogin()}
             label={flow.pendingAction === 'login' ? copy.loggingIn : loggedIn ? copy.relogin : copy.login}
           />
+          {loggedIn && flow.logout && (
+            <Button
+              variant="ghost"
+              size="sm"
+              isDisabled={flow.actionBusy}
+              onClick={() => void flow.logout?.()}
+              label={flow.pendingAction === 'logout'
+                ? providerCopy.oauthSection.loggingOut
+                : providerCopy.oauthSection.logout}
+            />
+          )}
+        </HStack>
       ) : undefined} />
   );
 }
diff --git a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
index 10d86d72f6..d1eb34de33 100644
--- a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
+++ b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
@@ -19,7 +19,7 @@
 
 import { useEffect, useRef, useState } from 'react';
 import { Banner, HStack, Text, VStack } from '@astryxdesign/core';
-import { type ProviderType } from '@maka/core/llm-connections';
+import { type LlmConnection, type ProviderType } from '@maka/core/llm-connections';
 import type { DesktopRuntimeHostRef } from '../../preload/bridge-contract.js';
 import {
   Badge,
@@ -31,8 +31,7 @@ import { getProviderSettingsCopy, type ProviderSettingsCopy } from '../locales/s
 import {
   useOAuthLoginFlow,
   subscriptionActionErrorMessage,
-  subscriptionResultMessage,
-  type OAuthLoginFlowBridge,
+  type OAuthConnectionIdentity,
   type SubscriptionSnapshot,
 } from './use-oauth-login-flow';
 import {
@@ -40,7 +39,9 @@ import {
   useRuntimeHostSettingsGenerationKey,
   useRuntimeHostSettingsTarget,
 } from './runtime-host-settings-target.js';
-import { runtimeHostOAuthLoginBridge } from './runtime-host-settings-bridge.js';
+import {
+  runtimeHostOAuthAuthorizationBridge,
+} from './runtime-host-settings-bridge.js';
 
 export type OAuthCardId = 'codex' | 'github-copilot' | 'xai';
 
@@ -48,7 +49,7 @@ export interface OAuthCard {
   id: OAuthCardId;
   providerType: ProviderType;
   name: string;
-  /** Account email once signed in, the static pitch otherwise. */
+  /** Enrollment summary, or the singleton Copilot account summary. */
   description: string;
   /** A meaningful account state; routine availability stays in the description. */
   status?: string;
@@ -64,8 +65,8 @@ function emptyOAuthCardStates(): Record<OAuthCardId, SubscriptionSnapshot | null
 }
 
 /**
- * Account sign-in rows for the provider catalog, plus the refresh that keeps
- * their badges live.
+ * Account enrollment rows for the provider catalog. Codex and xAI are pure
+ * add intents; only the singleton Copilot import retains aggregate state.
  *
  * This used to be a self-contained `ModelOAuthSection` that rendered both the
  * rows and a Dialog per service. The rows and the login body are now two
@@ -76,7 +77,10 @@ function emptyOAuthCardStates(): Record<OAuthCardId, SubscriptionSnapshot | null
  * login remounts it, which re-reads every account state; that is the refresh,
  * and it is why nothing here has to be pushed across a level boundary.
  */
-export function useOAuthCards(props: { query?: string }) {
+export function useOAuthCards(props: {
+  query?: string;
+  connections: readonly LlmConnection[];
+}) {
   const host = useRuntimeHostSettingsTarget();
   const generationKey = useRuntimeHostSettingsGenerationKey();
   const locale = useUiLocale();
@@ -84,12 +88,9 @@ export function useOAuthCards(props: { query?: string }) {
   const cards = modelOAuthCards(copy);
   const mountedRef = useMountedRef();
   const refreshTicketRef = useRef(0);
-  // PR-OAUTH-CARD-LIVE-STATE-0 (WAWQAQ msg d79fd115 follow-up): before this
-  // lift the cards stayed at their static catalog copy even after the user
-  // finished the OAuth flow — there was no parent re-fetch. Each service now
-  // carries a runtimeState + email so its row can show the account email inline,
-  // re-fetched whenever a login step closes (success OR
-  // cancel — the user may have signed out from inside it).
+  // Copilot remains a singleton local import, so its catalog row retains live
+  // account state. Codex and xAI are pure enrollment intents and derive only
+  // their Connection counts from the catalog.
   const [cardStates, setCardStates] = useState(emptyOAuthCardStates);
   const [refreshError, setRefreshError] = useState<string | null>(null);
   const normalizedQuery = props.query?.trim().toLocaleLowerCase() ?? '';
@@ -104,7 +105,7 @@ export function useOAuthCards(props: { query?: string }) {
     const ticket = refreshTicketRef.current + 1;
     refreshTicketRef.current = ticket;
     const results = await Promise.all(
-      cards.map(async (card) => {
+      cards.filter((card) => card.id === 'github-copilot').map(async (card) => {
         try {
           const snapshot = await getSubscriptionSnapshot(card.id, host);
           return { id: card.id, snapshot } as const;
@@ -152,24 +153,32 @@ export function useOAuthCards(props: { query?: string }) {
   }, [generationKey]);
 
   const visibleCards: OAuthCard[] = cards
-    .filter(matchesQuery)
     .map((card) => {
       const snapshot = cardStates[card.id];
+      const connectionCount = props.connections.filter(
+        (connection) => connection.providerType === card.providerType,
+      ).length;
       const runtimeState = snapshot?.runtimeState ?? 'unknown';
-      const isLoggedIn =
+      const isLoggedIn = card.id === 'github-copilot' && (
         runtimeState === 'authenticated' ||
         runtimeState === 'refreshing' ||
         runtimeState === 'quota_unavailable' ||
-        runtimeState === 'provider_rejected';
+        runtimeState === 'provider_rejected'
+      );
       return {
         id: card.id,
         providerType: card.providerType,
         name: card.name,
-        description: isLoggedIn && snapshot?.email ? snapshot.email : card.description,
+        description: card.id !== 'github-copilot' && connectionCount > 0
+          ? copy.configuredConnections(connectionCount)
+          : isLoggedIn && snapshot?.email
+            ? snapshot.email
+            : card.description,
         ...(isLoggedIn ? { status: copy.signedIn } : {}),
         isLoggedIn,
       };
-    });
+    })
+    .filter(matchesQuery);
 
   return { cards: visibleCards, refreshError };
 }
@@ -179,7 +188,10 @@ export function useOAuthCards(props: { query?: string }) {
  * this as its setup level; the header and the back affordance belong to that
  * level, the same ones the catalog and the connection detail use.
  */
-export function OAuthLoginPanel(props: { cardId: OAuthCardId; onLoginSuccess(): void | Promise<void> }) {
+export function OAuthLoginPanel(props: {
+  cardId: OAuthCardId;
+  onLoginSuccess(connection?: OAuthConnectionIdentity): void | Promise<void>;
+}) {
   return (
     <RuntimeHostSettingsGenerationBoundary>
       <OAuthLoginPanelForCurrentGeneration {...props} />
@@ -189,7 +201,7 @@ export function OAuthLoginPanel(props: { cardId: OAuthCardId; onLoginSuccess():
 
 function OAuthLoginPanelForCurrentGeneration(props: {
   cardId: OAuthCardId;
-  onLoginSuccess(): void | Promise<void>;
+  onLoginSuccess(connection?: OAuthConnectionIdentity): void | Promise<void>;
 }) {
   if (props.cardId === 'github-copilot') {
     return <GitHubCopilotLoginPanel onLoginSuccess={props.onLoginSuccess} />;
@@ -219,11 +231,10 @@ function modelOAuthCards(copy: ProviderSettingsCopy['oauthSection']): ReadonlyAr
 
 function SubscriptionLoginPanel(props: {
   service: 'codex' | 'xai';
-  onLoginSuccess(): void | Promise<void>;
+  onLoginSuccess(connection: OAuthConnectionIdentity): void | Promise<void>;
 }) {
   const host = useRuntimeHostSettingsTarget();
-  const locale = useUiLocale();
-  const copy = getProviderSettingsCopy(locale).oauthSection;
+  const copy = getProviderSettingsCopy(useUiLocale()).oauthSection;
   const isXai = props.service === 'xai';
   const display: SubscriptionDisplay = isXai
     ? { name: 'xAI Grok', shortName: 'SuperGrok / X Premium', detail: copy.xaiDetail }
@@ -234,9 +245,11 @@ function SubscriptionLoginPanel(props: {
   // localized toast copy) lives in useOAuthLoginFlow so the connection detail
   // page can drive the exact same flow behind its relogin button.
   const flow = useOAuthLoginFlow({
-    bridge: runtimeHostOAuthLoginBridge(
+    mode: 'create',
+    authorizationBridge: runtimeHostOAuthAuthorizationBridge(
       isXai ? window.maka.xaiOAuth : window.maka.openAiCodex,
       host,
+      { kind: 'create' },
     ),
     display: { name: display.name, shortName: display.shortName },
     onLoginSuccess: props.onLoginSuccess,
@@ -244,31 +257,26 @@ function SubscriptionLoginPanel(props: {
 
   return (
     <VStack gap={3} data-status={flow.runtimeState}>
-      <Text type="body">{presentSnapshotDetail(flow.state, display, locale)}</Text>
-      {!isXai && flow.stateHint && (
-        <Text type="supporting" color="secondary">
-          {copy.deviceCode} {flow.stateHint}
+      <Text type="body">{display.detail}</Text>
+      {flow.authRequestId && (
+        <Text type="supporting" color="secondary" role="status" aria-live="polite">
+          {!isXai && flow.stateHint
+            ? <>{copy.deviceCode} {flow.stateHint}</>
+            : copy.waitingAuthorization}
         </Text>
       )}
       {flow.errorMessage && (
         <Banner status="error" role="alert" title={flow.errorMessage} />
       )}
       <HStack gap={2} hAlign="end">
-        {!flow.isLoggedIn ? (
-          <Button
-            variant="primary"
-            onClick={() => void flow.startLogin()}
-            isDisabled={flow.actionBusy}
-            label={flow.pendingAction === 'login' ? copy.openingBrowser : copy.login(display.shortName)}
-          />
-        ) : (
-          <Button
-            variant="ghost"
-            onClick={() => void flow.logout()}
-            isDisabled={flow.actionBusy}
-            label={flow.pendingAction === 'logout' ? copy.loggingOut : copy.logout}
-          />
-        )}
+        <Button
+          variant="primary"
+          onClick={() => void flow.startLogin()}
+          isDisabled={flow.actionBusy}
+          label={flow.pendingAction === 'login'
+            ? flow.authRequestId ? copy.waitingAuthorization : copy.openingBrowser
+            : copy.loginAndAdd}
+        />
       </HStack>
     </VStack>
   );
@@ -283,10 +291,11 @@ function GitHubCopilotLoginPanel(props: { onLoginSuccess(): void | Promise<void>
   // no browser handoff, no logout confirm) instead of owning a separate
   // pending-action state machine here (#1042).
   const flow = useOAuthLoginFlow({
-    bridge: {
+    mode: 'direct',
+    accountBridge: {
       getAccountState: () => window.maka.githubCopilotSubscription.getAccountState(host),
       logout: () => window.maka.githubCopilotSubscription.logout(host),
-    } as OAuthLoginFlowBridge,
+    },
     display: { name: 'GitHub Copilot', shortName: 'GitHub Copilot' },
     onLoginSuccess: props.onLoginSuccess,
     direct: {
@@ -310,7 +319,7 @@ function GitHubCopilotLoginPanel(props: { onLoginSuccess(): void | Promise<void>
         {loggedIn && (
           <>
             <Button variant="secondary" onClick={() => void refreshTokens?.()} isDisabled={flow.actionBusy} label={flow.pendingAction === 'refresh' ? copy.verifying : copy.reverify} />
-            <Button variant="ghost" onClick={() => void flow.logout()} isDisabled={flow.actionBusy} label={flow.pendingAction === 'logout' ? copy.removing : copy.removeLocal} />
+            <Button variant="ghost" onClick={() => void flow.logout?.()} isDisabled={flow.actionBusy} label={flow.pendingAction === 'logout' ? copy.removing : copy.removeLocal} />
           </>
         )}
       </HStack>
@@ -325,10 +334,7 @@ async function getSubscriptionSnapshot(
   if (serviceId === 'github-copilot') {
     return window.maka.githubCopilotSubscription.getAccountState(host);
   }
-  if (serviceId === 'xai') {
-    return window.maka.xaiOAuth.getAccountState(host);
-  }
-  return (await window.maka.openAiCodex.getAccountState(host)) as SubscriptionSnapshot;
+  throw new Error(`Aggregate OAuth state is not available for ${serviceId}`);
 }
 
 interface SubscriptionDisplay {
@@ -336,31 +342,3 @@ interface SubscriptionDisplay {
   shortName: string;
   detail: string;
 }
-
-function presentSnapshotDetail(state: SubscriptionSnapshot | null, display: SubscriptionDisplay, locale: 'zh' | 'en'): string {
-  const copy = getProviderSettingsCopy(locale).oauthSection;
-  if (!state) return copy.loadingAccount;
-  switch (state.runtimeState) {
-    case 'not_logged_in':
-      return copy.signedOut(display.name);
-    case 'authorizing':
-      return copy.authorizing;
-    case 'authenticated': {
-      const parts = [copy.signedIn];
-      if (state.email) parts.push(state.email);
-      if (state.plan) parts.push(state.plan);
-      return parts.join(' · ');
-    }
-    case 'refreshing':
-      return copy.refreshing;
-    case 'refresh_failed':
-      return subscriptionResultMessage(state.errorMessage, copy.refreshTokenFailed, locale);
-    case 'storage_failed':
-      return subscriptionResultMessage(state.errorMessage, copy.storageFailed(display.name), locale);
-    case 'quota_unavailable':
-    case 'provider_rejected':
-      return subscriptionResultMessage(state.errorMessage, copy.providerUnavailable(display.name), locale);
-  }
-  const _exhaustive: never = state.runtimeState;
-  return _exhaustive;
-}
diff --git a/apps/desktop/src/renderer/settings/provider-panel-shared.ts b/apps/desktop/src/renderer/settings/provider-panel-shared.ts
index ec77a98ce6..7c9e6454a9 100644
--- a/apps/desktop/src/renderer/settings/provider-panel-shared.ts
+++ b/apps/desktop/src/renderer/settings/provider-panel-shared.ts
@@ -21,6 +21,7 @@ import { generalizedErrorMessage, generalizedErrorMessageChinese, redactSecrets
 import {
   type ConnectionTestResult,
   type CreateConnectionInput,
+  type IdentifiedLlmConnection,
   type LlmConnection,
   type ModelDiscoveryResult,
   type RequestHeaderUpdate,
@@ -32,20 +33,23 @@ import {
 import { type UiLocale } from '@maka/core/ui-locale';
 import { getProviderSettingsCopy } from '../locales/settings-provider-copy.js';
 import { cleanErrorMessage } from '../model-connection-errors.js';
-import type { DesktopConnectionSnapshot } from '../../shared/desktop-connection-snapshot.js';
+import type {
+  DesktopConnectionIdentity,
+  DesktopConnectionSnapshot,
+} from '../../shared/desktop-connection-snapshot.js';
 
 export interface ConnectionsBridge {
   getSnapshot(): Promise<DesktopConnectionSnapshot>;
-  setDefault(slug: string | null): Promise<void>;
-  create(input: CreateConnectionInput): Promise<LlmConnection>;
-  update(slug: string, patch: UpdateConnectionInput): Promise<LlmConnection>;
-  delete(slug: string): Promise<void>;
-  test(slug: string, opts?: { model?: string }): Promise<ConnectionTestResult>;
-  fetchModels(slug: string): Promise<ModelDiscoveryResult>;
-  hasSecret(slug: string): Promise<boolean>;
-  getRequestHeaders(slug: string): Promise<SavedRequestHeaders>;
+  setDefault(connection: DesktopConnectionIdentity | null): Promise<void>;
+  create(input: CreateConnectionInput): Promise<IdentifiedLlmConnection>;
+  update(connection: DesktopConnectionIdentity, patch: UpdateConnectionInput): Promise<LlmConnection>;
+  delete(connection: DesktopConnectionIdentity): Promise<void>;
+  test(connection: DesktopConnectionIdentity, opts?: { model?: string }): Promise<ConnectionTestResult>;
+  fetchModels(connection: DesktopConnectionIdentity): Promise<ModelDiscoveryResult>;
+  hasSecret(connection: DesktopConnectionIdentity): Promise<boolean>;
+  getRequestHeaders(connection: DesktopConnectionIdentity): Promise<SavedRequestHeaders>;
   setRequestHeaders(
-    slug: string,
+    connection: DesktopConnectionIdentity,
     headers: readonly RequestHeaderUpdate[],
   ): Promise<SavedRequestHeaders>;
   subscribeEvents?(handler: () => void): () => void;
diff --git a/apps/desktop/src/renderer/settings/providers-panel.tsx b/apps/desktop/src/renderer/settings/providers-panel.tsx
index f38af09d46..9e95ed559b 100644
--- a/apps/desktop/src/renderer/settings/providers-panel.tsx
+++ b/apps/desktop/src/renderer/settings/providers-panel.tsx
@@ -34,7 +34,7 @@ import {
 } from '@astryxdesign/core';
 import { ICON_SIZE, ChevronRight, Cpu } from '@maka/ui/icons';
 import {
-  type LlmConnection,
+  type IdentifiedLlmConnection,
   type ProviderType,
 } from '@maka/core/llm-connections';
 import { dotForStatus, useMountedRef, useUiLocale } from '@maka/ui';
@@ -45,6 +45,7 @@ import {
   ProviderCatalogPage,
   ProviderSetupPage,
   type CatalogFilter,
+  type CreatedOAuthConnectionIdentity,
   type SetupTarget,
 } from './provider-catalog-page';
 import { isRetiredProvider } from '@maka/core/provider-registry';
@@ -80,7 +81,8 @@ type PanelRoute =
   | { kind: 'list' }
   | { kind: 'catalog' }
   | { kind: 'setup'; target: SetupTarget; origin: 'list' | 'catalog' }
-  | { kind: 'detail'; slug: string };
+  | { kind: 'adopting-oauth'; identity: CreatedOAuthConnectionIdentity }
+  | { kind: 'detail'; connectionId: string };
 
 function backTarget(route: PanelRoute): PanelRoute {
   if (route.kind === 'setup' && route.origin === 'catalog') return { kind: 'catalog' };
@@ -106,7 +108,7 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
   onInitialCreateProviderConsumed?: () => void;
 }) {
   const reportHostError = useRuntimeHostSettingsErrorReporter();
-  const [connections, setConnections] = useState<LlmConnection[]>([]);
+  const [connections, setConnections] = useState<IdentifiedLlmConnection[]>([]);
   const [defaultSlug, setDefaultSlug] = useState<string | null>(null);
   const [route, setRoute] = useState<PanelRoute>({ kind: 'list' });
   // Browsing state, not navigation state: it outlives the catalog so that
@@ -119,29 +121,34 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
   const addButtonRef = useRef<HTMLButtonElement>(null);
   // Which row the user left the list from, so returning puts focus back where
   // they were rather than on the page's primary action.
-  const listReturnFocusRef = useRef<string | null>(null);
+  const returnFocusRef = useRef<
+    | { level: 'list'; connectionId: string }
+    | { level: 'catalog'; providerType: ProviderType }
+    | null
+  >(null);
   const detailTitleId = useId();
+  const setupTitleId = useId();
   const locale = useUiLocale();
   const providerCopy = getProviderSettingsCopy(locale);
   const copy = providerCopy.panel;
 
-  async function reload(): Promise<boolean> {
+  async function reload(): Promise<Awaited<ReturnType<ConnectionsBridge['getSnapshot']>> | null> {
     const ticket = ++providersReloadTicketRef.current;
     try {
       const snapshot = await bridge.getSnapshot();
-      if (!providersPanelMountedRef.current || providersReloadTicketRef.current !== ticket) return false;
+      if (!providersPanelMountedRef.current || providersReloadTicketRef.current !== ticket) return null;
       setConnections(snapshot.connections);
       setDefaultSlug(snapshot.defaultConnection);
       setLoadError(null);
       setLoading(false);
-      return true;
+      return snapshot;
     } catch (error) {
-      if (!providersPanelMountedRef.current || providersReloadTicketRef.current !== ticket) return false;
+      if (!providersPanelMountedRef.current || providersReloadTicketRef.current !== ticket) return null;
       const message = providerPanelActionErrorMessage(error, locale);
       setLoadError(message);
       setLoading(false);
       reportHostError(copy.loadFailed, message);
-      return false;
+      return null;
     }
   }
 
@@ -166,11 +173,29 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
 
   const initialConnectionDetailOpenedRef = useRef(false);
   useEffect(() => {
+    if (route.kind === 'adopting-oauth') {
+      const created = connections.find(
+        (connection) => connection.connectionId === route.identity.connectionId,
+      );
+      if (!created) return;
+      if (
+        created.slug !== route.identity.slug ||
+        created.providerType !== route.identity.providerType
+      ) {
+        setRoute({ kind: 'list' });
+        reportHostError(copy.loadFailed, copy.connectionIdentityChanged);
+        return;
+      }
+      returnFocusRef.current = { level: 'list', connectionId: created.connectionId };
+      setRoute({ kind: 'detail', connectionId: created.connectionId });
+      return;
+    }
     if (loading || !initialConnectionSlug || initialConnectionDetailOpenedRef.current) return;
-    if (!connections.some((candidate) => candidate.slug === initialConnectionSlug)) return;
+    const connection = connections.find((candidate) => candidate.slug === initialConnectionSlug);
+    if (!connection?.connectionId) return;
     initialConnectionDetailOpenedRef.current = true;
-    setRoute({ kind: 'detail', slug: initialConnectionSlug });
-  }, [loading, initialConnectionSlug, connections]);
+    setRoute({ kind: 'detail', connectionId: connection.connectionId });
+  }, [loading, initialConnectionSlug, connections, route, copy.connectionIdentityChanged, copy.loadFailed, reportHostError]);
 
   const initialCreateOpenedRef = useRef(false);
   useEffect(() => {
@@ -198,24 +223,29 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
     setRoute({ kind: 'list' });
   }
 
-  function openDetail(slug: string) {
-    listReturnFocusRef.current = slug;
-    setRoute({ kind: 'detail', slug });
+  function openDetail(connection: IdentifiedLlmConnection) {
+    returnFocusRef.current = { level: 'list', connectionId: connection.connectionId };
+    setRoute({ kind: 'detail', connectionId: connection.connectionId });
   }
 
   function openCatalog() {
-    listReturnFocusRef.current = null;
+    returnFocusRef.current = null;
     setRoute({ kind: 'catalog' });
   }
 
   const selected = route.kind === 'detail'
-    ? connections.find((connection) => connection.slug === route.slug) ?? null
+    ? connections.find((connection) => connection.connectionId === route.connectionId) ?? null
     : null;
+  const pendingOAuthIdentity = route.kind === 'adopting-oauth' ? route.identity : null;
 
   // A detail route whose connection vanished (deleted in another window) is an
   // unsatisfiable route, not a state to correct: the list is what it renders
   // as. Deriving that beats scheduling a setState from inside render.
-  const level: PanelRoute['kind'] = route.kind === 'detail' && !selected ? 'list' : route.kind;
+  const level: Exclude<PanelRoute['kind'], 'adopting-oauth'> = route.kind === 'adopting-oauth'
+    ? 'list'
+    : route.kind === 'detail' && !selected
+      ? 'list'
+      : route.kind;
 
   useSettingsRouteFocus({
     level,
@@ -223,7 +253,13 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
     isReady: !loading,
     resolveTarget: (current) => {
       const find = (selector: string) => document.querySelector<HTMLElement>(selector);
-      if (current === 'catalog') return find('[data-maka-contract="provider-catalog"] input');
+      if (current === 'catalog') {
+        const target = returnFocusRef.current;
+        returnFocusRef.current = null;
+        return (target?.level === 'catalog'
+          ? find(`[data-provider="${target.providerType}"] button`)
+          : null) ?? find('[data-maka-contract="provider-catalog"] input');
+      }
       if (current === 'setup') {
         return find('[data-maka-contract="provider-setup"] input')
           ?? find('[data-maka-contract="provider-setup"]');
@@ -235,9 +271,11 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
       // survive the levels in between. The row the user came from may be gone —
       // that is exactly what a deletion does — so the primary action is the
       // fallback, not the default.
-      const returnToSlug = listReturnFocusRef.current;
-      listReturnFocusRef.current = null;
-      return (returnToSlug ? find(`[data-connection-slug="${returnToSlug}"] button`) : null)
+      const target = returnFocusRef.current;
+      returnFocusRef.current = null;
+      return (target?.level === 'list'
+        ? find(`[data-connection-id="${target.connectionId}"] button`)
+        : null)
         ?? addButtonRef.current;
     },
   });
@@ -268,7 +306,7 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
             backLabel={copy.backToList}
             logo={<ProviderLogo type={selected.providerType} compact />}
             titleId={detailTitleId}
-            title={selected.name}
+            title={connectionDisplayName(selected, connections)}
             /* The control sits in the slot that shows the state. Setting the
                default connection was reachable only from the command palette,
                while this page displayed a 默认 Badge and offered no way to
@@ -306,7 +344,7 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
                   tooltip={copy.setDefaultTitle}
                   clickAction={async () => {
                     try {
-                      await bridge.setDefault(selected.slug);
+                      await bridge.setDefault({ connectionId: selected.connectionId, slug: selected.slug });
                       await reload();
                     } catch (error) {
                       // The state is unchanged on failure, so the Badge stays
@@ -322,7 +360,7 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
             subtitle={connectionSubtitle(selected, locale)}
           />
           <ConnectionDetail
-            key={selected.slug}
+            key={selected.connectionId ?? selected.slug}
             bridge={bridge}
             connection={selected}
             isDefault={selected.slug === defaultSlug}
@@ -344,8 +382,12 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
           />
           <ProviderCatalogPage
             filter={catalogFilter}
+            connections={connections}
             onFilterChange={setCatalogFilter}
-            onPick={(target) => setRoute({ kind: 'setup', target, origin: 'catalog' })}
+            onPick={(target) => {
+              returnFocusRef.current = { level: 'catalog', providerType: target.providerType };
+              setRoute({ kind: 'setup', target, origin: 'catalog' });
+            }}
           />
         </VStack>
       ) : level === 'setup' && route.kind === 'setup' ? (
@@ -354,7 +396,10 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
             onBack={goBack}
             backLabel={route.origin === 'catalog' ? copy.backToCatalog : copy.backToList}
             logo={<ProviderLogo type={route.target.providerType} compact />}
-            title={copy.connectTitle(route.target.name)}
+            titleId={setupTitleId}
+            title={route.target.method === 'account' && route.target.cardId !== 'github-copilot'
+              ? providerCopy.oauthSection.addAccountTitle(route.target.name)
+              : copy.connectTitle(route.target.name)}
             subtitle={route.target.method === 'account'
               ? oauthPanelSubtitle(route.target.cardId, providerCopy.oauthSection)
               : copy.createSubtitle}
@@ -363,16 +408,26 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
             bridge={bridge}
             target={route.target}
             existingSlugs={connections.map((connection) => connection.slug)}
+            labelledBy={setupTitleId}
             onCancel={goBack}
-            onAccountChanged={async () => { await reload(); }}
-            onCreated={async (slug, modelDiscoveryError) => {
+            onAccountCreated={async (identity) => {
+              if (!identity) return;
+              setRoute({ kind: 'adopting-oauth', identity });
               await reload();
-              if (!providersPanelMountedRef.current) return;
+            }}
+            onCreated={async (slug, modelDiscoveryError) => {
+              const snapshot = await reload();
+              if (!snapshot || !providersPanelMountedRef.current) return;
               // The new connection's detail, not the list: creating it is the
               // start of setting it up, and every next move — pick the default
               // model, enable models, fix the endpoint the discovery error just
               // complained about — is on that page.
-              setRoute({ kind: 'detail', slug });
+              const created = snapshot.connections.find((connection) => connection.slug === slug);
+              if (!created?.connectionId) {
+                reportHostError(copy.loadFailed, copy.connectionRemoved);
+                return;
+              }
+              setRoute({ kind: 'detail', connectionId: created.connectionId });
               if (modelDiscoveryError) {
                 const providerName = providerDisplay(route.target.providerType, locale).name;
                 reportHostError(
@@ -388,6 +443,9 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
         </VStack>
       ) : (
         <>
+          {route.kind === 'detail' && !selected && (
+            <Banner status="warning" role="status" title={copy.connectionRemoved} />
+          )}
           <HStack gap={2} vAlign="center" hAlign="between">
             <HStack gap={2} vAlign="center">
               <Heading level={3}>{copy.connections}</Heading>
@@ -400,10 +458,26 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
               variant="primary"
               label={copy.addConnection}
               onClick={openCatalog}
+              isDisabled={pendingOAuthIdentity !== null}
               data-maka-contract="add-connection"
             />
           </HStack>
-          {loadError ? (
+          {pendingOAuthIdentity && loadError ? (
+            <Banner
+              status="warning"
+              role="status"
+              title={copy.connectedLoadFailed}
+              description={loadError}
+              endContent={<Button variant="ghost" label={copy.retry} onClick={() => void reload()} />}
+            />
+          ) : pendingOAuthIdentity ? (
+            <Banner
+              status="info"
+              role="status"
+              title={copy.connectedLoading}
+              endContent={<Button variant="ghost" label={copy.retry} onClick={() => void reload()} />}
+            />
+          ) : loadError ? (
             <Banner
               status="error"
               title={copy.loadFailed}
@@ -415,7 +489,7 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
               icon={<Cpu size={ICON_SIZE.empty} />}
               title={copy.empty}
               description={copy.emptyHelp}
-              actions={<Button variant="primary" label={copy.addConnection} onClick={openCatalog} />}
+              actions={<Button variant="primary" label={copy.addConnection} onClick={openCatalog} isDisabled={pendingOAuthIdentity !== null} />}
             />
           ) : (
             <List hasDividers>
@@ -424,15 +498,16 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
                 const isDefault = connection.slug === defaultSlug;
                 return (
                   <ListItem
-                    key={connection.slug}
+                    key={connection.connectionId ?? connection.slug}
                     className="connectionRow"
+                    data-connection-id={connection.connectionId}
                     data-connection-slug={connection.slug}
                     data-disabled={connection.enabled ? undefined : 'true'}
                     startContent={<ProviderLogo type={connection.providerType} compact />}
                     label={(
                       <HStack gap={2} vAlign="center">
                         {/* a11y-allow: this label names the ROW, not the span. Astryx's Item puts consumer props on its outer wrapper and renders a separate invisible <button> for the click target, so an aria-label on the Item never reaches that button — measured. The button is named from its content, and this span is how the status reaches that name. Removing it drops the runtime error from the row's accessible name (settings.spec:226).*/}
-                        <span aria-label={chipAriaLabel(connection, isDefault)}>{connection.name}</span>
+                        <span aria-label={chipAriaLabel(connection, isDefault)}>{connectionDisplayName(connection, connections)}</span>
                         {isDefault && <Badge variant="neutral" label={copy.default} />}
                       </HStack>
                     )}
@@ -448,7 +523,7 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
                         <ChevronRight size={ICON_SIZE.chrome} aria-hidden="true" />
                       </HStack>
                     )}
-                    onClick={() => openDetail(connection.slug)}
+                    onClick={() => openDetail(connection)}
                   />
                 );
               })}
@@ -459,17 +534,35 @@ export function ProvidersPanel({ bridge, initialPage = 'connections', initialCon
     </VStack>
   );
 
-  function chipAriaLabel(connection: LlmConnection, isDefault: boolean): string {
+  function chipAriaLabel(connection: IdentifiedLlmConnection, isDefault: boolean): string {
     const provider = providerDisplay(connection.providerType, locale).name;
     const status = connectionChipStatus(connection, locale);
-    return copy.chipAria(connection.name, provider, isDefault, status?.label);
+    return copy.chipAria(
+      connectionDisplayName(connection, connections),
+      provider,
+      isDefault,
+      status?.label,
+    );
   }
 }
 
 /** Provider · default model — the row's second line, and the detail's subtitle. */
-function connectionSubtitle(connection: LlmConnection, locale: 'zh' | 'en'): string {
+function connectionSubtitle(connection: IdentifiedLlmConnection, locale: 'zh' | 'en'): string {
   const providerName = providerDisplay(connection.providerType, locale).name;
   const parts = [providerName];
   if (connection.defaultModel) parts.push(connection.defaultModel);
   return parts.join(' · ');
 }
+
+function connectionDisplayName(
+  connection: IdentifiedLlmConnection,
+  connections: readonly IdentifiedLlmConnection[],
+): string {
+  const ambiguous = connections.some(
+    (candidate) =>
+      candidate.connectionId !== connection.connectionId &&
+      candidate.providerType === connection.providerType &&
+      candidate.name === connection.name,
+  );
+  return ambiguous ? `${connection.name} · ${connection.slug}` : connection.name;
+}
diff --git a/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts b/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts
index a4a4aa1b20..041bd4d09c 100644
--- a/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts
+++ b/apps/desktop/src/renderer/settings/runtime-host-settings-bridge.ts
@@ -17,9 +17,16 @@
  * under the License.
  */
 
-import type { DesktopRuntimeHostRef } from '../../preload/bridge-contract.js';
+import type {
+  DesktopOAuthLoginTarget,
+  DesktopRuntimeHostRef,
+  MakaBridge,
+} from '../../preload/bridge-contract.js';
 import type { ConnectionsBridge } from './provider-panel-shared.js';
-import type { OAuthLoginFlowBridge } from './use-oauth-login-flow.js';
+import type {
+  OAuthAccountFlowBridge,
+  OAuthAuthorizationFlowBridge,
+} from './use-oauth-login-flow.js';
 
 export type RuntimeHostSettingsConnectionsBridge = ConnectionsBridge & {
   setDefaultModel(input: { slug: string; model: string } | null): Promise<void>;
@@ -30,36 +37,78 @@ export function runtimeHostConnectionsBridge(
 ): RuntimeHostSettingsConnectionsBridge {
   return {
     getSnapshot: () => window.maka.connections.getSnapshot(undefined, host),
-    setDefault: (slug) => window.maka.connections.setDefault(slug, host),
+    setDefault: (connection) => window.maka.connections.setDefault(connection, host),
     setDefaultModel: (input) => window.maka.connections.setDefaultModel(input, host),
     create: (input) => window.maka.connections.create(input, host),
-    update: (slug, patch) => window.maka.connections.update(slug, patch, host),
-    delete: (slug) => window.maka.connections.delete(slug, host),
-    test: (slug, options) => window.maka.connections.test(slug, options, host),
-    fetchModels: (slug) => window.maka.connections.fetchModels(slug, host),
-    hasSecret: (slug) => window.maka.connections.hasSecret(slug, host),
-    getRequestHeaders: (slug) => window.maka.connections.getRequestHeaders(slug, host),
-    setRequestHeaders: (slug, headers) =>
-      window.maka.connections.setRequestHeaders(slug, headers, host),
+    update: (connection, patch) => window.maka.connections.update(connection, patch, host),
+    delete: (connection) => window.maka.connections.delete(connection, host),
+    test: (connection, options) => window.maka.connections.test(connection, options, host),
+    fetchModels: (connection) => window.maka.connections.fetchModels(connection, host),
+    hasSecret: (connection) => window.maka.connections.hasSecret(connection, host),
+    getRequestHeaders: (connection) => window.maka.connections.getRequestHeaders(connection, host),
+    setRequestHeaders: (connection, headers) =>
+      window.maka.connections.setRequestHeaders(connection, headers, host),
     subscribeEvents: (handler) =>
       window.maka.connections.subscribeEvents(handler, host),
   };
 }
 
-export function runtimeHostOAuthLoginBridge(
-  bridge: typeof window.maka.openAiCodex | typeof window.maka.xaiOAuth,
+type RuntimeHostOAuthBridge = MakaBridge['openAiCodex'] | MakaBridge['xaiOAuth'];
+
+export function runtimeHostOAuthAuthorizationBridge(
+  bridge: RuntimeHostOAuthBridge,
   host: DesktopRuntimeHostRef,
-  connectionId?: string,
-): OAuthLoginFlowBridge {
+  target: DesktopOAuthLoginTarget,
+): OAuthAuthorizationFlowBridge {
   return {
-    getAuthUrl: () =>
-      bridge.getAuthUrl(host, connectionId) as ReturnType<OAuthLoginFlowBridge['getAuthUrl']>,
+    getAuthUrl: () => bridge.getAuthUrl(host, target),
     openAuthUrl: (authRequestId) => bridge.openAuthUrl(authRequestId, host),
-    completeAuthorization: (authRequestId) =>
-      bridge.completeAuthorization(authRequestId, host),
-    cancelAuthorization: (authRequestId) =>
-      bridge.cancelAuthorization(authRequestId, host),
-    getAccountState: () => bridge.getAccountState(host, connectionId),
+    completeAuthorization: (authRequestId) => bridge.completeAuthorization(authRequestId, host),
+    cancelAuthorization: (authRequestId) => bridge.cancelAuthorization(authRequestId, host),
+  };
+}
+
+export function runtimeHostOAuthAccountBridge(
+  bridge: RuntimeHostOAuthBridge,
+  host: DesktopRuntimeHostRef,
+  connectionId: string,
+): OAuthAccountFlowBridge {
+  return {
+    getAccountState: async () => requireOAuthAccountState(
+      await bridge.getAccountState(host, connectionId),
+    ),
     logout: () => bridge.logout(host, connectionId),
   };
 }
+
+function requireOAuthAccountState(value: unknown): unknown {
+  if (
+    value &&
+    typeof value === 'object' &&
+    'ok' in value &&
+    value.ok === false &&
+    'message' in value &&
+    typeof value.message === 'string'
+  ) {
+    throw new Error(value.message);
+  }
+  return value;
+}
+
+export function runtimeHostOAuthExistingLoginBridges(
+  bridge: RuntimeHostOAuthBridge,
+  host: DesktopRuntimeHostRef,
+  connectionId: string,
+): {
+  authorizationBridge: OAuthAuthorizationFlowBridge;
+  accountBridge: OAuthAccountFlowBridge;
+} {
+  return {
+    authorizationBridge: runtimeHostOAuthAuthorizationBridge(
+      bridge,
+      host,
+      { kind: 'existing', connectionId },
+    ),
+    accountBridge: runtimeHostOAuthAccountBridge(bridge, host, connectionId),
+  };
+}
diff --git a/apps/desktop/src/renderer/settings/use-connection-detail.ts b/apps/desktop/src/renderer/settings/use-connection-detail.ts
index e9e9859a04..8e24f0ac8e 100644
--- a/apps/desktop/src/renderer/settings/use-connection-detail.ts
+++ b/apps/desktop/src/renderer/settings/use-connection-detail.ts
@@ -26,7 +26,7 @@ import {
 } from './connection-name-draft.js';
 import {
   type ConnectionTestResult,
-  type LlmConnection,
+  type IdentifiedLlmConnection,
   type ModelInfo,
   type ProviderType,
 } from '@maka/core/llm-connections';
@@ -50,7 +50,10 @@ import { connectionChipStatus } from './provider-connection-status';
 import { relayProfileDraftReseedPlan, relayProfileDraftSeed } from './relay-profile-draft';
 import { applyBulkThinkingLevel, relayProfileWithThinkingLevels } from './relay-thinking-bulk';
 import { useKeyedActionGuard } from './use-action-guard';
-import type { OAuthLoginFlowBridge } from './use-oauth-login-flow';
+import type {
+  OAuthAccountFlowBridge,
+  OAuthAuthorizationFlowBridge,
+} from './use-oauth-login-flow';
 import {
   connectionLastTestMessageDisplay,
   connectionTestFailureMessage,
@@ -62,7 +65,9 @@ import {
   useRuntimeHostSettingsErrorReporter,
   useRuntimeHostSettingsTarget,
 } from './runtime-host-settings-target.js';
-import { runtimeHostOAuthLoginBridge } from './runtime-host-settings-bridge.js';
+import {
+  runtimeHostOAuthExistingLoginBridges,
+} from './runtime-host-settings-bridge.js';
 
 // Maps an OAuth model-connection provider type to the browser-assisted login
 // service that can re-run its authorization from inside the connection dialog. Only
@@ -70,7 +75,8 @@ import { runtimeHostOAuthLoginBridge } from './runtime-host-settings-bridge.js';
 // here; plain API-key providers return null so the notice falls back to
 // prose instead of rendering a dead button.
 export interface OAuthLoginService {
-  bridge: OAuthLoginFlowBridge;
+  authorizationBridge: OAuthAuthorizationFlowBridge;
+  accountBridge: OAuthAccountFlowBridge;
   display: { name: string; shortName: string };
   // Codex's device-authorization page requires the user to type the code the
   // flow surfaces as `stateHint` — the verification URL does not embed it, so
@@ -83,18 +89,27 @@ export function oauthLoginServiceFor(
   providerType: ProviderType,
   host: import('../../preload/bridge-contract.js').DesktopRuntimeHostRef,
   connectionId: string,
+  connectionLabel?: string,
 ): OAuthLoginService | null {
   switch (providerType) {
     case 'openai-codex':
       return {
-        bridge: runtimeHostOAuthLoginBridge(window.maka.openAiCodex, host, connectionId),
-        display: { name: 'OpenAI Codex', shortName: 'Codex' },
+        ...runtimeHostOAuthExistingLoginBridges(
+          window.maka.openAiCodex,
+          host,
+          connectionId,
+        ),
+        display: { name: connectionLabel ?? 'OpenAI Codex', shortName: 'Codex' },
         showsDeviceCode: true,
       };
     case 'xai-oauth':
       return {
-        bridge: runtimeHostOAuthLoginBridge(window.maka.xaiOAuth, host, connectionId),
-        display: { name: 'xAI Grok', shortName: 'SuperGrok / X Premium' },
+        ...runtimeHostOAuthExistingLoginBridges(
+          window.maka.xaiOAuth,
+          host,
+          connectionId,
+        ),
+        display: { name: connectionLabel ?? 'xAI Grok', shortName: 'SuperGrok / X Premium' },
         showsDeviceCode: false,
       };
     default:
@@ -104,7 +119,7 @@ export function oauthLoginServiceFor(
 
 export interface ConnectionDetailProps {
   bridge: ConnectionsBridge;
-  connection: LlmConnection;
+  connection: IdentifiedLlmConnection;
   isDefault: boolean;
   onChanged(): Promise<void>;
   onDeleted(): Promise<void>;
@@ -123,6 +138,10 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
   const locale = useUiLocale();
   const copy = getProviderSettingsCopy(locale).detail;
   const { connection } = props;
+  const connectionIdentity = {
+    connectionId: connection.connectionId,
+    slug: connection.slug,
+  } as const;
   const defaults = PROVIDER_DEFAULTS[connection.providerType];
   const [apiKey, setApiKey] = useState('');
   const [hasSecret, setHasSecret] = useState<CredentialPresenceStatus>(
@@ -160,7 +179,12 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
   // notice itself.
   const retired = isRetiredProvider(connection.providerType);
   const oauthLoginService = needsOAuth && !retired && connection.connectionId
-    ? oauthLoginServiceFor(connection.providerType, host, connection.connectionId)
+    ? oauthLoginServiceFor(
+        connection.providerType,
+        host,
+        connection.connectionId,
+        `${connection.name} · ${connection.slug}`,
+      )
     : null;
   const usesGitHubCopilotLogin = connection.providerType === 'github-copilot';
   const supportsRemoteDiscovery = providerSupportsModelDiscovery(connection.providerType);
@@ -224,7 +248,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
     }
     setHasSecret('loading');
     void props.bridge
-      .hasSecret(connection.slug)
+      .hasSecret(connectionIdentity)
       .then((next) => {
         if (isConnectionDetailCurrent(lifecycle)) setHasSecret(next);
       })
@@ -236,7 +260,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
           providerPanelActionErrorMessage(error, locale),
         );
       });
-  }, [props.bridge, connection.slug, probesCredential, reportHostError]);
+  }, [props.bridge, connection.connectionId, connection.slug, probesCredential, reportHostError]);
 
   useEffect(() => {
     const nextSnapshot = connectionDetailSnapshot(connection, defaults.baseUrl);
@@ -314,14 +338,14 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
     let saved = false;
     try {
       await props.bridge.update(
-        connection.slug,
+        connectionIdentity,
         field === 'key' ? { apiKey } : field === 'name' ? { name: draftName } : { baseUrl },
       );
       saved = true;
       if (!isConnectionDetailCurrent(lifecycle)) return true;
       const wroteNewKey = field === 'key' && apiKey.length > 0;
       if (wroteNewKey) setApiKey('');
-      const nextHasSecret = probesCredential ? await props.bridge.hasSecret(connection.slug) : true;
+      const nextHasSecret = probesCredential ? await props.bridge.hasSecret(connectionIdentity) : true;
       if (!isConnectionDetailCurrent(lifecycle)) return true;
       setHasSecret(nextHasSecret);
       await props.onChanged();
@@ -375,7 +399,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
     setEnabledModelIds(next);
     let saved = false;
     try {
-      await props.bridge.update(connection.slug, { enabledModelIds: next });
+      await props.bridge.update(connectionIdentity, { enabledModelIds: next });
       saved = true;
       if (!isConnectionDetailCurrent(lifecycle)) return;
       await props.onChanged();
@@ -546,7 +570,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
       // Send the whole table — the update contract is whole-table replace —
       // after the write-path sanitizer so a hand-assembled draft degrades the
       // same way a saved document would.
-      await props.bridge.update(connection.slug, {
+      await props.bridge.update(connectionIdentity, {
         relayModelProfiles: draftedRelayProfiles ?? null,
       });
       if (!isConnectionDetailCurrent(lifecycle)) return true;
@@ -598,7 +622,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
     setEnabledModelIds(next);
     let saved = false;
     try {
-      await props.bridge.update(connection.slug, {
+      await props.bridge.update(connectionIdentity, {
         enabledModelIds: next,
         relayModelProfiles: { ...(savedRelayProfiles ?? {}), [modelId]: { contextWindow } },
       });
@@ -643,7 +667,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
       // to a field this page no longer owns, which is '' once the user enables
       // no models. Left unset, a zero-model connection still verifies its
       // credential against a fallback instead of failing with 'No model to test'.
-      const result: ConnectionTestResult = await props.bridge.test(connection.slug);
+      const result: ConnectionTestResult = await props.bridge.test(connectionIdentity);
       if (!isConnectionDetailCurrent(lifecycle)) return;
       if (result.ok) {
         // The backend probes the enabled models first, then the provider
@@ -708,7 +732,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
       // Backend returns a `ModelDiscoveryResult` envelope and rejects empty or
       // malformed catalogs before persistence. Trust its explicit source
       // instead of reconstructing cache provenance in the renderer.
-      const result = await props.bridge.fetchModels(connection.slug);
+      const result = await props.bridge.fetchModels(connectionIdentity);
       fetched = true;
       if (!isConnectionDetailCurrent(lifecycle)) return;
       setModels(result.models);
@@ -764,7 +788,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
     }
     let deleted = false;
     try {
-      await props.bridge.delete(connection.slug);
+      await props.bridge.delete(connectionIdentity);
       deleted = true;
       if (!isConnectionDetailCurrent(lifecycle)) return;
       await props.onDeleted();
@@ -786,7 +810,7 @@ export function useConnectionDetail(props: ConnectionDetailProps) {
   async function refreshAfterRelogin() {
     const lifecycle = connectionDetailLifecycleRef.current;
     try {
-      const nextHasSecret = await props.bridge.hasSecret(connection.slug);
+      const nextHasSecret = await props.bridge.hasSecret(connectionIdentity);
       if (!isConnectionDetailCurrent(lifecycle)) return;
       setHasSecret(nextHasSecret);
     } catch (error) {
@@ -859,7 +883,7 @@ type ConnectionDetailSnapshot = {
 };
 
 function connectionDetailSnapshot(
-  connection: LlmConnection,
+  connection: IdentifiedLlmConnection,
   defaultBaseUrl: string | undefined,
 ): ConnectionDetailSnapshot {
   return {
diff --git a/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts b/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
index 57006a0c82..42ab18ee47 100644
--- a/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
+++ b/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
@@ -61,13 +61,26 @@ export interface SubscriptionSnapshot {
   errorMessage?: string;
 }
 
-export interface OAuthLoginFlowBridge {
+export interface OAuthConnectionIdentity {
+  connectionId: string;
+  slug: string;
+  providerType: 'openai-codex' | 'xai-oauth';
+}
+
+export interface OAuthAuthorizationFlowBridge {
   getAuthUrl(): Promise<
-    { authRequestId: string; stateHint: string } | { ok: boolean; reason?: string; message: string }
+    { authRequestId: string; stateHint: string; connection: OAuthConnectionIdentity }
+    | { ok: boolean; reason?: string; message: string }
   >;
   openAuthUrl(authRequestId: string): Promise<{ ok: true } | { ok: false; reason: string; message: string }>;
-  completeAuthorization(authRequestId: string): Promise<{ ok: true } | { ok: false; reason: string; message: string }>;
+  completeAuthorization(authRequestId: string): Promise<
+    { ok: true; connection: OAuthConnectionIdentity }
+    | { ok: false; reason: string; message: string }
+  >;
   cancelAuthorization(authRequestId?: string): Promise<{ ok: true }>;
+}
+
+export interface OAuthAccountFlowBridge {
   getAccountState(): Promise<unknown>;
   logout(): Promise<{ ok: true } | { ok: false; reason: string; message: string }>;
 }
@@ -103,30 +116,43 @@ export interface OAuthLoginFlowController {
   errorMessage: string | null;
   actionBusy: boolean;
   startLogin(): Promise<void>;
-  logout(): Promise<void>;
+  logout: (() => Promise<void>) | undefined;
   refresh(): Promise<boolean>;
   // Direct account flows only (GitHub Copilot 重新验证); undefined for the
   // browser-assisted services so they cannot render a dead action.
   refreshTokens: (() => Promise<void>) | undefined;
 }
 
-export function useOAuthLoginFlow(params: {
-  bridge: OAuthLoginFlowBridge;
-  display: OAuthLoginFlowDisplay;
-  // Fired after a successful completeAuthorization (browser handoff done).
-  // The detail sheet uses it to re-probe hasSecret + reload connection status;
-  // catalog modals use it to refresh both their account card and the shared
-  // model connection list without waiting for the modal to close.
-  onLoginSuccess?: () => void | Promise<void>;
-  // When present, startLogin runs this one-shot import instead of the
-  // getAuthUrl -> openAuthUrl -> completeAuthorization handoff, and the
-  // controller exposes the extra `refreshTokens` action.
-  direct?: OAuthDirectAccountFlow;
-}): OAuthLoginFlowController {
-  const { bridge, display } = params;
+type OAuthLoginFlowParams =
+  | {
+      mode: 'create';
+      authorizationBridge: OAuthAuthorizationFlowBridge;
+      display: OAuthLoginFlowDisplay;
+      onLoginSuccess(connection: OAuthConnectionIdentity): void | Promise<void>;
+    }
+  | {
+      mode: 'existing';
+      authorizationBridge: OAuthAuthorizationFlowBridge;
+      accountBridge: OAuthAccountFlowBridge;
+      display: OAuthLoginFlowDisplay;
+      onLoginSuccess?: (connection: OAuthConnectionIdentity) => void | Promise<void>;
+      onAccountChanged?: () => void | Promise<void>;
+    }
+  | {
+      mode: 'direct';
+      accountBridge: OAuthAccountFlowBridge;
+      display: OAuthLoginFlowDisplay;
+      onLoginSuccess?: () => void | Promise<void>;
+      direct: OAuthDirectAccountFlow;
+    };
+
+export function useOAuthLoginFlow(params: OAuthLoginFlowParams): OAuthLoginFlowController {
+  const { display } = params;
   const locale = useUiLocale();
   const copy = getProviderSettingsCopy(locale).oauthFlow;
-  const direct = params.direct;
+  const direct = params.mode === 'direct' ? params.direct : undefined;
+  const authorizationBridge = params.mode === 'direct' ? undefined : params.authorizationBridge;
+  const accountBridge = params.mode === 'create' ? undefined : params.accountBridge;
   const toast = useToast();
   const reportHostError = useRuntimeHostSettingsErrorReporter();
   const [state, setState] = useState<SubscriptionSnapshot | null>(null);
@@ -139,8 +165,9 @@ export function useOAuthLoginFlow(params: {
   const oauthLoginFlowMountedRef = useMountedRef();
 
   async function refresh(): Promise<boolean> {
+    if (!accountBridge) return true;
     try {
-      const next = (await bridge.getAccountState()) as SubscriptionSnapshot;
+      const next = (await accountBridge.getAccountState()) as SubscriptionSnapshot;
       if (!oauthLoginFlowMountedRef.current) return false;
       setState(next);
       setErrorMessage(null);
@@ -155,10 +182,15 @@ export function useOAuthLoginFlow(params: {
   }
 
   useEffect(() => {
-    void refresh();
+    if (accountBridge) void refresh();
     return () => {
       pendingGuard.finish();
-      teardownPendingAuthorization(authRequestIdRef, (id) => void bridge.cancelAuthorization(id));
+      if (authorizationBridge) {
+        teardownPendingAuthorization(
+          authRequestIdRef,
+          (id) => void authorizationBridge.cancelAuthorization(id),
+        );
+      }
     };
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
@@ -191,7 +223,9 @@ export function useOAuthLoginFlow(params: {
         }
         await refresh();
         if (!oauthLoginFlowMountedRef.current) return;
-        if (result.ok && params.onLoginSuccess) await params.onLoginSuccess();
+        if (result.ok && params.mode === 'direct' && params.onLoginSuccess) {
+          await params.onLoginSuccess();
+        }
       } catch (error) {
         if (!oauthLoginFlowMountedRef.current) return;
         reportHostError(
@@ -204,7 +238,8 @@ export function useOAuthLoginFlow(params: {
       return;
     }
     try {
-      const payload = await bridge.getAuthUrl();
+      if (!authorizationBridge || params.mode === 'direct') return;
+      const payload = await authorizationBridge.getAuthUrl();
       if ('ok' in payload) {
         if (!oauthLoginFlowMountedRef.current) return;
         const failureMessage = payload.ok ? copy.retry : subscriptionResultMessage(payload.message, copy.startFailedRetry, locale);
@@ -215,18 +250,18 @@ export function useOAuthLoginFlow(params: {
       authRequestIdRef.current = payload.authRequestId;
       if (!oauthLoginFlowMountedRef.current) {
         authRequestIdRef.current = null;
-        void bridge.cancelAuthorization(payload.authRequestId);
+        void authorizationBridge.cancelAuthorization(payload.authRequestId);
         return;
       }
       setAuthRequestId(payload.authRequestId);
       setStateHint(payload.stateHint);
-      const opened = await bridge.openAuthUrl(payload.authRequestId);
+      const opened = await authorizationBridge.openAuthUrl(payload.authRequestId);
       if (!oauthLoginFlowMountedRef.current) return;
       if (!opened.ok) {
         const message = subscriptionResultMessage(opened.message, copy.openFailedRetry, locale);
         reportHostError(copy.openFailed, message);
         setErrorMessage(message);
-        void bridge.cancelAuthorization(payload.authRequestId);
+        void authorizationBridge.cancelAuthorization(payload.authRequestId);
         authRequestIdRef.current = null;
         setAuthRequestId(null);
         setStateHint(null);
@@ -235,7 +270,7 @@ export function useOAuthLoginFlow(params: {
       const refreshed = await refresh();
       if (!oauthLoginFlowMountedRef.current || !refreshed) return;
       // Wait for the backend to finish polling the provider.
-      const result = await bridge.completeAuthorization(payload.authRequestId);
+      const result = await authorizationBridge.completeAuthorization(payload.authRequestId);
       if (!oauthLoginFlowMountedRef.current) return;
       authRequestIdRef.current = null;
       setAuthRequestId(null);
@@ -244,7 +279,7 @@ export function useOAuthLoginFlow(params: {
         toast.success(copy.loginSuccess, copy.bound(display.name));
         await refresh();
         if (!oauthLoginFlowMountedRef.current) return;
-        if (params.onLoginSuccess) await params.onLoginSuccess();
+        if (params.onLoginSuccess) await params.onLoginSuccess(result.connection);
       } else {
         const message = subscriptionResultMessage(result.message, copy.incompleteRetry, locale);
         reportHostError(copy.incomplete, message);
@@ -254,7 +289,9 @@ export function useOAuthLoginFlow(params: {
       if (!oauthLoginFlowMountedRef.current) return;
       const pendingAuthRequestId = authRequestIdRef.current;
       authRequestIdRef.current = null;
-      if (pendingAuthRequestId) void bridge.cancelAuthorization(pendingAuthRequestId);
+      if (pendingAuthRequestId && authorizationBridge) {
+        void authorizationBridge.cancelAuthorization(pendingAuthRequestId);
+      }
       setAuthRequestId(null);
       setStateHint(null);
       const message = subscriptionActionErrorMessage(error, locale);
@@ -266,6 +303,7 @@ export function useOAuthLoginFlow(params: {
   }
 
   async function logout() {
+    if (!accountBridge) return;
     if (!beginPendingAction('logout')) return;
     try {
       // Direct-import flows keep their original no-confirm, silent-success
@@ -281,13 +319,16 @@ export function useOAuthLoginFlow(params: {
         });
         if (!ok) return;
       }
-      const result = await bridge.logout();
+      const result = await accountBridge.logout();
       if (!oauthLoginFlowMountedRef.current) return;
       if (result.ok) {
         if (!direct) {
           toast.success(copy.loggedOut, copy.credentialsCleared);
         }
         await refresh();
+        if (params.mode === 'existing' && params.onAccountChanged) {
+          await params.onAccountChanged();
+        }
       } else if (direct) {
         reportHostError(
           copy.accountActionFailed(display.name),
@@ -355,7 +396,7 @@ export function useOAuthLoginFlow(params: {
     errorMessage,
     actionBusy,
     startLogin,
-    logout,
+    logout: accountBridge ? logout : undefined,
     refresh,
     refreshTokens: direct ? refreshTokens : undefined,
   };
diff --git a/apps/desktop/src/shared/desktop-connection-snapshot.ts b/apps/desktop/src/shared/desktop-connection-snapshot.ts
index 3a75acff13..3fe184bd4f 100644
--- a/apps/desktop/src/shared/desktop-connection-snapshot.ts
+++ b/apps/desktop/src/shared/desktop-connection-snapshot.ts
@@ -20,6 +20,12 @@
 import type { ChatModelChoice } from '@maka/core/chat-model-choice';
 import type { IdentifiedLlmConnection } from '@maka/core/llm-connections';
 
+/** Immutable identity plus the human-readable locator last shown by Desktop. */
+export interface DesktopConnectionIdentity {
+  readonly connectionId: string;
+  readonly slug: string;
+}
+
 export interface DesktopConnectionSnapshot {
   readonly connections: IdentifiedLlmConnection[];
   readonly defaultConnection: string | null;
diff --git a/apps/desktop/stories/settings/provider-settings.stories.tsx b/apps/desktop/stories/settings/provider-settings.stories.tsx
index c83957e808..d8b9b296ec 100644
--- a/apps/desktop/stories/settings/provider-settings.stories.tsx
+++ b/apps/desktop/stories/settings/provider-settings.stories.tsx
@@ -214,6 +214,37 @@ const problemConnections = [
   }),
 ];
 
+const oauthConnections = [
+  makeConnection({
+    slug: 'openai-codex',
+    name: 'OpenAI Codex',
+    providerType: 'openai-codex',
+    defaultModel: 'gpt-5',
+    lastTestStatus: 'verified',
+  }),
+  makeConnection({
+    slug: 'openai-codex-2',
+    name: 'OpenAI Codex',
+    providerType: 'openai-codex',
+    defaultModel: 'gpt-5',
+    lastTestStatus: 'verified',
+  }),
+  makeConnection({
+    slug: 'openai-codex-3',
+    name: 'OpenAI Codex',
+    providerType: 'openai-codex',
+    defaultModel: 'gpt-5',
+    lastTestStatus: 'verified',
+  }),
+  makeConnection({
+    slug: 'xai-oauth',
+    name: 'xAI Grok',
+    providerType: 'xai-oauth',
+    defaultModel: 'grok-4',
+    lastTestStatus: 'verified',
+  }),
+];
+
 function createBridge(input: {
   connections?: IdentifiedLlmConnection[];
   defaultSlug?: string | null;
@@ -233,8 +264,8 @@ function createBridge(input: {
         chatModelChoices: buildChatModelChoices(connections),
       };
     },
-    async setDefault(slug) {
-      defaultSlug = slug;
+    async setDefault(connection) {
+      defaultSlug = connection?.slug ?? null;
     },
     async create(next) {
       const connection = makeConnection({
@@ -249,8 +280,8 @@ function createBridge(input: {
       defaultSlug ??= connection.slug;
       return connection;
     },
-    async update(slug, patch) {
-      const current = connections.find((connection) => connection.slug === slug);
+    async update(identity, patch) {
+      const current = connections.find((connection) => connection.connectionId === identity.connectionId && connection.slug === identity.slug);
       if (!current) throw new Error('连接不存在');
       const updated: IdentifiedLlmConnection = {
         ...current,
@@ -267,15 +298,15 @@ function createBridge(input: {
             : (patch.requestBodyOverlay ?? undefined),
         updatedAt: NOW,
       };
-      connections = connections.map((connection) => connection.slug === slug ? updated : connection);
+      connections = connections.map((connection) => connection.connectionId === identity.connectionId ? updated : connection);
       return updated;
     },
-    async delete(slug) {
-      connections = connections.filter((connection) => connection.slug !== slug);
-      if (defaultSlug === slug) defaultSlug = connections[0]?.slug ?? null;
+    async delete(identity) {
+      connections = connections.filter((connection) => connection.connectionId !== identity.connectionId);
+      if (defaultSlug === identity.slug) defaultSlug = connections[0]?.slug ?? null;
     },
-    async test(slug): Promise<ConnectionTestResult> {
-      if (slug.includes('rate-limit')) {
+    async test(identity): Promise<ConnectionTestResult> {
+      if (identity.slug.includes('rate-limit')) {
         return {
           ok: false,
           statusCode: 429,
@@ -285,11 +316,11 @@ function createBridge(input: {
       }
       return { ok: true, latencyMs: 328, modelTested: 'glm-4.7' };
     },
-    async fetchModels(slug): Promise<ModelDiscoveryResult> {
+    async fetchModels(identity): Promise<ModelDiscoveryResult> {
       return {
         models: [
-          { id: slug.includes('openai') ? 'gpt-5' : 'glm-4.7' },
-          { id: slug.includes('openai') ? 'gpt-4o' : 'glm-4.6' },
+          { id: identity.slug.includes('openai') ? 'gpt-5' : 'glm-4.7' },
+          { id: identity.slug.includes('openai') ? 'gpt-4o' : 'glm-4.6' },
         ],
         source: 'fetched',
         fetchedAt: NOW,
@@ -310,17 +341,65 @@ function createBridge(input: {
   };
 }
 
-function installSubscriptionFixtures() {
+function createOAuthSuccessLifecycleFixture() {
+  const bridge = createBridge({ connections: oauthConnections });
+  let eventHandler: (() => void) | undefined;
+  let delayNextSnapshot = false;
+  let releaseSupersededSnapshot: (() => void) | undefined;
+  return {
+    bridge: {
+      ...bridge,
+      async getSnapshot() {
+        const snapshot = await bridge.getSnapshot();
+        if (delayNextSnapshot) {
+          delayNextSnapshot = false;
+          return new Promise<typeof snapshot>((resolve) => {
+            releaseSupersededSnapshot = () => resolve(snapshot);
+          });
+        }
+        const release = releaseSupersededSnapshot;
+        releaseSupersededSnapshot = undefined;
+        queueMicrotask(() => release?.());
+        return snapshot;
+      },
+      subscribeEvents(handler: () => void) {
+        eventHandler = handler;
+        return () => {
+          if (eventHandler === handler) eventHandler = undefined;
+        };
+      },
+    } satisfies ConnectionsBridge,
+    onOAuthComplete() {
+      // `create` mutates the fixture before its already-resolved Promise is
+      // observed. Delay the completion callback's reload, then emit the Host
+      // event so its newer reload wins the ticket and releases the older one:
+      // this is the exact ordering that used to strand the setup page.
+      void bridge.create({
+        slug: 'openai-codex-4',
+        name: 'OpenAI Codex',
+        providerType: 'openai-codex',
+        defaultModel: 'gpt-5',
+      });
+      delayNextSnapshot = true;
+      window.setTimeout(() => eventHandler?.(), 0);
+    },
+  };
+}
+
+function installSubscriptionFixtures(onOAuthComplete?: () => void) {
   const target = window as unknown as {
     maka?: Record<string, unknown>;
   };
   target.maka = {
     ...(target.maka ?? {}),
-    openAiCodex: browserSubscriptionFixture({
-      runtimeState: 'authenticated',
-      email: 'codex@example.com',
-      plan: 'Plus',
-    }),
+    openAiCodex: browserSubscriptionFixture(
+      {
+        runtimeState: 'authenticated',
+        email: 'codex@example.com',
+        plan: 'Plus',
+      },
+      onOAuthComplete,
+    ),
     githubCopilotSubscription: browserSubscriptionFixture({
       runtimeState: 'not_logged_in',
     }),
@@ -329,9 +408,14 @@ function installSubscriptionFixtures() {
 }
 
 function xaiDeviceSubscriptionFixture() {
+  const connection = {
+    connectionId: 'connection-xai-oauth-2',
+    slug: 'xai-oauth-2',
+    providerType: 'xai-oauth' as const,
+  };
   return {
     getAccountState: async () => ({ provider: 'xai-oauth', runtimeState: 'authorizing' }),
-    getAuthUrl: async () => ({ authRequestId: 'storybook-xai', stateHint: 'ABCD-EFGH' }),
+    getAuthUrl: async () => ({ authRequestId: 'storybook-xai', stateHint: 'ABCD-EFGH', connection }),
     openAuthUrl: async () => ({ ok: true }),
     completeAuthorization: async () => new Promise<never>(() => undefined),
     cancelAuthorization: async () => ({ ok: true }),
@@ -344,12 +428,20 @@ function browserSubscriptionFixture(state: {
   email?: string;
   plan?: string;
   errorMessage?: string;
-}) {
+}, onComplete?: () => void) {
+  const connection = {
+    connectionId: 'connection-openai-codex-4',
+    slug: 'openai-codex-4',
+    providerType: 'openai-codex' as const,
+  };
   return {
     getAccountState: async () => state,
-    getAuthUrl: async () => ({ authRequestId: 'storybook-oauth', stateHint: 'storybook' }),
+    getAuthUrl: async () => ({ authRequestId: 'storybook-oauth', stateHint: 'storybook', connection }),
     openAuthUrl: async () => ({ ok: true }),
-    completeAuthorization: async () => ({ ok: true }),
+    completeAuthorization: async () => {
+      onComplete?.();
+      return { ok: true as const, connection };
+    },
     cancelAuthorization: async () => ({ ok: true }),
     logout: async () => ({ ok: true }),
   };
@@ -358,13 +450,14 @@ function browserSubscriptionFixture(state: {
 function ProviderStoryFrame(props: {
   bridge: ConnectionsBridge;
   autoOpen?: AutoOpenTarget;
+  onOAuthComplete?: () => void;
 }) {
   const rootRef = useRef<HTMLDivElement>(null);
   const clickedRef = useRef(false);
 
   useEffect(() => {
-    installSubscriptionFixtures();
-  }, []);
+    installSubscriptionFixtures(props.onOAuthComplete);
+  }, [props.onOAuthComplete]);
 
   useEffect(() => {
     const autoOpen = props.autoOpen;
@@ -496,10 +589,11 @@ function clickAutoOpenTarget(root: HTMLElement, target: AutoOpenTarget): boolean
 function ProviderStory(props: {
   bridge: ConnectionsBridge;
   autoOpen?: AutoOpenTarget;
+  onOAuthComplete?: () => void;
 }): ReactNode {
   return (
     <RuntimeHostSettingsTarget host={{ profileId: 'local', hostId: 'storybook-local-host' }}>
-      <ProviderStoryFrame bridge={props.bridge} autoOpen={props.autoOpen} />
+      <ProviderStoryFrame bridge={props.bridge} autoOpen={props.autoOpen} onOAuthComplete={props.onOAuthComplete} />
     </RuntimeHostSettingsTarget>
   );
 }
@@ -601,6 +695,72 @@ export const AddConnectionCatalog: Story = {
   ),
 };
 
+// Real path: 设置 → 模型 → 添加连接. OAuth rows are enrollment intents and
+// describe the number of configured Connection entities, never provider-wide
+// login state.
+export const OAuthCatalogNoAccounts: Story = {
+  render: () => <ProviderStory bridge={createBridge({ connections: [] })} autoOpen="catalog" />,
+  play: async ({ canvasElement }) => {
+    await expect(within(canvasElement).findByText('使用 ChatGPT Plus / Pro 账号添加连接。')).resolves.toBeTruthy();
+  },
+};
+
+export const OAuthCatalogOneAccount: Story = {
+  render: () => (
+    <ProviderStory
+      bridge={createBridge({ connections: oauthConnections.slice(0, 1) })}
+      autoOpen="catalog"
+    />
+  ),
+  play: async ({ canvasElement }) => {
+    await expect(within(canvasElement).findByText('已有 1 个连接 · 添加另一个账号')).resolves.toBeTruthy();
+  },
+};
+
+export const OAuthCatalogMultipleAccounts: Story = {
+  render: () => (
+    <ProviderStory
+      bridge={createBridge({ connections: oauthConnections })}
+      autoOpen="catalog"
+    />
+  ),
+  play: async ({ canvasElement }) => {
+    await expect(within(canvasElement).findByText('已有 3 个连接 · 添加另一个账号')).resolves.toBeTruthy();
+    await expect(within(canvasElement).findByText('已有 1 个连接 · 添加另一个账号')).resolves.toBeTruthy();
+  },
+};
+
+export const OAuthConnectionsDisambiguated: Story = {
+  render: () => <ProviderStory bridge={createBridge({ connections: oauthConnections })} />,
+  play: async ({ canvasElement }) => {
+    const canvas = within(canvasElement);
+    await expect(canvas.findByText('OpenAI Codex · openai-codex')).resolves.toBeTruthy();
+    await expect(canvas.findByText('OpenAI Codex · openai-codex-2')).resolves.toBeTruthy();
+    await expect(canvas.findByText('OpenAI Codex · openai-codex-3')).resolves.toBeTruthy();
+  },
+};
+
+export const OAuthCreateAdoptsExactConnection: Story = {
+  render: () => {
+    const fixture = createOAuthSuccessLifecycleFixture();
+    return <ProviderStory bridge={fixture.bridge} onOAuthComplete={fixture.onOAuthComplete} />;
+  },
+  play: async ({ canvasElement }) => {
+    const canvas = within(canvasElement);
+    await userEvent.click(await canvas.findByRole('button', { name: '添加连接' }));
+    await userEvent.click(await canvas.findByRole('button', { name: /添加账号连接:OpenAI Codex/ }));
+    await userEvent.click(await canvas.findByRole('button', { name: '登录并添加' }));
+
+    await expect(canvas.findByRole('region', { name: 'OpenAI Codex · openai-codex-4' })).resolves.toBeTruthy();
+    await userEvent.click(await canvas.findByRole('button', { name: '返回模型连接' }));
+    const createdRow = canvasElement.querySelector<HTMLElement>(
+      '[data-connection-id="connection-openai-codex-4"]',
+    );
+    await expect(createdRow).not.toBeNull();
+    await expect(within(createdRow!).getByRole('button')).toHaveFocus();
+  },
+};
+
 // Real path: 设置 → 模型 → 添加连接 → pick a provider — level three, its form.
 export const AddProvider: Story = {
   render: () => (
diff --git a/apps/desktop/stories/settings/settings-pages.stories.tsx b/apps/desktop/stories/settings/settings-pages.stories.tsx
index 7acf3501b1..7fcfa3af03 100644
--- a/apps/desktop/stories/settings/settings-pages.stories.tsx
+++ b/apps/desktop/stories/settings/settings-pages.stories.tsx
@@ -148,8 +148,8 @@ const connectionsBridge: ConnectionsBridge = {
   async create(next) {
     return makeConnection({ slug: next.slug, name: next.name, providerType: next.providerType });
   },
-  async update(slug, patch) {
-    const current = connections.find((c) => c.slug === slug)!;
+  async update(identity, patch) {
+    const current = connections.find((c) => c.connectionId === identity.connectionId && c.slug === identity.slug)!;
     return {
       ...current,
       ...patch,
@@ -172,9 +172,9 @@ const connectionsBridge: ConnectionsBridge = {
   async test() {
     return { ok: true, latencyMs: 210, modelTested: 'glm-4.7' };
   },
-  async fetchModels(slug) {
+  async fetchModels(identity) {
     return {
-      models: slug.includes('openai') ? [{ id: 'gpt-5' }] : [{ id: 'glm-4.7' }],
+      models: identity.slug.includes('openai') ? [{ id: 'gpt-5' }] : [{ id: 'glm-4.7' }],
       source: 'fetched',
       fetchedAt: NOW,
     };
@@ -185,7 +185,7 @@ const connectionsBridge: ConnectionsBridge = {
   async getRequestHeaders() {
     return { names: [] };
   },
-  async setRequestHeaders(_slug, headers) {
+  async setRequestHeaders(_identity, headers) {
     return { names: headers.map(({ name }) => name) };
   },
   subscribeEvents() {
@@ -965,8 +965,8 @@ const withGeneralCachedRevalidationBridge = withScopedMakaBridge({
 let generationStoryCatalogPending = false;
 let generationStoryRuntimeHostProfiles = runtimeHostProfiles;
 let generationStoryConnectionsPending = false;
-let generationStoryCodexEmail = 'old-generation@example.com';
-let generationStoryCodexAccountReads = 0;
+let generationStoryCopilotEmail = 'old-generation@example.com';
+let generationStoryCopilotAccountReads = 0;
 let generationStoryOpenedAuthIds: string[] = [];
 let generationStoryCancelledAuthIds: string[] = [];
 let generationStoryCopilotImportAttempts = 0;
@@ -984,8 +984,8 @@ function resetGenerationStoryBridge(
   generationStoryCatalogPending = false;
   generationStoryRuntimeHostProfiles = snapshot;
   generationStoryConnectionsPending = false;
-  generationStoryCodexEmail = 'old-generation@example.com';
-  generationStoryCodexAccountReads = 0;
+  generationStoryCopilotEmail = 'old-generation@example.com';
+  generationStoryCopilotAccountReads = 0;
   generationStoryOpenedAuthIds = [];
   generationStoryCancelledAuthIds = [];
   generationStoryCopilotImportAttempts = 0;
@@ -1025,13 +1025,13 @@ const withGeneralHostGenerationRevalidationBridge = withScopedMakaBridge({
 const withModelsOAuthGenerationRevalidationBridge = withScopedMakaBridge({
   ...makaBridge,
   runtimeHostProfiles: generationStoryRuntimeHostProfilesBridge,
-  openAiCodex: {
-    ...makaBridge.openAiCodex,
+  githubCopilotSubscription: {
+    ...makaBridge.githubCopilotSubscription,
     getAccountState: async () => {
-      generationStoryCodexAccountReads += 1;
+      generationStoryCopilotAccountReads += 1;
       return {
         runtimeState: 'authenticated' as const,
-        email: generationStoryCodexEmail,
+        email: generationStoryCopilotEmail,
         plan: 'Plus',
       };
     },
@@ -1058,6 +1058,11 @@ const withModelsOAuthAuthorizationGenerationBridge = withScopedMakaBridge({
     getAuthUrl: async () => ({
       authRequestId: 'authorization-from-generation-1',
       stateHint: 'GEN1-CODE',
+      connection: {
+        connectionId: 'connection-openai-codex-generation-1',
+        slug: 'openai-codex-generation-1',
+        providerType: 'openai-codex' as const,
+      },
     }),
     openAuthUrl: async (authRequestId: string) => {
       generationStoryOpenedAuthIds.push(authRequestId);
@@ -2261,9 +2266,10 @@ export const ModelsConnectionsHostGenerationRevalidation: Story = {
 };
 
 // A ready event can replace the Runtime Host without changing
-// profileId:hostId. The catalog route stays mounted, but its OAuth account
-// snapshot belongs to the Host generation and must be read again before the
-// previous account can be presented as current.
+// profileId:hostId. The catalog route stays mounted, but Copilot's singleton
+// import state belongs to the Host generation and must be read again before
+// the previous account can be presented as current. Codex and xAI instead
+// project their Connection counts from the connection catalog.
 export const ModelsOAuthHostGenerationRevalidation: Story = {
   decorators: [withModelsOAuthGenerationRevalidationBridge],
   render: () => {
@@ -2279,11 +2285,11 @@ export const ModelsOAuthHostGenerationRevalidation: Story = {
   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
     await canvas.findByText('old-generation@example.com');
-    const readsBeforeReplacement = generationStoryCodexAccountReads;
+    const readsBeforeReplacement = generationStoryCopilotAccountReads;
     const listener = generationStoryProfileListener;
     if (!listener) throw new Error('Runtime Host generation listener did not subscribe');
 
-    generationStoryCodexEmail = 'new-generation@example.com';
+    generationStoryCopilotEmail = 'new-generation@example.com';
     listener({
       epoch: 'storybook-generation-2',
       profileId: 'local',
@@ -2297,7 +2303,7 @@ export const ModelsOAuthHostGenerationRevalidation: Story = {
 
     await canvas.findByText('new-generation@example.com');
     await expect(canvas.queryByText('old-generation@example.com')).not.toBeInTheDocument();
-    await expect(generationStoryCodexAccountReads).toBeGreaterThan(readsBeforeReplacement);
+    await expect(generationStoryCopilotAccountReads).toBeGreaterThan(readsBeforeReplacement);
     await expect(
       canvasElement.querySelector('[data-maka-contract="provider-catalog"]'),
     ).toBeInTheDocument();
@@ -2322,9 +2328,9 @@ export const ModelsOAuthAuthorizationHostGenerationRevalidation: Story = {
   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
     await userEvent.click(await canvas.findByRole('button', {
-      name: /打开 OAuth 登录:OpenAI Codex/,
+      name: /添加账号连接:OpenAI Codex/,
     }));
-    await userEvent.click(await canvas.findByRole('button', { name: '登录 Codex' }));
+    await userEvent.click(await canvas.findByRole('button', { name: '登录并添加' }));
     await waitForStoryCondition(
       () => generationStoryOpenedAuthIds.includes('authorization-from-generation-1'),
       'OAuth authorization did not reach the browser handoff',
@@ -2350,7 +2356,7 @@ export const ModelsOAuthAuthorizationHostGenerationRevalidation: Story = {
     await expect(
       canvasElement.querySelector('[data-maka-contract="provider-setup"]'),
     ).toBeInTheDocument();
-    await expect(await canvas.findByRole('button', { name: '登录 Codex' })).toBeEnabled();
+    await expect(await canvas.findByRole('button', { name: '登录并添加' })).toBeEnabled();
     await expect(generationStoryCancelledAuthIds).toEqual([
       'authorization-from-generation-1',
     ]);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org