Kaynağa Gözat

refactor(client): use settings Remote namespaces

imccyu 1 ay önce
ebeveyn
işleme
5918dd205e
65 değiştirilmiş dosya ile 1103 ekleme ve 819 silme
  1. 4 4
      apps/web/tests/onboarding-deepseek-config.e2e.ts
  2. 35 24
      apps/web/tests/preview-boot.e2e.ts
  3. 2 2
      apps/web/tests/startup-rpc-budget.e2e.ts
  4. 2 0
      packages/api/remotes/package.json
  5. 15 3
      packages/api/remotes/src/client/index.ts
  6. 3 0
      packages/api/remotes/tsconfig.client.json
  7. 0 10
      packages/api/session-controller/tests/fake-api.client.ts
  8. 8 6
      packages/client/connection/package.json
  9. 2 2
      packages/client/connection/src/client/api.ts
  10. 91 55
      packages/client/connection/src/client/fixture.ts
  11. 2 2
      packages/client/connection/src/client/index.ts
  12. 0 10
      packages/client/connection/tests/fake-api.client.ts
  13. 60 14
      packages/client/connection/tests/fixture.client.spec.ts
  14. 3 5
      packages/client/connection/tests/node-half.host.spec.ts
  15. 6 0
      packages/client/connection/tsconfig.client.json
  16. 7 13
      packages/client/locale/tests/apply.client.spec.ts
  17. 7 7
      packages/client/locale/tests/document-language.client.spec.ts
  18. 6 3
      packages/client/ui-agent-preset/src/client/index.ts
  19. 2 1
      packages/client/ui-agent-preset/src/client/section-store.ts
  20. 10 6
      packages/client/ui-agent-preset/src/client/settings-store.ts
  21. 16 10
      packages/client/ui-agent-preset/tests/apply.client.spec.ts
  22. 10 9
      packages/client/ui-agent-preset/tests/section-store.client.spec.ts
  23. 23 22
      packages/client/ui-agent-preset/tests/settings-store.client.spec.ts
  24. 0 3
      packages/client/ui-permission-presets/package.json
  25. 5 4
      packages/client/ui-permission-presets/src/client/index.ts
  26. 10 12
      packages/client/ui-permission-presets/src/client/settings-store.ts
  27. 3 2
      packages/client/ui-permission-presets/tests/browser-plugin.client.spec.ts
  28. 4 6
      packages/client/ui-permission-presets/tests/permission-presets-row.client.spec.tsx
  29. 11 13
      packages/client/ui-permission-presets/tests/settings-store.client.spec.ts
  30. 7 10
      packages/client/ui-settings-general/tests/apply.client.spec.ts
  31. 6 18
      packages/client/ui-settings-general/tests/components.client.spec.tsx
  32. 4 17
      packages/client/ui-settings-general/tests/settings-document-store.client.spec.ts
  33. 8 5
      packages/client/ui-settings-general/tests/shell.client.spec.ts
  34. 15 14
      packages/client/ui-settings-models/src/client/CustomProviderCard.tsx
  35. 2 3
      packages/client/ui-settings-models/src/client/DeepSeekOnboardingDialog.tsx
  36. 11 11
      packages/client/ui-settings-models/src/client/ModelsSection.tsx
  37. 19 16
      packages/client/ui-settings-models/src/client/ProviderEditor.tsx
  38. 10 9
      packages/client/ui-settings-models/src/client/SubagentModelSelectionCard.tsx
  39. 17 5
      packages/client/ui-settings-models/src/client/index.ts
  40. 26 11
      packages/client/ui-settings-models/src/client/store.ts
  41. 41 40
      packages/client/ui-settings-models/tests/apply.client.spec.ts
  42. 153 148
      packages/client/ui-settings-models/tests/components.client.spec.tsx
  43. 21 22
      packages/client/ui-settings-models/tests/onboarding-dialog.client.spec.tsx
  44. 39 29
      packages/client/ui-settings-models/tests/provider-form.client.spec.tsx
  45. 2 2
      packages/client/ui-settings-models/tests/readiness.client.spec.ts
  46. 32 21
      packages/client/ui-settings-models/tests/store.client.spec.ts
  47. 5 4
      packages/client/ui-settings-models/tests/welcome-notice.client.spec.tsx
  48. 8 9
      packages/client/ui-settings-models/tests/welcome-store.client.spec.ts
  49. 0 3
      packages/client/ui-settings-plugins/package.json
  50. 3 4
      packages/client/ui-settings-plugins/src/client/index.ts
  51. 12 9
      packages/client/ui-settings-plugins/src/client/web-search-card-controller.ts
  52. 15 21
      packages/client/ui-settings-plugins/tests/apply.client.spec.ts
  53. 21 24
      packages/client/ui-settings-plugins/tests/stores.client.spec.ts
  54. 9 7
      packages/client/ui-settings/src/client/index.ts
  55. 20 6
      packages/client/ui-settings/src/client/settings-mirror.ts
  56. 21 15
      packages/client/ui-settings/src/client/settings-scope.ts
  57. 3 7
      packages/client/ui-settings/tests/plugin.client.spec.ts
  58. 14 17
      packages/client/ui-settings/tests/settings-mirror.client.spec.ts
  59. 43 43
      packages/client/ui-settings/tests/settings-scope.client.spec.ts
  60. 7 13
      packages/client/ui-theme/tests/apply.client.spec.ts
  61. 0 1
      packages/client/ui-workspace/tests/workspaces-service.client.spec.ts
  62. 2 0
      packages/test-support/client-runtime/src/index.ts
  63. 20 7
      packages/test-support/client-runtime/src/remote.ts
  64. 85 0
      packages/test-support/client-runtime/src/settings-remote.ts
  65. 55 0
      packages/test-support/client-runtime/tests/remote.client.spec.ts

+ 4 - 4
apps/web/tests/onboarding-deepseek-config.e2e.ts

@@ -141,7 +141,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
     // Regression pin for the reload flash: both steps are satisfied, yet each
     // must load private facts before deciding not to show. Dialog chrome lives
     // inside each visible branch, so the deciding window paints and blocks
-    // nothing. Holding settings.describe widens that window from loopback
+    // nothing. Holding settings/describe widens that window from loopback
     // RTT scale to a deterministic hundreds of milliseconds, removing all
     // timing dependence from the sampler assertions below.
     //
@@ -162,7 +162,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
         if (document.getElementById('root')?.inert === true) sightings.push('inert')
       }, 8)
     })
-    // EVERY settings.describe issued before the release is held — not just
+    // EVERY settings/describe issued before the release is held — not just
     // the first — so the pin cannot silently collapse back to loopback
     // timing if a second boot-time consumer of the join ever appears.
     let released = false
@@ -171,7 +171,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
       released = true
       for (const resolve of heldRoutes.splice(0)) resolve()
     }
-    await page.route('**/api/settings.describe', async (route) => {
+    await page.route('**/api/settings/describe', async (route) => {
       if (!released) await new Promise<void>((resolve) => { heldRoutes.push(resolve) })
       await route.continue()
     })
@@ -182,7 +182,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
     await page.waitForTimeout(600)
     releaseDescribe()
     await page.waitForTimeout(400)
-    await page.unroute('**/api/settings.describe')
+    await page.unroute('**/api/settings/describe')
     acknowledgeReloadConnectionLoss(tripwire, warningsBefore)
     expect(await page.evaluate(() =>
       (window as unknown as { __takeoverSightings: string[] }).__takeoverSightings)).toEqual([])

+ 35 - 24
apps/web/tests/preview-boot.e2e.ts

@@ -317,17 +317,6 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
       type Result<T> = { result: { ok: true; value: T } | { ok: false; error: { code: string; message: string } } }
       interface PreviewApi {
         skills: { list(payload: { sessionId: string }): Promise<Result<{ skills: unknown[] }>> }
-        settings: {
-          describe(payload: object): Promise<Result<{ namespaces: Array<{ ns: string; revision: number }> }>>
-          update(payload: { ns: string; patch: object; expectedRevision: number }): Promise<Result<unknown>>
-        }
-        credentials: {
-          set(payload: { ref: string; value: string }): Promise<Result<unknown>>
-          unset(payload: { ref: string }): Promise<Result<unknown>>
-          describe(payload: { refs: string[] }): Promise<Result<{
-            credentials: Record<string, { configured: boolean }>
-          }>>
-        }
       }
       interface PreviewTransport {
         fetch(input: string, init: RequestInit): Promise<Response>
@@ -373,22 +362,44 @@ async function bootPreview(origin: string, browser: Browser): Promise<void> {
         if (!refreshed.result.ok) throw new Error(`skill.list refresh failed: ${refreshed.result.error.message}`)
       }
       await createDirectory('/dsh/workspace/.agents/skills', 'runtime-created')
-      const settings = await api.settings.describe({})
-      if (!settings.result.ok) throw new Error(`settings.describe failed: ${settings.result.error.message}`)
-      const shell = settings.result.value.namespaces.find(namespace => namespace.ns === 'shell')
-      if (shell === undefined) throw new Error('settings.describe omitted the shell namespace')
-      const updated = await api.settings.update({ ns: 'shell', patch: { timeoutMs: 61_000 }, expectedRevision: shell.revision })
-      if (!updated.result.ok) throw new Error(`settings.update failed: ${updated.result.error.message}`)
-      const stored = await api.credentials.set({ ref: 'PREVIEW_TEST_SECRET', value: 'worker-only' })
-      if (!stored.result.ok) throw new Error(`credentials.set failed: ${stored.result.error.message}`)
-      const credentials = await api.credentials.describe({ refs: ['PREVIEW_TEST_SECRET'] })
-      if (!credentials.result.ok) throw new Error(`credentials.describe failed: ${credentials.result.error.message}`)
-      const removed = await api.credentials.unset({ ref: 'PREVIEW_TEST_SECRET' })
-      if (!removed.result.ok) throw new Error(`credentials.unset failed: ${removed.result.error.message}`)
+      // Settings and credentials both answer over the Remote carrier, so this
+      // half of the sweep posts the generated endpoints directly like the
+      // session read above.
+      const remote = async <T>(endpoint: string, args: object): Promise<T> => {
+        const answered = await transport.fetch(`/api/${endpoint}`, {
+          method: 'POST',
+          headers: { 'content-type': 'application/json' },
+          body: JSON.stringify({
+            type: 'client-request',
+            rpcId: `preview-${endpoint.replace('/', '-')}`,
+            method: endpoint,
+            payload: { args },
+          }),
+        })
+        const body = await answered.json() as Result<T>
+        if (!body.result.ok) throw new Error(`${endpoint} failed: ${body.result.error.message}`)
+        return body.result.value
+      }
+      const settings = await remote<{ namespaces: { ns: string; revision: number }[] }>(
+        'settings/describe', {},
+      )
+      const shell = settings.namespaces.find(namespace => namespace.ns === 'shell')
+      if (shell === undefined) throw new Error('settings/describe omitted the shell namespace')
+      await remote('settings/update', {
+        ns: 'shell',
+        patch: { timeoutMs: 61_000 },
+        expectedRevision: shell.revision,
+      })
+      await remote('credentials/set', { ref: 'PREVIEW_TEST_SECRET', value: 'worker-only' })
+      const credentials = await remote<Record<string, { configured: boolean }>>(
+        'credentials/describe',
+        { refs: ['PREVIEW_TEST_SECRET'] },
+      )
+      await remote('credentials/unset', { ref: 'PREVIEW_TEST_SECRET' })
       await new Promise((resolve) => { setTimeout(resolve, 250) })
       return {
         skillCount: skills.result.value.skills.length,
-        credentialConfigured: credentials.result.value.credentials.PREVIEW_TEST_SECRET?.configured,
+        credentialConfigured: credentials.PREVIEW_TEST_SECRET?.configured,
       }
     })
     expect(exercised.skillCount).toBeGreaterThan(0)

+ 2 - 2
apps/web/tests/startup-rpc-budget.e2e.ts

@@ -1,4 +1,4 @@
-// Cold boot may issue at most two settings.describe calls regardless of client
+// Cold boot may issue at most two settings/describe calls regardless of client
 // plugin count. No model call or replay fixture is involved.
 import type { Browser, Page } from 'playwright'
 import { chromium } from 'playwright'
@@ -38,7 +38,7 @@ describe('startup RPC budget', () => {
     // absorbs the first-connection reset wave the budget must include.
     await page.getByRole('textbox', { name: 'Choose workspace' }).waitFor({ timeout: 30_000 })
     await page.waitForTimeout(3000)
-    const describeCount = calls.filter(method => method === 'settings.describe').length
+    const describeCount = calls.filter(method => method === 'settings/describe').length
     expect(describeCount, `startup /api calls:\n${calls.join('\n')}`).toBe(DESCRIBE_BUDGET)
   })
 })

+ 2 - 0
packages/api/remotes/package.json

@@ -63,6 +63,7 @@
     "@deepseek-ai/dsh-agent-presets": "workspace:^",
     "@deepseek-ai/dsh-api-gateway": "workspace:^",
     "@deepseek-ai/dsh-api-session-controller": "workspace:^",
+    "@deepseek-ai/dsh-api-settings-controller": "workspace:^",
     "@deepseek-ai/dsh-api-workspace-controller": "workspace:^",
     "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-cordis-host-runner": "workspace:^",
@@ -85,6 +86,7 @@
     "@deepseek-ai/dsh-agent-presets": "workspace:^",
     "@deepseek-ai/dsh-api-gateway": "workspace:^",
     "@deepseek-ai/dsh-api-session-controller": "workspace:^",
+    "@deepseek-ai/dsh-api-settings-controller": "workspace:^",
     "@deepseek-ai/dsh-api-workspace-controller": "workspace:^",
     "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-cordis-host-runner": "workspace:^",

+ 15 - 3
packages/api/remotes/src/client/index.ts

@@ -3,6 +3,7 @@
 import type { Context } from '@deepseek-ai/cordis'
 import agentPresetsRemote from '@deepseek-ai/dsh-agent-presets/remote'
 import commandsRemote from '@deepseek-ai/dsh-commands/remote'
+import settingsControllerRemote from '@deepseek-ai/dsh-api-settings-controller/remote'
 import goalsRemote from '@deepseek-ai/dsh-goal/remote'
 import dynamicRemote from '@deepseek-ai/dsh-cordis-host-runner/remote'
 import fileReferencesRemote from '@deepseek-ai/dsh-file-reference/remote'
@@ -18,6 +19,7 @@ export type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client'
 export type { PluginInventorySnapshot } from '@deepseek-ai/dsh-host-plugin-inventory/types'
 export type {} from '@deepseek-ai/dsh-agent-presets/remote'
 export type {} from '@deepseek-ai/dsh-commands/remote'
+export type {} from '@deepseek-ai/dsh-api-settings-controller/remote'
 export type {} from '@deepseek-ai/dsh-file-reference/remote'
 export type {} from '@deepseek-ai/dsh-goal/remote'
 export type {} from '@deepseek-ai/dsh-host-plugin-inventory/remote'
@@ -53,10 +55,10 @@ export type {} from '@deepseek-ai/dsh-api-session-controller/types'
  */
 export type {
   ConfigurableProviderView, ConnectionHandle, ConnectionSinks, ContentBlock,
-  CredentialView, DiscoveredModelView, IApiClient,
+  DiscoveredModelView, IApiClient,
   MessageId, ModelCatalog, ModelCatalogFailure, ModelProviderGroup, ModelReasoningEffort, ModelSelection,
   RpcError, RpcId, RpcRequest, RpcResponse, RpcResult, SessionId,
-  SettingsNamespaceView, SettingsPathOpView, SkillEntry, StreamChunk,
+  SkillEntry, StreamChunk,
 } from '@deepseek-ai/dsh-client-connection/client'
 export type {} from '@deepseek-ai/dsh-api-gateway/client'
 export type {} from '@deepseek-ai/dsh-cordis-host-runner/remote'
@@ -102,6 +104,13 @@ export type {
 // reason: a Client contribution names what it sends without importing a Host
 // package, and this assembly is where both planes legitimately meet.
 export type { JsonValue } from '@deepseek-ai/dsh-session/types'
+// Credential state vocabulary for the credentials namespace (values never ride it).
+export type { CredentialInfo } from '@deepseek-ai/dsh-credentials/types'
+// Redacted namespace vocabulary for the settings namespace (secrets never ride
+// it). It travels with its seam, whose `./types` the Client face already reads.
+export type {
+  SettingsDescribeValue, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
+} from '@deepseek-ai/dsh-settings/types'
 // Reference-discovery result vocabulary for the fileReferences and
 // sessionReferenceResolver namespaces.
 export type { FileReferenceCandidate } from '@deepseek-ai/dsh-file-reference/types'
@@ -112,6 +121,8 @@ export type ClientFailure =
   | import('@deepseek-ai/dsh-client-connection/client').RpcError
   | import('@deepseek-ai/dsh-agent-presets/types').AgentPresetError
   | import('@deepseek-ai/dsh-api-session-controller/types').SessionError
+  | import('@deepseek-ai/dsh-api-settings-controller/types').CredentialError
+  | import('@deepseek-ai/dsh-api-settings-controller/types').SettingsError
   | import('@deepseek-ai/dsh-subagent/client').SubagentControlError
   | import('@deepseek-ai/dsh-api-workspace-controller/types').WorkspaceError
 
@@ -139,7 +150,8 @@ export async function apply(ctx: Context): Promise<() => Promise<void>> {
   const disposers: Array<() => Promise<void>> = []
   try {
     for (const contribution of [
-      agentPresetsRemote, commandsRemote, goalsRemote, dynamicRemote, fileReferencesRemote,
+      agentPresetsRemote, commandsRemote, settingsControllerRemote, goalsRemote, dynamicRemote,
+      fileReferencesRemote,
       pluginInventoryRemote, messageFeedbackRemote, sessionReferencesRemote,
       subagentsRemote, sessionRemote, workspaceRemote,
     ]) {

+ 3 - 0
packages/api/remotes/tsconfig.client.json

@@ -66,6 +66,9 @@
     {
       "path": "../session-controller/tsconfig.client.json"
     },
+    {
+      "path": "../settings-controller"
+    },
     {
       "path": "../workspace-controller/tsconfig.client.json"
     },

+ 0 - 10
packages/api/session-controller/tests/fake-api.client.ts

@@ -234,17 +234,7 @@ export class FakeApiClient implements IApiClient {
   }
 
   readonly settings: IApiClient['settings'] = {
-    describe: payload => this.record('settings.describe', payload, Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [] }))),
     openDocument: payload => this.record('settings.openDocument', payload, Promise.resolve(ok({ opened: true as const }))),
-    update: payload => this.record('settings.update', payload, Promise.resolve(ok({ ns: 'fake', schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0 }))),
-    replace: payload => this.record('settings.replace', payload, Promise.resolve(ok({ ns: 'fake', schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0 }))),
-    mutate: payload => this.record('settings.mutate', payload, Promise.resolve(ok({ ns: 'fake', schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0 }))),
-  }
-
-  readonly credentials: IApiClient['credentials'] = {
-    describe: payload => this.record('credentials.describe', payload, Promise.resolve(ok({ credentials: {} }))),
-    set: payload => this.record('credentials.set', payload, Promise.resolve(ok({}))),
-    unset: payload => this.record('credentials.unset', payload, Promise.resolve(ok({}))),
   }
 
   readonly llm: IApiClient['llm'] = {

+ 8 - 6
packages/client/connection/package.json

@@ -47,29 +47,31 @@
     "lib/types/**/*.d.ts"
   ],
   "peerDependencies": {
-    "@deepseek-ai/dsh-host-webserver": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-attachment": "workspace:^",
+    "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-credentials": "workspace:^",
     "@deepseek-ai/dsh-host-apiproxy": "workspace:^",
     "@deepseek-ai/dsh-host-directory-picker": "workspace:^",
-    "@deepseek-ai/dsh-commands": "workspace:^",
+    "@deepseek-ai/dsh-host-webserver": "workspace:^",
+    "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/dsh-llm": "workspace:^",
     "@deepseek-ai/dsh-session": "workspace:^",
+    "@deepseek-ai/dsh-settings": "workspace:^",
     "@deepseek-ai/dsh-tool-todo": "workspace:^"
   },
   "devDependencies": {
-    "@deepseek-ai/dsh-host-webserver": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-attachment": "workspace:^",
+    "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-credentials": "workspace:^",
     "@deepseek-ai/dsh-host-apiproxy": "workspace:^",
     "@deepseek-ai/dsh-host-directory-picker": "workspace:^",
-    "@deepseek-ai/dsh-commands": "workspace:^",
+    "@deepseek-ai/dsh-host-webserver": "workspace:^",
+    "@deepseek-ai/dsh-invariants": "workspace:^",
     "@deepseek-ai/dsh-llm": "workspace:^",
     "@deepseek-ai/dsh-session": "workspace:^",
+    "@deepseek-ai/dsh-settings": "workspace:^",
     "@deepseek-ai/dsh-tool-todo": "workspace:^"
   }
 }

+ 2 - 2
packages/client/connection/src/client/api.ts

@@ -11,8 +11,8 @@ export type {
   SkillsApi, SkillEntry,
   ModelCatalog, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
   ModelReasoningEffort, ModelSelection,
-  SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
-  CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi,
+  SettingsApi,
+  ConfigurableProviderView, DiscoveredModelView, LlmApi,
 } from '@deepseek-ai/dsh-host-apiproxy/api'
 export type {
   RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,

+ 91 - 55
packages/client/connection/src/client/fixture.ts

@@ -29,7 +29,9 @@ import type { TodoItem } from '@deepseek-ai/dsh-tool-todo/client'
 // wire-fabrication boundary (the schema layer's one-cast-point posture).
 import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
 import type { CommandDescriptor, CommandExecution, CommandResult } from '@deepseek-ai/dsh-commands/types'
+import type { CredentialInfo } from '@deepseek-ai/dsh-credentials/types'
 import type { DirectoryListing as FixtureDirectoryListing } from '@deepseek-ai/dsh-host-directory-picker/types'
+import type { SettingsDescribeValue, SettingsNamespaceView } from '@deepseek-ai/dsh-settings/types'
 import { deriveEventMessage, foldSurface } from '@deepseek-ai/dsh-session/surface'
 import type {
   ApiProxy, ClientRequest,
@@ -1778,6 +1780,83 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
     // DeepSeek route so unrelated GUI journeys do not enter first-run setup.
     ['DEEPSEEK_API_KEY', true],
   ])
+
+  /** Canonical fixture implementation of the generated Settings Remote contract. */
+  const settingsRemotes = {
+    // Only the resolved DeepSeek address needed by first-run readiness is
+    // represented here. Fixture-backed journeys do not open its Models editor;
+    // real schema-driven forms ride the HTTP transport.
+    describe(): RpcResult<SettingsDescribeValue> {
+      return {
+        ok: true,
+        value: {
+          writable: true,
+          hasDocument: true,
+          namespaces: [{
+            ns: 'llm-deepseek',
+            schema: {},
+            value: { apiKeyEnv: 'DEEPSEEK_API_KEY' },
+            applies: 'live',
+            secrets: [{ path: ['apiKey'], set: false }],
+            revision: 0,
+          }],
+        },
+      }
+    },
+    update(ns: string): ConnectionRpcResult<SettingsNamespaceView> {
+      return {
+        ok: false,
+        error: {
+          code: 'settings-rejected',
+          message: 'fixture: the minimal readiness settings descriptor is read-only',
+          details: { ns },
+        },
+      }
+    },
+    replace(ns: string): ConnectionRpcResult<SettingsNamespaceView> {
+      return {
+        ok: false,
+        error: {
+          code: 'settings-rejected',
+          message: 'fixture: the minimal readiness settings descriptor is read-only',
+          details: { ns },
+        },
+      }
+    },
+    mutate(ns: string): ConnectionRpcResult<SettingsNamespaceView> {
+      // A Remote failure code is free-form, unlike the unary error vocabulary.
+      return {
+        ok: false,
+        error: {
+          code: 'settings-rejected',
+          message: 'fixture: no settings namespaces are registered',
+          details: { ns },
+        },
+      }
+    },
+  }
+
+  const credentialRemotes = {
+    describe(refs: readonly string[]): RpcResult<Record<string, CredentialInfo>> {
+      return {
+        ok: true,
+        value: Object.fromEntries(refs.map(ref => [ref, {
+          configured: fixtureCredentials.has(ref),
+          ...fixtureCredentials.has(ref) ? { source: 'file' } : {},
+          writable: true,
+        }])),
+      }
+    },
+    set(ref: string): RpcResult<void> {
+      fixtureCredentials.set(ref, true)
+      return { ok: true, value: undefined }
+    },
+    unset(ref: string): RpcResult<void> {
+      fixtureCredentials.delete(ref)
+      return { ok: true, value: undefined }
+    },
+  }
+
   /**
    * Preset compositions the fixture serves. Held as state rather than
    * constants so the settings editor's save and delete are exercisable: the
@@ -3347,55 +3426,8 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
       },
     },
     settings: {
-      // Only the resolved DeepSeek address needed by first-run readiness is
-      // represented here. Fixture-backed journeys do not open its Models
-      // editor; real schema-driven forms ride the HTTP transport.
-      describe: request => ok(request, {
-        writable: true,
-        hasDocument: true,
-        namespaces: [{
-          ns: 'llm-deepseek',
-          schema: {},
-          value: { apiKeyEnv: 'DEEPSEEK_API_KEY' },
-          applies: 'live',
-          secrets: [{ path: ['apiKey'], set: false }],
-          revision: 0,
-        }],
-      }),
       // Native opens are deterministic no-op successes in this fixture, as is host.openPath.
       openDocument: request => ok(request, { opened: true as const }),
-      update: request => err(request, {
-        code: 'settings-rejected',
-        message: 'fixture: the minimal readiness settings descriptor is read-only',
-        details: { ns: request.payload.ns },
-      }),
-      replace: request => err(request, {
-        code: 'settings-rejected',
-        message: 'fixture: the minimal readiness settings descriptor is read-only',
-        details: { ns: request.payload.ns },
-      }),
-      mutate: request => err(request, {
-        code: 'settings-rejected',
-        message: 'fixture: no settings namespaces are registered',
-        details: { ns: request.payload.ns },
-      }),
-    },
-    credentials: {
-      describe: request => ok(request, {
-        credentials: Object.fromEntries(request.payload.refs.map(ref => [ref, {
-          configured: fixtureCredentials.has(ref),
-          ...fixtureCredentials.has(ref) ? { source: 'file' } : {},
-          writable: true,
-        }])),
-      }),
-      set: (request) => {
-        fixtureCredentials.set(request.payload.ref, true)
-        return ok(request, {})
-      },
-      unset: (request) => {
-        fixtureCredentials.delete(request.payload.ref)
-        return ok(request, {})
-      },
     },
     llm: {
       providers: request => ok(request, {
@@ -3442,7 +3474,11 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
           path?: string
           name?: string
           images?: readonly unknown[]
-          ref?: { id: string; revision: number }
+          // A goal ref and a credential reference name share this wire field name.
+          ref?: string | { id: string; revision: number }
+          refs?: readonly string[]
+          value?: string
+          ns?: string
           agentPreset?: string
           from?: string
           id?: string
@@ -3493,6 +3529,13 @@ function createFixtureWorld(options: FixtureOptions): FixtureWorld {
           },
         })
         case 'subagents/interruptByParent': return Promise.resolve({ ok: true, value: { accepted: true } })
+        case 'credentials/describe': return Promise.resolve(credentialRemotes.describe(args.refs ?? []))
+        case 'credentials/set': return Promise.resolve(credentialRemotes.set(args.ref as string))
+        case 'credentials/unset': return Promise.resolve(credentialRemotes.unset(args.ref as string))
+        case 'settings/describe': return Promise.resolve(settingsRemotes.describe())
+        case 'settings/update': return Promise.resolve(settingsRemotes.update(args.ns as string))
+        case 'settings/replace': return Promise.resolve(settingsRemotes.replace(args.ns as string))
+        case 'settings/mutate': return Promise.resolve(settingsRemotes.mutate(args.ns as string))
         case 'session/list': return sessionApi.list(
           args._request as Parameters<FixtureSessionApi['list']>[0],
         )
@@ -3619,14 +3662,7 @@ export class FixtureApiClient extends AbstractApiClient {
       case 'host.openPath': return this.api.host.openPath(request, new AbortController().signal)
       case 'skill.list': return this.api.skills.list(request)
       case 'agentPreset.openDocument': return this.api.agentPresets.openDocument(request, new AbortController().signal)
-      case 'settings.describe': return this.api.settings.describe(request)
       case 'settings.openDocument': return this.api.settings.openDocument(request, signal)
-      case 'settings.update': return this.api.settings.update(request)
-      case 'settings.replace': return this.api.settings.replace(request)
-      case 'settings.mutate': return this.api.settings.mutate(request)
-      case 'credentials.describe': return this.api.credentials.describe(request)
-      case 'credentials.set': return this.api.credentials.set(request)
-      case 'credentials.unset': return this.api.credentials.unset(request)
       case 'llm.providers': return this.api.llm.providers(request)
       case 'llm.models': return this.api.llm.models(request)
       case 'llm.discoverModels': return this.api.llm.discoverModels(request, signal)

+ 2 - 2
packages/client/connection/src/client/index.ts

@@ -37,8 +37,8 @@ export type {
   RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
   ClientRequest, ServerResponse, RpcMessage,
   HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
-  SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView,
-  CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi,
+  SettingsApi,
+  ConfigurableProviderView, DiscoveredModelView, LlmApi,
 } from './api.ts'
 export {
   RpcId,

+ 0 - 10
packages/client/connection/tests/fake-api.client.ts

@@ -76,17 +76,7 @@ export class FakeApiClient implements IApiClient {
   }
 
   readonly settings: IApiClient['settings'] = {
-    describe: payload => this.record('settings.describe', payload, Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [] }))),
     openDocument: payload => this.record('settings.openDocument', payload, Promise.resolve(ok({ opened: true as const }))),
-    update: payload => this.record('settings.update', payload, Promise.resolve(ok({ ns: 'fake', schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0 }))),
-    replace: payload => this.record('settings.replace', payload, Promise.resolve(ok({ ns: 'fake', schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0 }))),
-    mutate: payload => this.record('settings.mutate', payload, Promise.resolve(ok({ ns: 'fake', schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0 }))),
-  }
-
-  readonly credentials: IApiClient['credentials'] = {
-    describe: payload => this.record('credentials.describe', payload, Promise.resolve(ok({ credentials: {} }))),
-    set: payload => this.record('credentials.set', payload, Promise.resolve(ok({}))),
-    unset: payload => this.record('credentials.unset', payload, Promise.resolve(ok({}))),
   }
 
   readonly llm: IApiClient['llm'] = {

+ 60 - 14
packages/client/connection/tests/fixture.client.spec.ts

@@ -297,6 +297,8 @@ type FixtureTestApi = ReturnType<typeof createFixtureFaces>['api'] & {
   readonly sessionRemote: FixtureSessionRemote
   readonly workspace: FixtureWorkspaceApi
   readonly workspaceRemote: FixtureWorkspaceRemote
+  readonly credentialRemote: FixtureCredentialRemote
+  readonly settingsRemote: FixtureSettingsRemote
   readonly remoteEvents: (signal: AbortSignal) => FixtureRemoteEventStream
   readonly answerRemoteEvent: (result: FixtureRemoteEventResult) => Promise<unknown>
 }
@@ -318,12 +320,48 @@ function createFixtureApi(options: FixtureOptions = {}): FixtureTestApi {
     sessionRemote: createSessionRemote(rpc),
     workspace: createWorkspaceApi(rpc),
     workspaceRemote: createWorkspaceRemote(rpc),
+    credentialRemote: createCredentialRemote(rpc),
+    settingsRemote: createSettingsRemote(rpc),
     remoteEvents: (signal: AbortSignal) => openFixtureRemoteEvents(rpc, signal),
     answerRemoteEvent: (result: FixtureRemoteEventResult) =>
       rpc.call('/api', '$events/result', { args: result }),
   })
 }
 
+/** The fixture's Credentials Remote endpoints over the shared RPC carrier. */
+interface FixtureCredentialRemote {
+  describe(refs: readonly string[]): Promise<ConnectionRpcResult<unknown>>
+  set(ref: string, value: string): Promise<ConnectionRpcResult<unknown>>
+  unset(ref: string): Promise<ConnectionRpcResult<unknown>>
+}
+
+/** The settings Remote reads the fixture serves, addressed like the credential half. */
+interface FixtureSettingsRemote {
+  describe(): Promise<ConnectionRpcResult<unknown>>
+  update(ns: string, patch: unknown, expectedRevision?: number): Promise<ConnectionRpcResult<unknown>>
+  replace(ns: string, section: unknown, expectedRevision?: number): Promise<ConnectionRpcResult<unknown>>
+}
+
+function createSettingsRemote(rpc: ClientConnectionRpc): FixtureSettingsRemote {
+  return {
+    describe: () => rpc.call('/api', 'settings/describe', { args: {} }),
+    update: (ns, patch, expectedRevision) => rpc.call('/api', 'settings/update', {
+      args: { ns, patch, expectedRevision },
+    }),
+    replace: (ns, section, expectedRevision) => rpc.call('/api', 'settings/replace', {
+      args: { ns, section, expectedRevision },
+    }),
+  }
+}
+
+function createCredentialRemote(rpc: ClientConnectionRpc): FixtureCredentialRemote {
+  return {
+    describe: refs => rpc.call('/api', 'credentials/describe', { args: { refs } }),
+    set: (ref, value) => rpc.call('/api', 'credentials/set', { args: { ref, value } }),
+    unset: ref => rpc.call('/api', 'credentials/unset', { args: { ref } }),
+  }
+}
+
 function openFixtureRemoteEvents(
   rpc: ClientConnectionRpc,
   signal: AbortSignal,
@@ -724,32 +762,40 @@ describe('createFixtureApi', () => {
 
   it('serves configured DeepSeek readiness and keeps credential values write-only', async () => {
     const api = createFixtureApi()
-    const settings = await api.settings.describe(req({}))
-    if (!settings.result.ok) throw new Error('settings describe failed')
-    expect(settings.result.value.namespaces).toMatchObject([{
+    const settings = await api.settingsRemote.describe()
+    if (!settings.ok) throw new Error('settings describe failed')
+    expect((settings.value as { namespaces: unknown[] }).namespaces).toMatchObject([{
       ns: 'llm-deepseek',
       value: { apiKeyEnv: 'DEEPSEEK_API_KEY' },
       secrets: [{ path: ['apiKey'], set: false }],
     }])
+    for (const result of [
+      await api.settingsRemote.update('llm-deepseek', {}, undefined),
+      await api.settingsRemote.replace('llm-deepseek', {}, undefined),
+    ]) {
+      expect(result).toMatchObject({
+        ok: false,
+        error: { code: 'settings-rejected', message: 'fixture: the minimal readiness settings descriptor is read-only' },
+      })
+    }
 
-    const initial = await api.credentials.describe(req({ refs: ['DEEPSEEK_API_KEY', 'TEST_API_KEY'] }))
-    if (!initial.result.ok) throw new Error('credential describe failed')
-    expect(initial.result.value.credentials).toEqual({
+    const describe = async (refs: readonly string[]): Promise<Record<string, unknown>> => {
+      const result = await api.credentialRemote.describe(refs)
+      if (!result.ok) throw new Error('credential describe failed')
+      return result.value as Record<string, unknown>
+    }
+    expect(await describe(['DEEPSEEK_API_KEY', 'TEST_API_KEY'])).toEqual({
       DEEPSEEK_API_KEY: { configured: true, source: 'file', writable: true },
       TEST_API_KEY: { configured: false, writable: true },
     })
-    await api.credentials.set(req({ ref: 'TEST_API_KEY', value: 'write-only-fixture-secret' }))
-    const configured = await api.credentials.describe(req({ refs: ['TEST_API_KEY'] }))
-    if (!configured.result.ok) throw new Error('credential describe failed')
-    expect(configured.result.value.credentials.TEST_API_KEY).toEqual({
+    await api.credentialRemote.set('TEST_API_KEY', 'write-only-fixture-secret')
+    expect((await describe(['TEST_API_KEY'])).TEST_API_KEY).toEqual({
       configured: true,
       source: 'file',
       writable: true,
     })
-    await api.credentials.unset(req({ ref: 'TEST_API_KEY' }))
-    const cleared = await api.credentials.describe(req({ refs: ['TEST_API_KEY'] }))
-    if (!cleared.result.ok) throw new Error('credential describe failed')
-    expect(cleared.result.value.credentials.TEST_API_KEY).toEqual({ configured: false, writable: true })
+    await api.credentialRemote.unset('TEST_API_KEY')
+    expect((await describe(['TEST_API_KEY'])).TEST_API_KEY).toEqual({ configured: false, writable: true })
   })
 
   it('emits the todo/write snapshot at the real tool boundary: between tool/call and tool/result, timestamps monotonic', async () => {

+ 3 - 5
packages/client/connection/tests/node-half.host.spec.ts

@@ -175,7 +175,6 @@ describe('connection node half', () => {
     const { routes, connection, dispose } = await mounted({ trustedHosts: ['harness.example'] })
     const methods = [
       'host.openPath',
-      'settings.describe', 'settings.update', 'credentials.describe', 'credentials.set',
       'llm.discoverModels', 'llm.models', 'agentPreset.openDocument',
     ]
     for (const method of methods) {
@@ -501,8 +500,7 @@ describe('connection node half over a real HTTP server', () => {
     const { port, close } = await serve(routes)
     try {
       const methods = [
-        'settings.describe', 'settings.openDocument', 'settings.update', 'settings.replace', 'settings.mutate',
-        'credentials.describe', 'credentials.set', 'credentials.unset',
+        'settings.openDocument',
         'host.openPath',
         'llm.discoverModels',
         'agentPreset.openDocument',
@@ -512,7 +510,7 @@ describe('connection node half over a real HTTP server', () => {
         expect([method, await call(port, method, 'localhost')]).toEqual([method, 401])
         expect([method, await call(port, method, 'harness.example')]).toEqual([method, 401])
       }
-      expect(await call(port, 'settings.describe', 'other.example')).toBe(403)
+      expect(await call(port, 'settings.openDocument', 'other.example')).toBe(403)
 
       const declaredCookie = browserCookie(connection, 'harness.example')
       for (const method of methods) {
@@ -521,7 +519,7 @@ describe('connection node half over a real HTTP server', () => {
       const loopbackAuthority = `127.0.0.1:${String(port)}`
       expect(await call(
         port,
-        'settings.describe',
+        'settings.openDocument',
         loopbackAuthority,
         browserCookie(connection, loopbackAuthority),
       )).toBe(404)

+ 6 - 0
packages/client/connection/tsconfig.client.json

@@ -33,6 +33,12 @@
     {
       "path": "../../core/tools"
     },
+    {
+      "path": "../../credentials/credentials"
+    },
+    {
+      "path": "../../settings/settings"
+    },
     {
       "path": "../../host/apiproxy"
     },

+ 7 - 13
packages/client/locale/tests/apply.client.spec.ts

@@ -30,22 +30,16 @@ async function bench() {
     revision,
   })
   const describe = vi.fn(async () => ({
-    rpcId: 'locale-describe' as never,
-    result: {
-      ok: true as const,
-      value: { writable: true, hasDocument: true, namespaces: [namespace()] },
-    },
+    ok: true as const,
+    value: { writable: true, hasDocument: true, namespaces: [namespace()] },
   }))
-  const mutate = vi.fn(async (request: { ops: { value: string }[] }) => {
-    preference = request.ops[0]!.value
+  const mutate = vi.fn(async (_ns: string, ops: { value: string }[]) => {
+    preference = ops[0]!.value
     revision += 1
-    return {
-      rpcId: 'locale-mutate' as never,
-      result: { ok: true as const, value: namespace() },
-    }
+    return { ok: true as const, value: namespace() }
   })
-  ctx.provide('connection', { api: { settings: { describe, mutate } }, isLoopback: true } as never)
-  const events = new TestRemote(ctx)
+  ctx.provide('connection', { api: {}, isLoopback: true } as never)
+  const events = new TestRemote(ctx, { settings: { describe, mutate } })
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   return {
     ctx, slots: ctx.get('slots') as SlotRegistry, describe, mutate, events,

+ 7 - 7
packages/client/locale/tests/document-language.client.spec.ts

@@ -32,17 +32,17 @@ async function bench(preference?: string) {
     revision,
   })
   const describeRpc = vi.fn(async () => ({
-    rpcId: 'locale-describe' as never,
-    result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [namespace()] } },
+    ok: true as const,
+    value: { writable: true, hasDocument: true, namespaces: [namespace()] },
   }))
-  const mutate = vi.fn(async (request: { ops: { value: string }[] }) => {
-    stored = request.ops[0]!.value
+  const mutate = vi.fn(async (_ns: string, ops: { value: string }[]) => {
+    stored = ops[0]!.value
     revision += 1
-    return { rpcId: 'locale-mutate' as never, result: { ok: true as const, value: namespace() } }
+    return { ok: true as const, value: namespace() }
   })
-  ctx.provide('connection', { api: { settings: { describe: describeRpc, mutate } }, isLoopback: true } as never)
+  ctx.provide('connection', { api: {}, isLoopback: true } as never)
   // The settings transport and the forwarded-event port the plugin injects.
-  new TestRemote(ctx)
+  new TestRemote(ctx, { settings: { describe: describeRpc, mutate } })
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   await ctx.plugin({ inject: [...inject], apply }).await()
   return { ctx, locale: ctx.get('locale') as LocaleRuntime }

+ 6 - 3
packages/client/ui-agent-preset/src/client/index.ts

@@ -50,7 +50,9 @@ export type { AgentPresetOption, AgentPresetSettingsState } from './settings-sto
 export { AGENT_PRESET_SETTINGS_NS, writeDefaultPreset } from './settings-store.ts'
 
 /** Required services (cordis fiber inject). */
-export const inject = ['slots', 'locale', 'connection', 'remote', 'remote.agentPresets', 'settingsScope']
+export const inject = [
+  'slots', 'locale', 'connection', 'remote', 'remote.agentPresets', 'remote.settings', 'settingsScope',
+]
 
 /**
  * Mount the General-settings row.
@@ -58,11 +60,12 @@ export const inject = ['slots', 'locale', 'connection', 'remote', 'remote.agentP
  */
 export function apply(ctx: ClientContext): void {
   const { api } = ctx.get('connection') as ConnectionHandle
-  const controller = new AgentPresetSettingsController(api, ctx.remote, ctx.settingsScope.describe())
+  const settingsWire = { settings: ctx.remote.settings }
+  const controller = new AgentPresetSettingsController(settingsWire, ctx.remote, ctx.settingsScope.describe())
   // One roster, four surfaces. The chip is registered in a later scope, so it
   // subscribes here rather than being reached from this one.
   const rosterReaders = new Set<() => void>()
-  const section = new AgentPresetSectionController(api, ctx.remote, () => {
+  const section = new AgentPresetSectionController({ ...api, ...settingsWire }, ctx.remote, () => {
     void controller.load()
     for (const read of rosterReaders) read()
   })

+ 2 - 1
packages/client/ui-agent-preset/src/client/section-store.ts

@@ -15,6 +15,7 @@
  */
 
 import type { ClientRemote, IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsWireFace } from '@deepseek-ai/dsh-client-ui-settings/client'
 import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
 import { beginRosterRead, messageOf, writeDefaultPreset } from './settings-store.ts'
 
@@ -133,7 +134,7 @@ export class AgentPresetSectionController {
   readonly store: SnapshotStore<AgentPresetSectionState> = createSnapshotStore(INITIAL)
 
   constructor(
-    private readonly api: Pick<IApiClient, 'agentPresets' | 'settings' | 'host'>,
+    private readonly api: SettingsWireFace & Pick<IApiClient, 'agentPresets' | 'host'>,
     private readonly remote: Pick<ClientRemote, 'agentPresets'>,
     /**
      * Called after this page changes the roster DIRECTORY, so the other

+ 10 - 6
packages/client/ui-agent-preset/src/client/settings-store.ts

@@ -7,10 +7,10 @@
  * namespace's `default` field, which is what the host resolves at creation.
  */
 
-import type { ClientRemote, IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
+import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client'
 import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
 import type { AgentPresetRoster } from '@deepseek-ai/dsh-agent-presets/types'
-import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client'
+import type { SettingsDescribeFace, SettingsWireFace } from '@deepseek-ai/dsh-client-ui-settings/client'
 
 /** The agent-preset settings namespace on the host wire. */
 export const AGENT_PRESET_SETTINGS_NS = 'agent-presets'
@@ -37,18 +37,22 @@ export function messageOf(error: unknown): string {
  * @returns the failure message, or undefined once the write landed.
  */
 export async function writeDefaultPreset(
-  api: Pick<IApiClient, 'settings'>,
+  api: SettingsWireFace,
   id: string,
 ): Promise<string | undefined> {
   let response
   try {
-    response = await api.settings.update({ ns: AGENT_PRESET_SETTINGS_NS, patch: { default: id } })
+    response = await api.settings.update(
+      AGENT_PRESET_SETTINGS_NS,
+      { default: id },
+      undefined,
+    )
   } catch (error) {
     // The transport rejected rather than answering; the caller must be able to
     // say so instead of the row silently snapping back.
     return messageOf(error)
   }
-  return response.result.ok ? undefined : response.result.error.message
+  return response.ok ? undefined : response.error.message
 }
 
 /** One selectable preset. */
@@ -181,7 +185,7 @@ export class AgentPresetSettingsController {
    * @param describeFace - the shared mirror's describe face (writability source).
    */
   constructor(
-    private readonly api: Pick<IApiClient, 'settings'>,
+    private readonly api: SettingsWireFace,
     private readonly remote: Pick<ClientRemote, 'agentPresets'>,
     private readonly describeFace: SettingsDescribeFace,
   ) {}

+ 16 - 10
packages/client/ui-agent-preset/tests/apply.client.spec.ts

@@ -70,8 +70,20 @@ async function bench() {
   const locale = new LocaleRuntime(ctx)
   locale.setLocale('zh')
   ctx.provide('locale', locale)
-  const remote = new TestRemote(ctx)
   const calls: string[] = []
+  // The row reads `describe` to learn whether this browser may write at all,
+  // and its default write is the one op this spec records.
+  const settings = {
+    describe: () => Promise.resolve({
+      ok: true as const,
+      value: { writable: true, hasDocument: true, namespaces: [] },
+    }),
+    update: (_ns: string, patch: unknown) => {
+      calls.push(`settings:${JSON.stringify(patch)}`)
+      return Promise.resolve({ ok: true as const, value: {} })
+    },
+  }
+  const remote = new TestRemote(ctx, { settings })
   // The roster and the switch are the AgentPresets Remote namespace; the
   // shared double carries no generated namespaces, so this spec stages its
   // own. Registered twice on purpose: the nested key satisfies the plugin's
@@ -112,14 +124,6 @@ async function bench() {
           return Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: { opened: true as const } } })
         },
       },
-      settings: {
-        // The row reads this to learn whether this browser may write at all.
-        describe: () => Promise.resolve({
-          rpcId: 'r',
-          result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
-        }),
-        update: (payload: { patch: unknown }) => { calls.push(`settings:${JSON.stringify(payload.patch)}`); return Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: {} } }) },
-      },
     },
   } as never)
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
@@ -182,7 +186,9 @@ function sessionsDouble(state: {
 
 describe('ui-agent-preset apply', () => {
   it('declares the services it uses', () => {
-    expect(inject).toEqual(['slots', 'locale', 'connection', 'remote', 'remote.agentPresets', 'settingsScope'])
+    expect(inject).toEqual([
+      'slots', 'locale', 'connection', 'remote', 'remote.agentPresets', 'remote.settings', 'settingsScope',
+    ])
   })
 
   it('registers the General row and the settings section', async () => {

+ 10 - 9
packages/client/ui-agent-preset/tests/section-store.client.spec.ts

@@ -8,6 +8,7 @@
 
 import { describe, expect, it } from 'vitest'
 import type { ClientRemote, IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsWireFace } from '@deepseek-ai/dsh-client-ui-settings/client'
 import { AgentPresetSectionController, draftBlocker } from '../src/client/section-store.ts'
 import type { CopyDraft, PresetRow } from '../src/client/section-store.ts'
 
@@ -65,7 +66,7 @@ const remoteFail = (message: string) =>
 function fakeApi(
   defaultId: { id: string },
   options: FakeOptions = {},
-): Pick<IApiClient, 'agentPresets' | 'settings' | 'host'> {
+): SettingsWireFace & Pick<IApiClient, 'agentPresets' | 'host'> {
   const record = (method: string, payload: unknown): void => { options.calls?.push({ method, payload }) }
   return {
     host: {
@@ -84,15 +85,15 @@ function fakeApi(
       },
     },
     settings: {
-      update: (payload: { ns: string; patch: { default?: string } }) => {
-        record('settings.update', payload)
-        if (options.failSettings !== undefined) return fail(options.failSettings)
-        /* v8 ignore next -- the controller only ever patches `default` */
-        defaultId.id = payload.patch.default ?? defaultId.id
-        return ok({})
+      update: (ns: string, patch: { default?: string }) => {
+        record('settings.update', { ns, patch })
+        if (options.failSettings !== undefined) return remoteFail(options.failSettings)
+        /* v8 ignore next -- the controller only ever sets `default` */
+        defaultId.id = patch.default ?? defaultId.id
+        return remoteOk({})
       },
     },
-  } as unknown as Pick<IApiClient, 'agentPresets' | 'settings' | 'host'>
+  } as unknown as SettingsWireFace & Pick<IApiClient, 'agentPresets' | 'host'>
 }
 
 /**
@@ -579,7 +580,7 @@ describe('deleting', () => {
     await controller.load()
     presets.clear()
     const broken = new AgentPresetSectionController(
-      { agentPresets: {}, settings: {}, host: {} } as unknown as Pick<IApiClient, 'agentPresets' | 'settings' | 'host'>,
+      { agentPresets: {}, settings: {}, host: {} } as unknown as SettingsWireFace & Pick<IApiClient, 'agentPresets' | 'host'>,
       {
         agentPresets: {
           list: () => Promise.reject(new Error('gone')),

+ 23 - 22
packages/client/ui-agent-preset/tests/settings-store.client.spec.ts

@@ -6,7 +6,8 @@
  */
 
 import { describe, expect, it } from 'vitest'
-import type { ClientRemote, IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
+import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsWireFace } from '@deepseek-ai/dsh-client-ui-settings/client'
 import type { SessionSummary } from '@deepseek-ai/dsh-api-session-controller/client'
 import { SettingsDescribeMirror } from '@deepseek-ai/dsh-client-ui-settings/src/client/settings-mirror.ts'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
@@ -16,7 +17,7 @@ import {
 
 /** The two faces the row reads: the roster Remote and the settings wire. */
 interface FakeWire {
-  api: IApiClient
+  api: SettingsWireFace
   remote: Pick<ClientRemote, 'agentPresets'>
 }
 
@@ -28,7 +29,7 @@ import { AgentPresetSeatController } from '../src/client/seat-store.ts'
 
 type SeatSession = Pick<SessionSummary, 'id' | 'blank' | 'projectionValues'>
 
-interface Recorded { ns: string; patch: unknown }
+interface Recorded { ns: string; ops: unknown }
 
 /** A roster Remote answering a fixed set of rows, or refusing. */
 function fakeRoster(
@@ -66,26 +67,23 @@ function fakeApi(
       // Host persistence is enabled in production only on the selected client path; a read-only provider answers writable:false
       // and the row disables its control instead of offering a refused write.
       describe: () => Promise.resolve({
-        rpcId: 'r',
-        result: {
-          ok: true as const,
-          value: { writable: options.readOnly !== true, hasDocument: true, namespaces: [] },
-        },
+        ok: true as const,
+        value: { writable: options.readOnly !== true, hasDocument: true, namespaces: [] },
       }),
-      update: (payload: { ns: string; patch: unknown }) => {
-        options.writes?.push({ ns: payload.ns, patch: payload.patch })
+      update: (ns: string, patch: { default?: unknown }) => {
+        options.writes?.push({ ns, ops: patch })
         if (options.failWriteWith !== undefined) return Promise.reject(options.failWriteWith)
         if (options.failWrite !== undefined) {
-          return Promise.resolve({ rpcId: 'r', result: { ok: false as const, error: { code: 'internal', message: options.failWrite, details: {} } } })
+          return Promise.resolve({ ok: false as const, error: { code: 'internal', message: options.failWrite, details: {} } })
         }
         // A committed write moves the roster's default.
         for (const preset of presets) {
-          preset.isDefault = preset.id === (payload.patch as { default?: string }).default
+          preset.isDefault = preset.id === patch.default
         }
-        return Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: {} } })
+        return Promise.resolve({ ok: true as const, value: {} })
       },
     },
-  } as unknown as IApiClient
+  } as unknown as SettingsWireFace
   return {
     api,
     remote: fakeRoster(presets, options.failList === undefined ? {} : { failList: options.failList }),
@@ -165,7 +163,7 @@ describe('the agent-preset settings controller', () => {
 
   it('treats an unavailable optional namespace as an empty roster', async () => {
     const controller = derivedController({
-      api: {} as IApiClient,
+      api: {} as SettingsWireFace,
       remote: fakeRoster([], {
         failList: 'no active Remote method exports this endpoint',
         failListCode: 'invocation-unavailable',
@@ -187,7 +185,10 @@ describe('the agent-preset settings controller', () => {
 
     await controller.select('minimal')
 
-    expect(writes).toEqual([{ ns: AGENT_PRESET_SETTINGS_NS, patch: { default: 'minimal' } }])
+    expect(writes).toEqual([{
+      ns: AGENT_PRESET_SETTINGS_NS,
+      ops: { default: 'minimal' },
+    }])
     expect(controller.store.getSnapshot().currentValue).toBe('minimal')
   })
 
@@ -260,7 +261,7 @@ describe('the agent-preset settings controller', () => {
 
   it('reports a transport that rejects rather than answering', async () => {
     const controller = derivedController({
-      api: {} as IApiClient,
+      api: {} as SettingsWireFace,
       remote: fakeRoster([], { throwOnList: true }),
     })
 
@@ -310,7 +311,7 @@ describe('the new-session chip controller', () => {
         },
         select: (agentId: SessionId, agentPreset: string) => {
           if (options.throwOn === 'select') return Promise.reject(new Error('socket closed'))
-          options.writes?.push({ ns: 'select', patch: agentPreset })
+          options.writes?.push({ ns: 'select', ops: agentPreset })
           return Promise.resolve(options.failSelect === undefined
             ? { ok: true as const, value: agentPreset }
             : {
@@ -434,7 +435,7 @@ describe('the new-session chip controller', () => {
     await controller.load()
     await controller.select('minimal')
 
-    expect(writes).toEqual([{ ns: 'select', patch: 'minimal' }])
+    expect(writes).toEqual([{ ns: 'select', ops: 'minimal' }])
     expect(controller.store.getSnapshot().current).toBe('minimal')
   })
 
@@ -453,7 +454,7 @@ describe('the new-session chip controller', () => {
 
     // Every later list movement calls apply(); an unspent stage would keep
     // switching sessions the user never picked for.
-    expect(writes).toEqual([{ ns: 'select', patch: 'minimal' }])
+    expect(writes).toEqual([{ ns: 'select', ops: 'minimal' }])
   })
 
   it('drops the stage against a session that already started', async () => {
@@ -535,7 +536,7 @@ describe('the new-session chip controller', () => {
     await controller.select('standard')
     await first
 
-    expect(writes).toEqual([{ ns: 'select', patch: 'minimal' }])
+    expect(writes).toEqual([{ ns: 'select', ops: 'minimal' }])
   })
 
   it('keeps a staged pick across a roster refresh', async () => {
@@ -570,7 +571,7 @@ describe('the new-session chip controller', () => {
     const controller = derivedController({
       // The roster answered; the mirror's read is what failed, so the row
       // shows the current default without offering a write it never confirmed.
-      api: { settings: { describe: () => Promise.reject(new Error('socket closed')) } } as unknown as IApiClient,
+      api: { settings: { describe: () => Promise.reject(new Error('socket closed')) } } as unknown as SettingsWireFace,
       remote: fakeRoster([{ id: 'standard', trust: 'system', isDefault: true }]),
     })
 

+ 0 - 3
packages/client/ui-permission-presets/package.json

@@ -33,7 +33,6 @@
     "client": {
       "inject": [
         "@deepseek-ai/dsh-api-session-controller",
-        "@deepseek-ai/dsh-client-connection",
         "@deepseek-ai/dsh-client-locale",
         "@deepseek-ai/dsh-client-ui-commands",
         "@deepseek-ai/dsh-api-remotes",
@@ -51,7 +50,6 @@
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
     "@deepseek-ai/dsh-api-session-controller": "workspace:^",
-    "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-ui-commands": "workspace:^",
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
@@ -65,7 +63,6 @@
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
     "@deepseek-ai/dsh-api-session-controller": "workspace:^",
-    "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-store": "workspace:^",
     "@deepseek-ai/dsh-client-test-runtime": "workspace:^",

+ 5 - 4
packages/client/ui-permission-presets/src/client/index.ts

@@ -14,7 +14,6 @@
  * created later through the host Settings API.
  */
 import type { Context as ClientContext } from '@deepseek-ai/cordis'
-import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'
 import type { SessionFace } from '@deepseek-ai/dsh-api-session-controller/client'
 // Type-only: pulls the locale plugin's Context merge (ctx.locale).
 import type {} from '@deepseek-ai/dsh-client-locale/client'
@@ -44,7 +43,10 @@ export type {
 } from './settings-store.ts'
 
 /** Required services (cordis fiber inject). */
-export const inject = ['commandUi', 'sessions', 'slots', 'locale', 'connection', 'remote', 'settingsScope', 'settingsSchema']
+export const inject = [
+  'commandUi', 'sessions', 'slots', 'locale', 'remote', 'remote.settings',
+  'settingsScope', 'settingsSchema',
+]
 
 const ACCESS_NS = 'permission.access'
 
@@ -113,10 +115,9 @@ export function apply(ctx: ClientContext): void {
 
   ctx.effect(() => ctx.locale.register('settings.permission', { zh, en }), 'ui-permission: settings row dictionaries')
 
-  const connection = ctx.get('connection') as ConnectionHandle
   // The shared SettingsScope mirror updates after document commits and reconnects.
   const controller = new PermissionPresetSettingsController(
-    ctx.settingsScope.describe(), connection.api, ctx.settingsSchema)
+    ctx.settingsScope.describe(), { settings: ctx.remote.settings }, ctx.settingsSchema)
   const load = (): Promise<void> => controller.load()
   const select = (preset: string): Promise<void> => controller.select(preset)
   const injected = (): PermissionRowInjected => ({

+ 10 - 12
packages/client/ui-permission-presets/src/client/settings-store.ts

@@ -6,14 +6,12 @@
  * back into the mirror.
  */
 
-import type {
-  IApiClient, SettingsNamespaceView,
-} from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import {
   createSnapshotStore, type SnapshotStore,
 } from '@deepseek-ai/dsh-client-store'
 import type {
-  SchemaNode, SettingsDescribeFace, SettingsSchemaService,
+  SchemaNode, SettingsDescribeFace, SettingsSchemaService, SettingsWireFace,
 } from '@deepseek-ai/dsh-client-ui-settings/client'
 import { displayPermissionPreset } from './presentation.ts'
 
@@ -101,7 +99,7 @@ export class PermissionPresetSettingsController {
    */
   constructor(
     private readonly describeFace: SettingsDescribeFace,
-    private readonly api: Pick<IApiClient, 'settings'>,
+    private readonly api: SettingsWireFace,
     private readonly schema: SettingsSchemaService,
   ) {}
 
@@ -139,17 +137,17 @@ export class PermissionPresetSettingsController {
       draft.error = null
     })
     try {
-      const response = await this.api.settings.mutate({
-        ns: PERMISSION_SETTINGS_NS,
-        ops: [{ op: 'set', path: ['defaultPreset'], value: preset }],
-        expectedRevision: view.revision,
-      })
-      if (!response.result.ok) throw new Error(response.result.error.message)
+      const response = await this.api.settings.mutate(
+        PERMISSION_SETTINGS_NS,
+        [{ op: 'set', path: ['defaultPreset'], value: preset }],
+        view.revision,
+      )
+      if (!response.ok) throw new Error(response.error.message)
       this.saving = false
       if (this.disposed) return
       // The mirror publish reaches this row's own subscription, so the fold
       // is also what republishes the accepted value here.
-      this.describeFace.acceptView(response.result.value)
+      this.describeFace.acceptView(response.value)
     } catch (error) {
       this.saving = false
       if (this.disposed) return

+ 3 - 2
packages/client/ui-permission-presets/tests/browser-plugin.client.spec.ts

@@ -13,7 +13,7 @@ import { describe, expect, it } from 'vitest'
 import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
-import { TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
+import { TestRemote, scriptedSettingsRemote } from '@deepseek-ai/dsh-client-test-runtime'
 import { apply as settingsApply, inject as settingsInject } from '@deepseek-ai/dsh-client-ui-settings/client'
 import type { CommandDecoration } from '@deepseek-ai/dsh-client-ui-commands/client'
 import type { PermissionSelect } from '@deepseek-ai/dsh-permission-presets/client'
@@ -40,7 +40,8 @@ async function bench() {
   const locale = new LocaleRuntime(ctx)
   locale.setLocale('en')
   ctx.provide('locale', locale)
-  const remote = new TestRemote(ctx)
+  const settingsRemote = scriptedSettingsRemote()
+  const remote = new TestRemote(ctx, { settings: settingsRemote.settings })
   ctx.slots.register({
     name: 'root',
     children: {

+ 4 - 6
packages/client/ui-permission-presets/tests/permission-presets-row.client.spec.tsx

@@ -43,8 +43,9 @@ function view(defaultPreset: string, revision = 0): SettingsNamespaceView {
   }
 }
 
+/** The settings namespace answers over the Remote carrier, which has no envelope. */
 function ok<T>(value: T) {
-  return { rpcId: 'test', result: { ok: true as const, value } }
+  return { ok: true as const, value }
 }
 
 const dictionary: Record<string, string> = en
@@ -153,11 +154,8 @@ describe('PermissionRow', () => {
       settings: {
         describe: () => describe.promise,
         mutate: () => Promise.resolve({
-          rpcId: 'test',
-          result: {
-            ok: false as const,
-            error: { code: 'settings-conflict', message: 'changed elsewhere', details: {} },
-          },
+          ok: false as const,
+          error: { code: 'settings-conflict', message: 'changed elsewhere', details: {} },
         }),
       },
     })

+ 11 - 13
packages/client/ui-permission-presets/tests/settings-store.client.spec.ts

@@ -35,8 +35,9 @@ function view(defaultPreset: string, revision = 0, schema: SettingsNamespaceView
   }
 }
 
+/** The settings namespace answers over the Remote carrier, which has no envelope. */
 function ok<T>(value: T) {
-  return { rpcId: 'test', result: { ok: true as const, value } }
+  return { ok: true as const, value }
 }
 
 /** The permission controller over a real mirror and one fake wire. */
@@ -117,11 +118,11 @@ describe('permission settings store', () => {
       revision: 4,
     })
     await controller.select('workspace-write')
-    expect(mutate).toHaveBeenCalledWith({
-      ns: 'permission',
-      ops: [{ op: 'set', path: ['defaultPreset'], value: 'workspace-write' }],
-      expectedRevision: 4,
-    })
+    expect(mutate).toHaveBeenCalledWith(
+      'permission',
+      [{ op: 'set', path: ['defaultPreset'], value: 'workspace-write' }],
+      4,
+    )
     expect(controller.store.getSnapshot()).toMatchObject({
       status: 'ready',
       currentValue: 'workspace-write',
@@ -140,11 +141,8 @@ describe('permission settings store', () => {
     const failing = permissionController({
       describe: () => Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: [view('read-only')] })),
       mutate: () => Promise.resolve({
-        rpcId: 'test',
-        result: {
-          ok: false as const,
-          error: { code: 'settings-conflict', message: 'stale', details: {} },
-        },
+        ok: false as const,
+        error: { code: 'settings-conflict', message: 'stale', details: {} },
       }),
     }).controller
     await failing.load()
@@ -171,8 +169,8 @@ describe('permission settings store', () => {
 
     const rejected = permissionController({
       describe: () => Promise.resolve({
-        rpcId: 'test',
-        result: { ok: false as const, error: { code: 'internal', message: 'offline', details: {} } },
+        ok: false as const,
+        error: { code: 'internal', message: 'offline', details: {} },
       }),
       mutate,
     }).controller

+ 7 - 10
packages/client/ui-settings-general/tests/apply.client.spec.ts

@@ -32,14 +32,11 @@ async function bench(isLoopback = true) {
   locale.setLocale('zh')
   ctx.provide('locale', locale)
   const settingsDescribe = vi.fn(() => Promise.resolve({
-    rpcId: 'settings-general' as never,
-    result: {
-      ok: true as const,
-      value: {
-        writable: true,
-        hasDocument: true,
-        namespaces: [],
-      },
+    ok: true as const,
+    value: {
+      writable: true,
+      hasDocument: true,
+      namespaces: [],
     },
   }))
   const settingsOpenDocument = vi.fn(() => Promise.resolve({
@@ -47,10 +44,10 @@ async function bench(isLoopback = true) {
     result: { ok: true as const, value: { opened: true as const } },
   }))
   ctx.provide('connection', {
-    api: { settings: { describe: settingsDescribe, openDocument: settingsOpenDocument } },
+    api: { settings: { openDocument: settingsOpenDocument } },
     isLoopback,
   } as never)
-  new TestRemote(ctx)
+  new TestRemote(ctx, { settings: { describe: settingsDescribe } })
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   return { ctx, slots: ctx.get('slots') as SlotRegistry, locale, settingsDescribe, settingsOpenDocument }
 }

+ 6 - 18
packages/client/ui-settings-general/tests/components.client.spec.tsx

@@ -77,11 +77,8 @@ describe('SettingsDocumentAction', () => {
     const controller = derivedDocumentStore({
       settings: {
         describe: vi.fn(() => Promise.resolve({
-          rpcId: 'document-action' as never,
-          result: {
-            ok: true as const,
-            value: { writable: true, hasDocument: true, namespaces: [] },
-          },
+          ok: true as const,
+          value: { writable: true, hasDocument: true, namespaces: [] },
         })),
         openDocument,
       },
@@ -99,14 +96,8 @@ describe('SettingsDocumentAction', () => {
 
   it('stays absent without a document and follows a mirror refresh to available', async () => {
     const describe = vi.fn()
-      .mockResolvedValueOnce({
-        rpcId: 'document-action-absent' as never,
-        result: { ok: true as const, value: { writable: true, hasDocument: false, namespaces: [] } },
-      })
-      .mockResolvedValueOnce({
-        rpcId: 'document-action-ready' as never,
-        result: { ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } },
-      })
+      .mockResolvedValueOnce({ ok: true as const, value: { writable: true, hasDocument: false, namespaces: [] } })
+      .mockResolvedValueOnce({ ok: true as const, value: { writable: true, hasDocument: true, namespaces: [] } })
     const wire = { settings: { describe, openDocument: vi.fn() } } as never
     const mirror = new SettingsDescribeMirror(wire)
     const controller = new SettingsDocumentStore(wire, mirror)
@@ -138,11 +129,8 @@ describe('SettingsDocumentAction', () => {
     const controller = derivedDocumentStore({
       settings: {
         describe: vi.fn(() => Promise.resolve({
-          rpcId: 'document-action' as never,
-          result: {
-            ok: true as const,
-            value: { writable: true, hasDocument: true, namespaces: [] },
-          },
+          ok: true as const,
+          value: { writable: true, hasDocument: true, namespaces: [] },
         })),
         openDocument: vi.fn(() => Promise.resolve({
           rpcId: 'document-open-failed' as never,

+ 4 - 17
packages/client/ui-settings-general/tests/settings-document-store.client.spec.ts

@@ -9,18 +9,8 @@ function derivedDocumentStore(api: object) {
   return new SettingsDocumentStore(wire, new SettingsDescribeMirror(wire))
 }
 
-function response(hasDocument = false): RpcResponse<{
-  writable: boolean
-  hasDocument: boolean
-  namespaces: []
-}> {
-  return {
-    rpcId: 'settings-document' as never,
-    result: {
-      ok: true,
-      value: { writable: true, hasDocument, namespaces: [] },
-    },
-  }
+function response(hasDocument = false) {
+  return { ok: true, value: { writable: true, hasDocument, namespaces: [] } }
 }
 
 function opened(): RpcResponse<{ opened: true }> {
@@ -30,11 +20,8 @@ function opened(): RpcResponse<{ opened: true }> {
   }
 }
 
-function describeFailed(message: string): RpcResponse<never> {
-  return {
-    rpcId: 'settings-document-failed' as never,
-    result: { ok: false, error: { code: 'internal', message, details: {} } },
-  }
+function describeFailed(message: string) {
+  return { ok: false as const, error: { code: 'internal', message, details: {} } }
 }
 
 describe('SettingsDocumentStore', () => {

+ 8 - 5
packages/client/ui-settings-general/tests/shell.client.spec.ts

@@ -18,11 +18,14 @@ async function bench() {
     getSnapshot: () => ({ active: 'zh', locales: [], revision: 0 }),
     subscribe: () => () => {},
   } as never)
-  ctx.provide('connection', {
-    api: { settings: { describe: async () => ({ result: { ok: false } }) } },
-    isLoopback: false,
-  } as never)
-  ctx.provide('remote', { $on: () => () => {} } as never)
+  ctx.provide('connection', { api: {}, isLoopback: false } as never)
+  // The shell mounts ui-settings, which injects `remote.settings`; without the
+  // namespace provided its fiber parks and no slot is ever declared.
+  const settings = {
+    describe: async () => ({ ok: false, error: { code: 'internal', message: 'no settings', details: {} } }),
+  }
+  ctx.provide('remote', { $on: () => () => {}, settings } as never)
+  ctx.provide('remote.settings', settings as never)
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   return { ctx, slots: ctx.get('slots') as SlotRegistry }
 }

+ 15 - 14
packages/client/ui-settings-models/src/client/CustomProviderCard.tsx

@@ -7,7 +7,7 @@
  * the provider editor with extra fields: the route id is being *chosen* here,
  * and the settings address does not exist until it is. One `settings.mutate`
  * sets the whole profile at `providers.<route>`; the key travels separately
- * through `credentials.set` under the reference the profile records, exactly as
+ * through `credentials/set` under the reference the profile records, exactly as
  * an existing provider's key does.
  *
  * The three fields a hand-declared route cannot default — endpoint, protocol,
@@ -23,13 +23,14 @@
 
 import { useState } from 'react'
 import type { ReactNode } from 'react'
-import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
+import type { JsonValue } from '@deepseek-ai/dsh-api-remotes/client'
 import { apiKeyFailure } from './apiKey.ts'
 import { EditorFooter } from './EditorFooter.tsx'
 import { validateDeepSeekModels } from './DeepSeekModelsEditor.tsx'
 import { ModelListEditor } from './ModelListEditor.tsx'
 import type { ModelDraft } from './ModelListEditor.tsx'
 import { deriveKeyRef, messageOf } from './store.ts'
+import type { ModelsWire } from './store.ts'
 import type { en } from './locales.ts'
 import styles from './ModelsSection.module.css'
 
@@ -59,7 +60,7 @@ export interface CustomProviderCardProps {
    */
   revision: number
   /** Wire faces for the write and for interrogating the endpoint. */
-  api: Pick<IApiClient, 'settings' | 'credentials' | 'llm'>
+  api: ModelsWire
   /** Section copy. */
   t: (key: keyof typeof en) => string
   /** Disable writes (read-only settings provider). */
@@ -143,15 +144,15 @@ export function CustomProviderCard(props: CustomProviderCardProps): ReactNode {
         baseURL,
         models: models.map(model => ({ ...model })),
       }
-      const response = await api.settings.mutate({
-        ns: NS,
-        ops: [{ op: 'set', path: ['providers', route], value: profile }],
-        // `taken` is a snapshot too, so the id check alone cannot see a route
-        // declared after this card opened; the revision makes that race a
-        // `settings-conflict` instead of a write over the other profile.
-        expectedRevision: openedAt,
-      })
-      if (!response.result.ok) return response.result.error.message
+      // `taken` is a snapshot too, so the id check alone cannot see a route
+      // declared after this card opened; the revision makes that race a
+      // `settings-conflict` instead of a write over the other profile.
+      const response = await api.settings.mutate(
+        NS,
+        [{ op: 'set', path: ['providers', route], value: profile as JsonValue }],
+        openedAt,
+      )
+      if (!response.ok) return response.error.message
       // The provider now exists. A retry after the key write below fails must
       // not re-run this mutate: the revision it holds is the one this write
       // just superseded, so the Host would answer `settings-conflict` and the
@@ -159,10 +160,10 @@ export function CustomProviderCard(props: CustomProviderCardProps): ReactNode {
       setCommitted(true)
     }
     if (storesKey) {
-      const stored = await api.credentials.set({ ref: keyRef, value: keyValue })
+      const stored = await api.credentials.set(keyRef, keyValue)
       // The profile landed; saying the key did not is the only honest report,
       // and the retry above now goes straight back to this write.
-      if (!stored.result.ok) return stored.result.error.message
+      if (!stored.ok) return stored.error.message
     }
     return undefined
   }

+ 2 - 3
packages/client/ui-settings-models/src/client/DeepSeekOnboardingDialog.tsx

@@ -8,10 +8,9 @@
 
 import { useEffect } from 'react'
 import type { ReactNode } from 'react'
-import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
 import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
 import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
-import type { ModelsSettingsState, ModelsSettingsStore } from './store.ts'
+import type { ModelsSettingsState, ModelsSettingsStore, ModelsWire } from './store.ts'
 import { onboardingReadiness } from './store.ts'
 import type { SettingsSchemaOperations } from './schema-operations.ts'
 import { ProviderEditor } from './ProviderEditor.tsx'
@@ -28,7 +27,7 @@ export interface DeepSeekOnboardingInjected {
   /** Shared Models-page join controller. */
   controller: ModelsSettingsStore
   /** Existing wire face reused by the Models credential editor. */
-  api: Pick<IApiClient, 'settings' | 'credentials' | 'llm'>
+  api: ModelsWire
   /** Settings schema and immutable path callbacks. */
   schema: SettingsSchemaOperations
   /** Feature copy. */

+ 11 - 11
packages/client/ui-settings-models/src/client/ModelsSection.tsx

@@ -14,14 +14,13 @@
 
 import { useState } from 'react'
 import type { ReactNode } from 'react'
-import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
 import { Button, IconPlusOutline16, Modal } from '@deepseek-ai/dsh-client-ui-primitives'
 import type { InjectFace, PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
 // Type-only: pulls this package's SlotMap merge (the two Models child slots).
 import type {} from './slot-contract.ts'
 import { CustomProviderCard } from './CustomProviderCard.tsx'
 import { deriveKeyRef, messageOf, protocolChoices, providerUsable } from './store.ts'
-import type { ModelsSettingsStore, ProviderRow } from './store.ts'
+import type { ModelsSettingsStore, ModelsWire, ProviderRow } from './store.ts'
 import type { SettingsSchemaOperations } from './schema-operations.ts'
 import { ProviderEditor, type ProviderEditorProps } from './ProviderEditor.tsx'
 import { SubagentModelSelectionCard } from './SubagentModelSelectionCard.tsx'
@@ -37,7 +36,7 @@ export interface ModelsSectionInjected {
     snapshot: ModelsSettingsStore['store']
   }
   /** Wire faces the editor writes through. */
-  api: Pick<IApiClient, 'settings' | 'credentials' | 'llm'>
+  api: ModelsWire
   /** Settings schema and immutable path callbacks. */
   schema: SettingsSchemaOperations
   /** Section copy. */
@@ -112,20 +111,21 @@ function renderProviderEditor({ target, ...props }: ProviderEditorRenderProps):
  * @returns the failure message, or undefined once the write and reload landed.
  */
 export async function removeProviderProfile(
-  api: Pick<IApiClient, 'settings' | 'credentials'>,
+  api: Pick<ModelsWire, 'settings' | 'credentials'>,
   controller: ModelsSettingsStore,
   target: { settingsNs: string; settingsPath: readonly string[]; credentialRef?: string },
 ): Promise<string | undefined> {
   try {
     if (target.credentialRef !== undefined) {
-      const credential = await api.credentials.unset({ ref: target.credentialRef })
-      if (!credential.result.ok) return credential.result.error.message
+      const credential = await api.credentials.unset(target.credentialRef)
+      if (!credential.ok) return credential.error.message
     }
-    const response = await api.settings.mutate({
-      ns: target.settingsNs,
-      ops: [{ op: 'unset', path: [...target.settingsPath] }],
-    })
-    if (!response.result.ok) return response.result.error.message
+    const response = await api.settings.mutate(
+      target.settingsNs,
+      [{ op: 'unset', path: [...target.settingsPath] }],
+      undefined,
+    )
+    if (!response.ok) return response.error.message
   } catch (error) {
     // The transport rejected rather than answering; the caller must be able
     // to retry the idempotent operation instead of the row silently staying.

+ 19 - 16
packages/client/ui-settings-models/src/client/ProviderEditor.tsx

@@ -1,7 +1,7 @@
 /**
  * One provider's editor card, hand-written per adapter family: the primary
  * field is a single write-only **API key** input (the page never asks for an
- * environment-variable name — a typed key stores through `credentials.set`
+ * environment-variable name — a typed key stores through `credentials/set`
  * under the profile's reference, deriving `<ROUTE>_API_KEY` when the profile
  * has none. The pi-ai profile records that derivation as `apiKeyEnv` only when
  * a key is entered; a blank key materializes a reference-free profile for
@@ -23,7 +23,9 @@
 
 import { useEffect, useMemo, useState } from 'react'
 import type { ReactNode } from 'react'
-import type { CredentialView, IApiClient, SettingsNamespaceView, SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client'
+import type {
+  CredentialInfo, JsonValue, SettingsNamespaceView, SettingsPathOpView,
+} from '@deepseek-ai/dsh-api-remotes/client'
 import {
   DeepSeekModelsEditor, modelDrafts, validateDeepSeekModels,
 } from './DeepSeekModelsEditor.tsx'
@@ -31,6 +33,7 @@ import { apiKeyFailure } from './apiKey.ts'
 import { EditorFooter } from './EditorFooter.tsx'
 import { ModelListEditor } from './ModelListEditor.tsx'
 import { deriveKeyRef, messageOf, protocolChoices } from './store.ts'
+import type { ModelsWire } from './store.ts'
 import type { SettingsSchemaOperations } from './schema-operations.ts'
 import type { en } from './locales.ts'
 import styles from './ModelsSection.module.css'
@@ -64,7 +67,7 @@ export interface ProviderEditorProps {
   /** Path from the section root to this provider's profile. */
   settingsPath: readonly string[]
   /** Wire faces for writes and for interrogating a provider endpoint. */
-  api: Pick<IApiClient, 'settings' | 'credentials' | 'llm'>
+  api: ModelsWire
   /** Section copy. */
   t: (key: keyof typeof en) => string
   /** Disable writes (read-only settings provider). */
@@ -117,7 +120,7 @@ export function pathOps(
   const ops: SettingsPathOpView[] = []
   for (const [key, value] of Object.entries(after)) {
     if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue
-    ops.push({ op: 'set', path: [...base, key], value })
+    ops.push({ op: 'set', path: [...base, key], value: value as JsonValue })
   }
   for (const key of Object.keys(previous)) {
     if (!(key in after)) ops.push({ op: 'unset', path: [...base, key] })
@@ -155,7 +158,7 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
   const { namespace, schema, settingsPath, api, t } = props
   const [draft, setDraft] = useState<Record<string, unknown>>(() => draftAt(schema, namespace, settingsPath))
   const [keyDraft, setKeyDraft] = useState('')
-  const [keyState, setKeyState] = useState<CredentialView | undefined>(undefined)
+  const [keyState, setKeyState] = useState<CredentialInfo | undefined>(undefined)
   const [busy, setBusy] = useState(false)
   const [failure, setFailure] = useState<string | undefined>(undefined)
   // A settings success advances both retry baselines immediately. Keeping the
@@ -187,10 +190,10 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
     // neither a business rejection nor a transport failure may reach the
     // browser as an unhandled rejection, so the card simply renders without
     // the "already configured" hint.
-    void api.credentials.describe({ refs: [keyRef] }).then(
+    void api.credentials.describe([keyRef]).then(
       (response) => {
-        if (stale || !response.result.ok) return
-        setKeyState(response.result.value.credentials[keyRef])
+        if (stale || !response.ok) return
+        setKeyState(response.value[keyRef])
       },
       () => undefined,
     )
@@ -279,19 +282,19 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
         ? [{ op: 'set', path: [...settingsPath], value: {} }]
         : pathOps(settingsPath, committedOriginal, next)
     if (ops.length > 0) {
-      const response = await api.settings.mutate({ ns, ops, expectedRevision })
-      if (!response.result.ok) {
-        return response.result.error.code === 'settings-conflict'
+      const response = await api.settings.mutate(ns, ops, expectedRevision)
+      if (!response.ok) {
+        return response.error.code === 'settings-conflict'
           ? t('conflict')
-          : response.result.error.message
+          : response.error.message
       }
-      setCommittedOriginal(schema.getPath(response.result.value.user, settingsPath))
-      setExpectedRevision(response.result.value.revision)
+      setCommittedOriginal(schema.getPath(response.value.user, settingsPath))
+      setExpectedRevision(response.value.revision)
       setDraft(next)
     }
     if (keyValue.length > 0) {
-      const stored = await api.credentials.set({ ref: keyRef, value: keyValue })
-      if (!stored.result.ok) return stored.result.error.message
+      const stored = await api.credentials.set(keyRef, keyValue)
+      if (!stored.ok) return stored.error.message
     }
     setKeyDraft('')
     return undefined

+ 10 - 9
packages/client/ui-settings-models/src/client/SubagentModelSelectionCard.tsx

@@ -2,7 +2,8 @@
 
 import { useState } from 'react'
 import type { ReactNode } from 'react'
-import type { IApiClient, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsWireFace } from '@deepseek-ai/dsh-client-ui-settings/client'
 import type { ModelsSettingsStore } from './store.ts'
 import type { en } from './locales.ts'
 import { messageOf } from './store.ts'
@@ -15,7 +16,7 @@ export interface SubagentModelSelectionCardProps {
   /** Whether the settings provider accepts writes. */
   writable: boolean
   /** Settings wire face. */
-  api: Pick<IApiClient, 'settings'>
+  api: SettingsWireFace
   /** Models page controller to refresh after a commit. */
   controller: ModelsSettingsStore
   /** Localized Models copy. */
@@ -45,13 +46,13 @@ export function SubagentModelSelectionCard({
     setSaving(true)
     setSaved(false)
     setError(undefined)
-    void api.settings.update({
-      ns: namespace.ns,
-      patch: { enabled: !enabled },
-      expectedRevision: namespace.revision,
-    }).then(async (response) => {
-      if (!response.result.ok) throw new Error(response.result.error.message)
-      controller.acceptNamespace(response.result.value)
+    void api.settings.update(
+      namespace.ns,
+      { enabled: !enabled },
+      namespace.revision,
+    ).then(async (response) => {
+      if (!response.ok) throw new Error(response.error.message)
+      controller.acceptNamespace(response.value)
       await controller.load()
       setSaved(true)
     }).catch((reason: unknown) => {

+ 17 - 5
packages/client/ui-settings-models/src/client/index.ts

@@ -24,6 +24,7 @@ import { WelcomeNotice } from './WelcomeNotice.tsx'
 import type { WelcomeNoticeInjected } from './WelcomeNotice.tsx'
 import { decodeWelcomeSection, WelcomeNoticeStore } from './welcome-store.ts'
 import { ModelsSettingsStore } from './store.ts'
+import type { ModelsWire } from './store.ts'
 import { createSettingsSchemaOperations } from './schema-operations.ts'
 import { en, zh, type ModelsKey } from './locales.ts'
 import { WELCOME_NOTICE_SETTINGS_NAMESPACE } from '../onboarding-copy.ts'
@@ -41,7 +42,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
 
 /** Dictionary namespace owned by this plugin. */
 const NS = 'settings.models'
-export type { ModelsSettingsState, ProviderRow } from './store.ts'
+export type { ModelsCredentials, ModelsSettingsState, ModelsWire, ProviderRow } from './store.ts'
 
 /**
  * Refetch the page snapshot only after its first load: an unopened Models
@@ -58,7 +59,10 @@ export function refreshIfLoaded(controller: ModelsSettingsStore): void {
  * ui-settings' apply, whose activation order relative to this one is NOT
  * constrained; registration depends on each slot through `slots.inject()`.
  */
-export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope', 'settingsSchema']
+export const inject = [
+  'slots', 'locale', 'connection', 'remote', 'remote.credentials', 'remote.settings',
+  'settingsScope', 'settingsSchema',
+]
 
 /**
  * Register the Models section once the `settings.section` declaration is on
@@ -71,21 +75,29 @@ export function apply(ctx: ClientContext): void {
 
   const connection = ctx.get('connection') as ConnectionHandle
   const schema = createSettingsSchemaOperations(ctx.settingsSchema)
-  const controller = new ModelsSettingsStore(connection.api, schema, ctx.settingsScope.describe())
+  // The page's two carriers under one face: model discovery and the catalog
+  // still ride the unary API, while settings and credentials are Remote
+  // namespaces.
+  const wire: ModelsWire = {
+    ...connection.api,
+    credentials: ctx.remote.credentials,
+    settings: ctx.remote.settings,
+  }
+  const controller = new ModelsSettingsStore(wire, schema, ctx.settingsScope.describe())
   // Registration-time text (the nav label thunk) and the inject faces share
   // one bound translate; copy freshness rides the locale revision.
   const t = ctx.locale.bind(NS) as ModelsSectionInjected['t']
   const injected = (): ModelsSectionInjected => ({
     controller,
     hooks: { snapshot: controller.store },
-    api: connection.api,
+    api: wire,
     schema,
     t,
   })
   const deepSeekOnboardingInjected = (): DeepSeekOnboardingInjected => ({
     controller,
     hooks: { models: controller.store },
-    api: connection.api,
+    api: wire,
     schema,
     t,
   })

+ 26 - 11
packages/client/ui-settings-models/src/client/store.ts

@@ -1,17 +1,17 @@
 /**
  * Models settings page store: one snapshot joining the configurable-provider
  * directory (`llm.providers`), the settings namespaces (shared settings mirror),
- * and the referenced credentials (`credentials.describe`). The host stays the
+ * and the referenced credentials (`credentials/describe`). The host stays the
  * single fact source — every mutation writes through the wire and the page
  * re-renders from the next describe, pushed or refetched.
  */
 
 import type {
-  ConfigurableProviderView, CredentialView, IApiClient, SettingsNamespaceView,
+  ClientRemote, ConfigurableProviderView, CredentialInfo, IApiClient, SettingsNamespaceView,
 } from '@deepseek-ai/dsh-api-remotes/client'
 import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
 import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
-import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client'
+import type { SettingsDescribeFace, SettingsRemote } from '@deepseek-ai/dsh-client-ui-settings/client'
 import type { SettingsSchemaOperations } from './schema-operations.ts'
 
 /**
@@ -20,6 +20,21 @@ import type { SettingsSchemaOperations } from './schema-operations.ts'
  */
 const PROBE_ROUTE = '\u0000probe'
 
+/** The credentials Remote methods the Models page reads and writes through. */
+export type ModelsCredentials = Pick<ClientRemote['credentials'], 'describe' | 'set' | 'unset'>
+
+/**
+ * Every wire face the Models page reaches: the settings and llm unary domains,
+ * plus the credentials Remote namespace, which is addressed by reference name
+ * and never answers with a value.
+ */
+export interface ModelsWire extends Pick<IApiClient, 'llm'> {
+  /** The settings Remote namespace: the redacted read and the profile writes. */
+  settings: SettingsRemote
+  /** Credential state and writes for the references provider profiles name. */
+  credentials: ModelsCredentials
+}
+
 /** One provider row the page renders. */
 export interface ProviderRow {
   /** The directory entry (route id, display name, settings address, live state). */
@@ -31,14 +46,14 @@ export interface ProviderRow {
   /** The credential reference the resolved profile names, when one does. */
   apiKeyEnv: string | undefined
   /** Credential state for {@link apiKeyEnv}, once described. */
-  credential: CredentialView | undefined
+  credential: CredentialInfo | undefined
   /**
    * Credential state for the page's derived `<ROUTE>_API_KEY`, described only
    * while the profile names no reference — the provider-card seat's
    * `keyConfigured` fact for dormant and keyless rows, matching the editor's
    * own derivation rule.
    */
-  derivedCredential?: CredentialView
+  derivedCredential?: CredentialInfo
 }
 
 /** Page snapshot. */
@@ -122,11 +137,11 @@ export class ModelsSettingsStore {
   private generation = 0
 
   /**
-   * @param api - the wire face (credentials/llm domains, and settings writes).
+   * @param api - the page's wire faces (credentials Remote, llm reads, settings writes).
    * @param describeFace - the shared mirror's describe face (namespace views and writability).
    */
   constructor(
-    private readonly api: Pick<IApiClient, 'settings' | 'credentials' | 'llm'>,
+    private readonly api: ModelsWire,
     private readonly schema: SettingsSchemaOperations,
     private readonly describeFace: SettingsDescribeFace,
   ) {}
@@ -193,16 +208,16 @@ export class ModelsSettingsStore {
       }
     })
     const refs = [...new Set(rows.map(row => row.apiKeyEnv ?? deriveKeyRef(row.entry.provider)))]
-    let credentials: Record<string, CredentialView> = {}
+    let credentials: Record<string, CredentialInfo> = {}
     let credentialError: string | null = null
     if (refs.length > 0) {
       try {
-        const response = await this.api.credentials.describe({ refs })
+        const response = await this.api.credentials.describe(refs)
         // Credential state is an enrichment for the Models page: neither a
         // business rejection nor a transport failure fails the load. The
         // onboarding projection below retains the failure distinction.
-        if (response.result.ok) credentials = response.result.value.credentials
-        else credentialError = response.result.error.message
+        if (response.ok) credentials = response.value
+        else credentialError = response.error.message
       } catch (error) {
         credentialError = messageOf(error)
       }

+ 41 - 40
packages/client/ui-settings-models/tests/apply.client.spec.ts

@@ -4,7 +4,7 @@ import { describe, expect, it, vi } from 'vitest'
 import { resolveSlotLabel } from '@deepseek-ai/dsh-client-ui-slots'
 import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client'
 import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
-import { TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
+import { TestRemote, scriptedSettingsRemote } from '@deepseek-ai/dsh-client-test-runtime'
 import { apply as settingsApply, inject as settingsInject } from '@deepseek-ai/dsh-client-ui-settings/client'
 import { apply, inject, refreshIfLoaded } from '@deepseek-ai/dsh-client-ui-settings-models/client'
 import {
@@ -24,14 +24,18 @@ async function bench(isLoopback = true, settings?: object, services: object = {}
   const locale = new LocaleRuntime(ctx)
   locale.setLocale('zh')
   ctx.provide('locale', locale)
-  const remote = new TestRemote(ctx)
-  // Without a settings face the mirror's reads fail and stay contained; the
-  // Models join itself never fetches until a section actually loads. The real
-  // ui-settings apply also provides the settingsSchema service.
-  ctx.provide('connection', {
-    api: settings === undefined ? services : { ...services, settings },
-    isLoopback,
-  } as never)
+  const remote = new TestRemote(ctx, {
+    credentials: {
+      describe: vi.fn(() => Promise.resolve({ ok: true, value: {} })),
+      set: vi.fn(),
+      unset: vi.fn(),
+    },
+    // Without a settings face the mirror's reads fail and stay contained; the
+    // Models join itself never fetches until a section actually loads. The real
+    // ui-settings apply also provides the settingsSchema service.
+    settings: settings ?? scriptedSettingsRemote().settings,
+  })
+  ctx.provide('connection', { api: services, isLoopback } as never)
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   return { ctx, slots: ctx.get('slots') as SlotRegistry, locale, remote }
 }
@@ -51,7 +55,10 @@ function declare(slots: SlotRegistry): () => void {
 
 describe('ui-settings-models apply', () => {
   it('declares the services it uses', () => {
-    expect(inject).toEqual(['slots', 'locale', 'connection', 'remote', 'settingsScope', 'settingsSchema'])
+    expect(inject).toEqual([
+      'slots', 'locale', 'connection', 'remote', 'remote.credentials', 'remote.settings',
+      'settingsScope', 'settingsSchema',
+    ])
   })
 
   it('registers the models nav entry for declarations before or after apply', async () => {
@@ -243,21 +250,18 @@ describe('pushed invalidations', () => {
     const acknowledgement = { current: undefined as string | undefined }
     const settings = {
       describe: vi.fn(() => Promise.resolve({
-        rpcId: 'apply-welcome' as never,
-        result: {
-          ok: true as const,
-          value: {
-            writable: true,
-            hasDocument: false,
-            namespaces: [{
-              ns: WELCOME_NOTICE_SETTINGS_NAMESPACE,
-              schema: {},
-              value: acknowledgement.current === undefined ? {} : { [WELCOME_NOTICE_ACK_FIELD]: acknowledgement.current },
-              applies: 'live' as const,
-              secrets: [],
-              revision: 0,
-            }],
-          },
+        ok: true as const,
+        value: {
+          writable: true,
+          hasDocument: false,
+          namespaces: [{
+            ns: WELCOME_NOTICE_SETTINGS_NAMESPACE,
+            schema: {},
+            value: acknowledgement.current === undefined ? {} : { [WELCOME_NOTICE_ACK_FIELD]: acknowledgement.current },
+            applies: 'live' as const,
+            secrets: [],
+            revision: 0,
+          }],
         },
       })),
     }
@@ -284,21 +288,18 @@ describe('pushed invalidations', () => {
   it('joins the refreshed mirror view on a settings invalidation', async () => {
     let revision = 1
     const describe = vi.fn(() => Promise.resolve({
-      rpcId: `apply-models-${revision}` as never,
-      result: {
-        ok: true as const,
-        value: {
-          writable: true,
-          hasDocument: false,
-          namespaces: [{
-            ns: 'llm-test',
-            schema: {},
-            value: {},
-            applies: 'live' as const,
-            secrets: [],
-            revision,
-          }],
-        },
+      ok: true as const,
+      value: {
+        writable: true,
+        hasDocument: false,
+        namespaces: [{
+          ns: 'llm-test',
+          schema: {},
+          value: {},
+          applies: 'live' as const,
+          secrets: [],
+          revision,
+        }],
       },
     }))
     const providers = vi.fn(() => Promise.resolve({

+ 153 - 148
packages/client/ui-settings-models/tests/components.client.spec.tsx

@@ -4,7 +4,7 @@ import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testi
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import Schema from '@deepseek-ai/schemastery'
 import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-test-runtime'
-import type { RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { JsonValue, RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import {
   ModelsSection, needsSetup, providerCopy, providerTargetLabel, removeProviderProfile,
 } from '../src/client/ModelsSection.tsx'
@@ -90,7 +90,7 @@ function wireNamespaces(): SettingsNamespaceView[] {
   return [
     {
       ns: 'llm-deepseek',
-      schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
+      schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as JsonValue,
       value: {
         apiKeyEnv: 'DEEPSEEK_API_KEY',
         baseURL: 'https://base',
@@ -108,7 +108,7 @@ function wireNamespaces(): SettingsNamespaceView[] {
       ns: 'llm-plain',
       schema: JSON.parse(JSON.stringify(Schema.object({
         profiles: Schema.dict(Schema.object({ note: Schema.string() })),
-      }).toJSON())) as unknown,
+      }).toJSON())) as JsonValue,
       value: {},
       applies: 'live',
       secrets: [],
@@ -116,7 +116,7 @@ function wireNamespaces(): SettingsNamespaceView[] {
     },
     {
       ns: 'llm-pi-ai',
-      schema: JSON.parse(JSON.stringify(PiAiConfig.toJSON())) as unknown,
+      schema: JSON.parse(JSON.stringify(PiAiConfig.toJSON())) as JsonValue,
       value: { providers: { openai: { apiKeyEnv: 'OPENAI_API_KEY', baseURL: 'https://proxy', headers: { 'X-Team': 'a' } }, zombie: {} } },
       user: { providers: { openai: { apiKeyEnv: 'OPENAI_API_KEY', baseURL: 'https://proxy', headers: { 'X-Team': 'a' } }, zombie: {} } },
       applies: 'live',
@@ -125,7 +125,7 @@ function wireNamespaces(): SettingsNamespaceView[] {
     },
     {
       ns: 'subagent-model-selection',
-      schema: JSON.parse(JSON.stringify(Schema.object({ enabled: Schema.boolean().default(false) }).toJSON())) as unknown,
+      schema: JSON.parse(JSON.stringify(Schema.object({ enabled: Schema.boolean().default(false) }).toJSON())) as JsonValue,
       value: { enabled: false },
       applies: 'live',
       secrets: [],
@@ -144,20 +144,25 @@ function fail<T>(message: string, code = 'settings-rejected'): RpcResponse<T> {
     result: { ok: false, error: { code, message, details: { ns: 'x' } } as never },
   }
 }
+/** Credentials answers over the Remote carrier, which has no envelope. */
+function remoteOk<T>(value: T) {
+  return { ok: true as const, value }
+}
+function remoteFail(message: string, code = 'credential-rejected') {
+  return { ok: false as const, error: { code, message, details: {} } }
+}
 
 function scriptedFace(overrides: {
   update?: ReturnType<typeof vi.fn>
-  replace?: ReturnType<typeof vi.fn>
   mutate?: ReturnType<typeof vi.fn>
   set?: ReturnType<typeof vi.fn>
   unset?: ReturnType<typeof vi.fn>
 } = {}) {
   const providerNamespace = wireNamespaces().find(view => view.ns === 'llm-pi-ai')!
-  const update = overrides.update ?? vi.fn(() => Promise.resolve(ok(providerNamespace)))
-  const replace = overrides.replace ?? vi.fn(() => Promise.resolve(ok(providerNamespace)))
-  const mutate = overrides.mutate ?? vi.fn(() => Promise.resolve(ok(providerNamespace)))
-  const set = overrides.set ?? vi.fn(() => Promise.resolve(ok({})))
-  const unset = overrides.unset ?? vi.fn(() => Promise.resolve(ok({})))
+  const update = overrides.update ?? vi.fn(() => Promise.resolve(remoteOk(providerNamespace)))
+  const mutate = overrides.mutate ?? vi.fn(() => Promise.resolve(remoteOk(providerNamespace)))
+  const set = overrides.set ?? vi.fn(() => Promise.resolve(remoteOk(undefined)))
+  const unset = overrides.unset ?? vi.fn(() => Promise.resolve(remoteOk(undefined)))
   const face = {
     llm: {
       providers: vi.fn(() => Promise.resolve(ok({
@@ -173,24 +178,23 @@ function scriptedFace(overrides: {
       models: vi.fn(() => Promise.resolve(ok({ groups: [], failures: [] }))),
     },
     settings: {
-      describe: vi.fn(() => Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: wireNamespaces() }))),
+      describe: vi.fn(() => Promise.resolve(remoteOk({ writable: true, hasDocument: false, namespaces: wireNamespaces() }))),
       update,
-      replace,
       mutate,
     },
     credentials: {
-      describe: vi.fn((payload: { refs: string[] }) => Promise.resolve(ok({
-        credentials: Object.fromEntries(payload.refs.map(ref => [ref, {
+      describe: vi.fn((refs: string[]) => Promise.resolve(remoteOk(
+        Object.fromEntries(refs.map(ref => [ref, {
           configured: ref === 'OPENAI_API_KEY',
           ...ref === 'OPENAI_API_KEY' ? { source: 'file' } : {},
           writable: true,
         }])),
-      }))),
+      ))),
       set,
       unset,
     },
   }
-  return { face, update, replace, mutate, set, unset }
+  return { face, update, mutate, set, unset }
 }
 
 type WireFace = ConstructorParameters<typeof ModelsSettingsStore>[0]
@@ -218,7 +222,7 @@ function cardSeatCalls(
 }
 
 async function mountFace(scripted: ReturnType<typeof scriptedFace>) {
-  const { face, update, replace, mutate, set, unset } = scripted
+  const { face, update, mutate, set, unset } = scripted
   const mirror = new SettingsDescribeMirror(face as never)
   const controller = new ModelsSettingsStore(face as unknown as WireFace, settingsSchema, mirror)
   await controller.load()
@@ -232,7 +236,7 @@ async function mountFace(scripted: ReturnType<typeof scriptedFace>) {
     renderSlot: renderSlot as unknown as ModelsSectionProps['renderSlot'],
   }
   const view = render(<ModelsSection {...injected} />)
-  return { view, face, update, replace, mutate, set, unset, controller, mirror, renderSlot }
+  return { view, face, update, mutate, set, unset, controller, mirror, renderSlot }
 }
 
 async function mountSection(overrides: Parameters<typeof scriptedFace>[0] = {}) {
@@ -245,10 +249,10 @@ async function mountSection(overrides: Parameters<typeof scriptedFace>[0] = {})
  */
 async function mountFirstRun(overrides: Parameters<typeof scriptedFace>[0] = {}) {
   const scripted = scriptedFace(overrides)
-  scripted.face.credentials.describe.mockImplementation((payload: { refs: string[] }) =>
-    Promise.resolve(ok({
-      credentials: Object.fromEntries(payload.refs.map(ref => [ref, { configured: false, writable: true }])),
-    })))
+  scripted.face.credentials.describe.mockImplementation((refs: string[]) =>
+    Promise.resolve(remoteOk(
+      Object.fromEntries(refs.map(ref => [ref, { configured: false, writable: true }])),
+    )))
   return mountFace(scripted)
 }
 
@@ -295,12 +299,12 @@ describe('ModelsSection', () => {
 
   it('derives the draft seat\'s key fact from the page\'s conventional reference', async () => {
     const scripted = scriptedFace()
-    scripted.face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
-      credentials: Object.fromEntries(payload.refs.map(ref => [ref, {
+    scripted.face.credentials.describe.mockImplementation((refs: string[]) => Promise.resolve(remoteOk(
+      Object.fromEntries(refs.map(ref => [ref, {
         configured: ref === 'OPENAI_API_KEY' || ref === 'ANTHROPIC_API_KEY',
         writable: true,
       }])),
-    })))
+    )))
     const { renderSlot } = await mountFace(scripted)
     renderSlot.mockClear()
     fireEvent.click(screen.getByRole('button', { name: en.add }))
@@ -332,7 +336,7 @@ describe('ModelsSection', () => {
       user: { enabled: true },
       revision: 5,
     }
-    const update = vi.fn(() => Promise.resolve(ok(enabledNamespace)))
+    const update = vi.fn(() => Promise.resolve(remoteOk(enabledNamespace)))
     await mountSection({ update })
 
     const toggle = screen.getByRole('switch', { name: en.subagentModelSelectionToggle })
@@ -340,18 +344,18 @@ describe('ModelsSection', () => {
     fireEvent.click(toggle)
 
     await waitFor(() => { expect(toggle.getAttribute('aria-checked')).toBe('true') })
-    expect(update).toHaveBeenCalledWith({
-      ns: 'subagent-model-selection',
-      patch: { enabled: true },
-      expectedRevision: 4,
-    })
+    expect(update).toHaveBeenCalledWith(
+      'subagent-model-selection',
+      { enabled: true },
+      4,
+    )
     expect(screen.getByRole('status').textContent).toBe(en.subagentModelSelectionSaved)
   })
 
   it('reports rejected subagent model-selection updates and permits a retry', async () => {
     const update = vi.fn()
-      .mockResolvedValueOnce(fail<SettingsNamespaceView>('revision changed'))
-      .mockResolvedValueOnce(ok({
+      .mockResolvedValueOnce(remoteFail('revision changed', 'settings-rejected'))
+      .mockResolvedValueOnce(remoteOk({
         ...wireNamespaces().find(view => view.ns === 'subagent-model-selection')!,
         value: { enabled: true },
         revision: 5,
@@ -372,12 +376,12 @@ describe('ModelsSection', () => {
       ...wireNamespaces().find(view => view.ns === 'subagent-model-selection')!,
       value: null,
     } as unknown as SettingsNamespaceView
-    const update = vi.fn()
+    const mutate = vi.fn()
     render(
       <SubagentModelSelectionCard
         namespace={namespace}
         writable={false}
-        api={{ settings: { update } } as never}
+        api={{ settings: { mutate } } as never}
         controller={{ acceptNamespace: vi.fn(), load: vi.fn() } as never}
         t={t}
       />,
@@ -387,7 +391,7 @@ describe('ModelsSection', () => {
     expect(toggle.getAttribute('aria-checked')).toBe('false')
     expect((toggle as HTMLButtonElement).disabled).toBe(true)
     fireEvent.click(toggle)
-    expect(update).not.toHaveBeenCalled()
+    expect(mutate).not.toHaveBeenCalled()
   })
 
   it('renders the unkeyed whole-section provider as an open setup card in the first-run posture', async () => {
@@ -420,9 +424,9 @@ describe('ModelsSection', () => {
 
   it('marks only a confirmed missing reference and leaves native or unavailable state unmarked', async () => {
     const { face } = scriptedFace()
-    face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
-      credentials: Object.fromEntries(payload.refs.map(ref => [ref, { configured: false, writable: true }])),
-    })))
+    face.credentials.describe.mockImplementation((refs: string[]) => Promise.resolve(remoteOk(
+      Object.fromEntries(refs.map(ref => [ref, { configured: false, writable: true }])),
+    )))
     const controller = new ModelsSettingsStore(face as unknown as WireFace, settingsSchema, new SettingsDescribeMirror(face as never))
     await controller.load()
     render(<ModelsSection
@@ -444,9 +448,9 @@ describe('ModelsSection', () => {
 
   it('turns the setup card into a row once the credential reports configured', async () => {
     const { face } = await mountFirstRun()
-    face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
-      credentials: Object.fromEntries(payload.refs.map(ref => [ref, { configured: true, writable: true }])),
-    })))
+    face.credentials.describe.mockImplementation((refs: string[]) => Promise.resolve(remoteOk(
+      Object.fromEntries(refs.map(ref => [ref, { configured: true, writable: true }])),
+    )))
     const controller = new ModelsSettingsStore(face as unknown as WireFace, settingsSchema, new SettingsDescribeMirror(face as never))
     await controller.load()
     cleanup()
@@ -503,12 +507,12 @@ describe('ModelsSection', () => {
   })
 
   it('stores a typed key write-only from the setup card without touching settings', async () => {
-    const { set, update, face } = await mountFirstRun()
+    const { set, mutate, face } = await mountFirstRun()
     const key = screen.getByLabelText<HTMLInputElement>(en.keyInput)
     fireEvent.change(key, { target: { value: '  sk-live  ' } })
     fireEvent.click(screen.getByText(en.apply))
-    await waitFor(() => { expect(set).toHaveBeenCalledWith({ ref: 'DEEPSEEK_API_KEY', value: 'sk-live' }) })
-    expect(update).not.toHaveBeenCalled()
+    await waitFor(() => { expect(set).toHaveBeenCalledWith('DEEPSEEK_API_KEY', 'sk-live') })
+    expect(mutate).not.toHaveBeenCalled()
     // The saved key re-loads the join; the settings answer rides the shared
     // mirror, so the reload shows as a directory read rather than a describe.
     await waitFor(() => { expect(face.llm.providers.mock.calls.length).toBeGreaterThan(1) })
@@ -520,8 +524,8 @@ describe('ModelsSection', () => {
   })
 
   it('reuses the provider editor as a required credential-only onboarding form', async () => {
-    let finishSet: ((response: RpcResponse<Record<string, never>>) => void) | undefined
-    const set = vi.fn(() => new Promise<RpcResponse<Record<string, never>>>((resolve) => {
+    let finishSet: ((response: { ok: true; value: undefined }) => void) | undefined
+    const set = vi.fn(() => new Promise<{ ok: true; value: undefined }>((resolve) => {
       finishSet = resolve
     }))
     const { face, mutate } = scriptedFace({ set })
@@ -567,13 +571,13 @@ describe('ModelsSection', () => {
     fireEvent.click(save)
 
     expect(await screen.findByText(en.onboardingSaving)).toBeTruthy()
-    expect(set).toHaveBeenCalledWith({ ref: 'DEEPSEEK_API_KEY', value: 'sk-onboarding' })
+    expect(set).toHaveBeenCalledWith('DEEPSEEK_API_KEY', 'sk-onboarding')
     expect(mutate).not.toHaveBeenCalled()
     expect(onClose).not.toHaveBeenCalled()
 
     if (finishSet === undefined) throw new Error('credential write did not start')
     await act(async () => {
-      finishSet?.(ok({}))
+      finishSet?.(remoteOk(undefined))
       await Promise.resolve()
     })
     expect(onClose).toHaveBeenCalledWith(true)
@@ -581,7 +585,7 @@ describe('ModelsSection', () => {
 
   it('applies customized deepseek fields as path ops', async () => {
     const { mutate } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
+      mutate: vi.fn(() => Promise.resolve(remoteOk(wireNamespaces()[0]))),
     })
     fireEvent.click(screen.getByText(en.customized))
     const baseURL = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
@@ -593,16 +597,16 @@ describe('ModelsSection', () => {
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
     // Only the field that actually changed: reasoningEffort was already
     // 'high' in the loaded profile, so it produces no op.
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-deepseek',
-      ops: [{ op: 'set', path: ['baseURL'], value: 'https://next2' }],
-      expectedRevision: 0,
-    })
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-deepseek',
+      [{ op: 'set', path: ['baseURL'], value: 'https://next2' }],
+      0,
+    ])
   })
 
   it('materializes inherited models and adds an arbitrary DeepSeek id', async () => {
     const { mutate } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
+      mutate: vi.fn(() => Promise.resolve(remoteOk(wireNamespaces()[0]))),
     })
     fireEvent.click(screen.getByText(en.customized))
     expect(screen.getByText(en.modelsInherited)).toBeTruthy()
@@ -620,9 +624,9 @@ describe('ModelsSection', () => {
     fireEvent.click(screen.getByText(en.apply))
 
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-deepseek',
-      ops: [{
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-deepseek',
+      [{
         op: 'set',
         path: ['models'],
         value: [
@@ -630,8 +634,8 @@ describe('ModelsSection', () => {
           { id: 'private-preview', name: 'Private Preview', contextWindow: 131_072 },
         ],
       }],
-      expectedRevision: 0,
-    })
+      0,
+    ])
   })
 
   it('rejects duplicate DeepSeek model ids before writing', async () => {
@@ -706,7 +710,7 @@ describe('ModelsSection', () => {
 
   it('accepts a suffixed context window and stores the plain count', async () => {
     const { mutate } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
+      mutate: vi.fn(() => Promise.resolve(remoteOk(wireNamespaces()[0]))),
     })
     fireEvent.click(screen.getByText(en.customized))
     expandRow(1)
@@ -730,9 +734,9 @@ describe('ModelsSection', () => {
     fireEvent.click(screen.getByText(en.apply))
 
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-deepseek',
-      ops: [{
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-deepseek',
+      [{
         op: 'set',
         path: ['models'],
         value: [
@@ -740,8 +744,8 @@ describe('ModelsSection', () => {
           { ...DEFAULT_DEEPSEEK_MODELS[1], contextWindow: 256_000 },
         ],
       }],
-      expectedRevision: 0,
-    })
+      0,
+    ])
   })
 
   it('keeps unreadable context-window text on screen and refuses the write', async () => {
@@ -773,7 +777,7 @@ describe('ModelsSection', () => {
     const stored = { models: [{ id: 'user-only-model', name: 'User Only' }] }
     const overridden: SettingsNamespaceView = {
       ns: 'llm-deepseek',
-      schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
+      schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as JsonValue,
       value: { ...stored, defaultContextWindow: 1_000_000 },
       ...base === undefined ? {} : { base },
       user: stored,
@@ -858,7 +862,7 @@ describe('ModelsSection', () => {
     // inherited row displayed text no settings layer stores — and because an
     // unreadable buffer never settles, it stayed there indefinitely.
     const { mutate } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
+      mutate: vi.fn(() => Promise.resolve(remoteOk(wireNamespaces()[0]))),
     })
     fireEvent.click(screen.getByText(en.customized))
     expandRow(1)
@@ -881,7 +885,7 @@ describe('ModelsSection', () => {
 
   it('edits an output cap per model and carries its text across a removal', async () => {
     const { mutate } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
+      mutate: vi.fn(() => Promise.resolve(remoteOk(wireNamespaces()[0]))),
     })
     fireEvent.click(screen.getByText(en.customized))
     expandRow(1)
@@ -902,15 +906,15 @@ describe('ModelsSection', () => {
 
     fireEvent.click(screen.getByText(en.apply))
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-deepseek',
-      ops: [{
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-deepseek',
+      [{
         op: 'set',
         path: ['models'],
         value: [{ ...DEFAULT_DEEPSEEK_MODELS[1], maxTokens: 64_000 }],
       }],
-      expectedRevision: 0,
-    })
+      0,
+    ])
   })
 
   it('settles a pasted id and refuses whitespace that would never match', async () => {
@@ -952,7 +956,7 @@ describe('ModelsSection', () => {
 
   it('can empty and reset the model override, then clear optional fields without dropping hidden data', async () => {
     const { mutate } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(ok(wireNamespaces()[0]))),
+      mutate: vi.fn(() => Promise.resolve(remoteOk(wireNamespaces()[0]))),
     })
     fireEvent.click(screen.getByText(en.customized))
     fireEvent.click(screen.getAllByLabelText(new RegExp(en.removeModel))[0] as HTMLElement)
@@ -969,9 +973,9 @@ describe('ModelsSection', () => {
     fireEvent.click(screen.getByText(en.apply))
 
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-deepseek',
-      ops: [{
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-deepseek',
+      [{
         op: 'set',
         path: ['models'],
         value: [
@@ -979,33 +983,33 @@ describe('ModelsSection', () => {
           DEFAULT_DEEPSEEK_MODELS[1],
         ],
       }],
-      expectedRevision: 0,
-    })
+      0,
+    ])
   })
 
   it('clears an inherited override with an unset op, never a whole-section replace', async () => {
     // A whole-section replace would clobber sibling overrides to clear one field.
-    const { replace, update, mutate } = await mountDeepSeekCard()
+    const { mutate } = await mountDeepSeekCard()
     fireEvent.click(screen.getByText(en.customized))
     const url = screen.getByLabelText<HTMLInputElement>(en.baseUrl)
     expect(url.value).toBe('https://base')
     fireEvent.change(url, { target: { value: '' } })
     fireEvent.click(screen.getByText(en.apply))
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
-    expect(replace).not.toHaveBeenCalled()
-    expect(update).not.toHaveBeenCalled()
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-deepseek',
-      ops: [{ op: 'unset', path: ['baseURL'] }],
-      expectedRevision: 0,
-    })
+    // This editor clears one field through an unset op so it cannot clobber
+    // sibling overrides with a whole-section replacement.
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-deepseek',
+      [{ op: 'unset', path: ['baseURL'] }],
+      0,
+    ])
   })
 
   it('pins the deepseek placeholder and clears typed input back to inherited', async () => {
     const { face } = scriptedFace()
     const bare: SettingsNamespaceView = {
       ns: 'llm-deepseek',
-      schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
+      schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as JsonValue,
       value: {},
       applies: 'live',
       secrets: [],
@@ -1033,12 +1037,12 @@ describe('ModelsSection', () => {
   })
 
   it('rejects an invalid draft before writing', async () => {
-    const { update } = await mountDeepSeekCard()
+    const { mutate } = await mountDeepSeekCard()
     fireEvent.click(screen.getByText(en.customized))
     fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'not-a-url' } })
     fireEvent.click(screen.getByText(en.apply))
     await screen.findByText(/baseURL/)
-    expect(update).not.toHaveBeenCalled()
+    expect(mutate).not.toHaveBeenCalled()
   })
 
   it('edits a pi-ai profile with the curated fields only', async () => {
@@ -1057,11 +1061,11 @@ describe('ModelsSection', () => {
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
     // Only the edited field travels: apiKeyEnv and headers were already stored
     // with these values, so no op restates them.
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-pi-ai',
-      ops: [{ op: 'set', path: ['providers', 'openai', 'baseURL'], value: 'https://proxy/v2' }],
-      expectedRevision: 0,
-    })
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-pi-ai',
+      [{ op: 'set', path: ['providers', 'openai', 'baseURL'], value: 'https://proxy/v2' }],
+      0,
+    ])
   })
 
   it('adds a dormant provider with a derived reference and stores its key', async () => {
@@ -1079,12 +1083,12 @@ describe('ModelsSection', () => {
     fireEvent.change(addKey, { target: { value: 'sk-ant' } })
     fireEvent.click(screen.getByText(en.apply))
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-pi-ai',
-      ops: [{ op: 'set', path: ['providers', 'anthropic', 'apiKeyEnv'], value: 'ANTHROPIC_API_KEY' }],
-      expectedRevision: 0,
-    })
-    await waitFor(() => { expect(set).toHaveBeenCalledWith({ ref: 'ANTHROPIC_API_KEY', value: 'sk-ant' }) })
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-pi-ai',
+      [{ op: 'set', path: ['providers', 'anthropic', 'apiKeyEnv'], value: 'ANTHROPIC_API_KEY' }],
+      0,
+    ])
+    await waitFor(() => { expect(set).toHaveBeenCalledWith('ANTHROPIC_API_KEY', 'sk-ant') })
   })
 
   it('keeps pi-ai provider-native authentication when no key is entered', async () => {
@@ -1093,11 +1097,11 @@ describe('ModelsSection', () => {
     await screen.findByLabelText(en.provider)
     fireEvent.click(screen.getByText(en.apply))
     await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-pi-ai',
-      ops: [{ op: 'set', path: ['providers', 'anthropic'], value: {} }],
-      expectedRevision: 0,
-    })
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-pi-ai',
+      [{ op: 'set', path: ['providers', 'anthropic'], value: {} }],
+      0,
+    ])
     expect(set).not.toHaveBeenCalled()
   })
 
@@ -1115,10 +1119,10 @@ describe('ModelsSection', () => {
       } },
       revision: 1,
     }
-    const mutate = vi.fn(() => Promise.resolve(ok(afterSettings)))
+    const mutate = vi.fn(() => Promise.resolve(remoteOk(afterSettings)))
     const set = vi.fn()
-      .mockResolvedValueOnce(fail('credential store unavailable', 'credential-rejected'))
-      .mockResolvedValueOnce(ok({}))
+      .mockResolvedValueOnce(remoteFail('credential store unavailable'))
+      .mockResolvedValueOnce(remoteOk(undefined))
     const { face, controller, mirror } = await mountSection({ mutate, set })
     fireEvent.click(screen.getByText(en.add))
     await screen.findByLabelText(en.provider)
@@ -1126,7 +1130,7 @@ describe('ModelsSection', () => {
     fireEvent.click(screen.getByText(en.apply))
     await screen.findByText('credential store unavailable')
     expect(mutate).toHaveBeenCalledOnce()
-    face.settings.describe.mockResolvedValue(ok({
+    face.settings.describe.mockResolvedValue(remoteOk({
       writable: true,
       hasDocument: false,
       namespaces: wireNamespaces().map(namespace => namespace.ns === 'llm-pi-ai' ? afterSettings : namespace),
@@ -1141,7 +1145,7 @@ describe('ModelsSection', () => {
     fireEvent.click(screen.getByText(en.apply))
     await waitFor(() => { expect(set).toHaveBeenCalledTimes(2) })
     expect(mutate).toHaveBeenCalledOnce()
-    expect(set).toHaveBeenLastCalledWith({ ref: 'ANTHROPIC_API_KEY', value: 'sk-ant' })
+    expect(set).toHaveBeenLastCalledWith('ANTHROPIC_API_KEY', 'sk-ant')
   })
 
   it('switches the add card target and degrades unknown or broken targets loudly', async () => {
@@ -1161,7 +1165,7 @@ describe('ModelsSection', () => {
 
   it('surfaces a rejected settings write and never stores the key after it', async () => {
     const { set } = await mountSection({
-      mutate: vi.fn(() => Promise.resolve(fail('llm-pi-ai: unknown pi-ai provider "bogus"'))),
+      mutate: vi.fn(() => Promise.resolve(remoteFail('llm-pi-ai: unknown pi-ai provider "bogus"', 'settings-rejected'))),
     })
     fireEvent.click(screen.getByText(en.add))
     await screen.findByLabelText(en.provider)
@@ -1202,7 +1206,7 @@ describe('ModelsSection', () => {
     // The stale-draft overwrite: two tabs open the same card, the other saves,
     // and this one must be refused rather than replay its opening snapshot.
     const { set } = await mountDeepSeekCard({
-      mutate: vi.fn(() => Promise.resolve(fail('changed since it was read', 'settings-conflict'))),
+      mutate: vi.fn(() => Promise.resolve(remoteFail('changed since it was read', 'settings-conflict'))),
     })
     fireEvent.click(screen.getByText(en.customized))
     fireEvent.change(screen.getByLabelText<HTMLInputElement>(en.baseUrl), { target: { value: 'https://mine' } })
@@ -1226,7 +1230,7 @@ describe('ModelsSection', () => {
 
   it('surfaces a shadowed credential write on the card', async () => {
     await mountFirstRun({
-      set: vi.fn(() => Promise.resolve(fail('credentials: DEEPSEEK_API_KEY is shadowed by the read-only environment', 'credential-rejected'))),
+      set: vi.fn(() => Promise.resolve(remoteFail('credentials: DEEPSEEK_API_KEY is shadowed by the read-only environment'))),
     })
     const key = screen.getByLabelText<HTMLInputElement>(en.keyInput)
     fireEvent.change(key, { target: { value: 'sk-live' } })
@@ -1237,11 +1241,11 @@ describe('ModelsSection', () => {
 
   it('locks the key input when the launch environment provides the credential', async () => {
     const { face } = await mountSection()
-    face.credentials.describe.mockImplementation((payload: { refs: string[] }) => Promise.resolve(ok({
-      credentials: Object.fromEntries(payload.refs.map(ref => [ref, {
+    face.credentials.describe.mockImplementation((refs: string[]) => Promise.resolve(remoteOk(
+      Object.fromEntries(refs.map(ref => [ref, {
         configured: ref === 'OPENAI_API_KEY', source: 'env', writable: false,
       }])),
-    })))
+    )))
     fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.editProvider) }))
     const editorKey = await screen.findByLabelText<HTMLInputElement>(en.keyInput)
     await waitFor(() => { expect(editorKey.placeholder).toBe(en.keyEnvLocked) })
@@ -1250,7 +1254,7 @@ describe('ModelsSection', () => {
 
   it('keeps a failed credential describe silent and the input usable', async () => {
     const { face, set } = await mountSection()
-    face.credentials.describe.mockImplementation(() => Promise.resolve(fail('down', 'internal')) as never)
+    face.credentials.describe.mockImplementation(() => Promise.resolve(remoteFail('down', 'internal')) as never)
     fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.editProvider) }))
     const editorKey = await screen.findByLabelText<HTMLInputElement>(en.keyInput)
     expect(editorKey.placeholder).toBe(en.keyPlaceholderNative)
@@ -1260,7 +1264,7 @@ describe('ModelsSection', () => {
   })
 
   it('requires confirmation before removing a user-added provider', async () => {
-    const { replace, mutate, unset } = await mountSection()
+    const { mutate, unset } = await mountSection()
     fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
     const dialog = screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })
     expect(dialog.textContent).toContain(openaiCopy(en.deleteDescriptionWithCredential))
@@ -1280,20 +1284,20 @@ describe('ModelsSection', () => {
     fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
     fireEvent.click(within(screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) }))
       .getByRole('button', { name: openaiCopy(en.deleteConfirm) }))
-    await waitFor(() => { expect(unset).toHaveBeenCalledWith({ ref: 'OPENAI_API_KEY' }) })
+    await waitFor(() => { expect(unset).toHaveBeenCalledWith('OPENAI_API_KEY') })
     await waitFor(() => { expect(mutate).toHaveBeenCalledTimes(1) })
     expect(unset.mock.invocationCallOrder[0]).toBeLessThan(mutate.mock.invocationCallOrder[0] as number)
     expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
-    expect(replace).not.toHaveBeenCalled()
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-pi-ai',
-      ops: [{ op: 'unset', path: ['providers', 'openai'] }],
-    })
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-pi-ai',
+      [{ op: 'unset', path: ['providers', 'openai'] }],
+      undefined,
+    ])
   })
 
   it('blocks duplicate deletion while the confirmed removal is pending', async () => {
-    let resolveRemoval!: (response: RpcResponse<SettingsNamespaceView>) => void
-    const mutate = vi.fn(() => new Promise<RpcResponse<SettingsNamespaceView>>((resolve) => {
+    let resolveRemoval!: (response: { ok: true; value: SettingsNamespaceView }) => void
+    const mutate = vi.fn(() => new Promise<{ ok: true; value: SettingsNamespaceView }>((resolve) => {
       resolveRemoval = resolve
     }))
     await mountSection({ mutate })
@@ -1309,7 +1313,7 @@ describe('ModelsSection', () => {
     fireEvent.click(within(dialog).getByRole('button', { name: en.close }))
     expect(screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBe(dialog)
     expect(mutate).toHaveBeenCalledOnce()
-    await act(async () => { resolveRemoval(ok(wireNamespaces()[2]!)) })
+    await act(async () => { resolveRemoval(remoteOk(wireNamespaces()[2]!)) })
     await waitFor(() => {
       expect(screen.queryByRole('dialog', { name: openaiCopy(en.deleteTitle) })).toBeNull()
     })
@@ -1336,7 +1340,7 @@ describe('ModelsSection', () => {
 
   it('shows the read-only notice and disables mutations for a read-only provider', async () => {
     const { face } = await mountSection()
-    face.settings.describe.mockImplementation(() => Promise.resolve(ok({
+    face.settings.describe.mockImplementation(() => Promise.resolve(remoteOk({
       writable: false,
       hasDocument: false,
       namespaces: wireNamespaces(),
@@ -1358,7 +1362,7 @@ describe('ModelsSection', () => {
   })
 
   it('toggles the row editor closed on a second edit click and on cancel', async () => {
-    const { update } = await mountSection()
+    const { mutate } = await mountSection()
     const edit = screen.getByRole('button', { name: openaiCopy(en.editProvider) })
     fireEvent.click(edit)
     await waitFor(() => { expect(screen.queryAllByLabelText(en.keyInput).length).toBe(1) })
@@ -1368,7 +1372,7 @@ describe('ModelsSection', () => {
     await waitFor(() => { expect(screen.queryAllByLabelText(en.keyInput).length).toBe(1) })
     fireEvent.click(screen.getByText(en.cancel))
     expect(screen.queryAllByLabelText(en.keyInput)).toHaveLength(0)
-    expect(update).not.toHaveBeenCalled()
+    expect(mutate).not.toHaveBeenCalled()
   })
 
   it('cancels the add card back to the add button', async () => {
@@ -1420,22 +1424,22 @@ describe('ModelsSection', () => {
   it('removes by unsetting the profile path, never by rebuilding the section', async () => {
     // The page only needs to name the profile path; rebuilding the section
     // would widen the write for no benefit.
-    const { face, mutate, replace, controller } = await mountSection()
+    const { face, mutate, controller } = await mountSection()
     await removeProviderProfile(
       face as unknown as Parameters<typeof removeProviderProfile>[0],
       controller,
       { settingsNs: 'llm-plain', settingsPath: ['ghost-profile'] },
     )
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-plain',
-      ops: [{ op: 'unset', path: ['ghost-profile'] }],
-    })
-    expect(replace).not.toHaveBeenCalled()
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-plain',
+      [{ op: 'unset', path: ['ghost-profile'] }],
+      undefined,
+    ])
   })
 
   it('keeps the snapshot untouched and reports the message when a removal write is refused', async () => {
     const { face, controller } = await mountSection({
-      mutate: vi.fn(() => Promise.resolve(fail('read-only'))),
+      mutate: vi.fn(() => Promise.resolve(remoteFail('read-only', 'settings-rejected'))),
     })
     const before = controller.store.getSnapshot().rows
     const failure = await removeProviderProfile(
@@ -1449,8 +1453,8 @@ describe('ModelsSection', () => {
 
   it('keeps a failed identified deletion recoverable in its confirmation dialog', async () => {
     const mutate = vi.fn()
-      .mockResolvedValueOnce(fail('the host refused'))
-      .mockResolvedValueOnce(ok(wireNamespaces()[2]!))
+      .mockResolvedValueOnce(remoteFail('the host refused', 'settings-rejected'))
+      .mockResolvedValueOnce(remoteOk(wireNamespaces()[2]!))
     const { unset } = await mountSection({ mutate })
     fireEvent.click(screen.getByRole('button', { name: openaiCopy(en.removeProvider) }))
     const dialog = screen.getByRole('dialog', { name: openaiCopy(en.deleteTitle) })
@@ -1478,15 +1482,16 @@ describe('ModelsSection', () => {
     fireEvent.click(within(dialog).getByRole('button', { name: providerCopy(en.deleteConfirm, target) }))
     await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
     expect(unset).not.toHaveBeenCalled()
-    expect(mutate.mock.calls[0]?.[0]).toEqual({
-      ns: 'llm-pi-ai',
-      ops: [{ op: 'unset', path: ['providers', 'zombie'] }],
-    })
+    expect(mutate.mock.calls[0]).toEqual([
+      'llm-pi-ai',
+      [{ op: 'unset', path: ['providers', 'zombie'] }],
+      undefined,
+    ])
   })
 
   it('does not remove provider settings when its managed credential removal is refused', async () => {
     const { face, controller, mutate } = await mountSection({
-      unset: vi.fn(() => Promise.resolve(fail('credential is read-only', 'credential-rejected'))),
+      unset: vi.fn(() => Promise.resolve(remoteFail('credential is read-only'))),
     })
     const failure = await removeProviderProfile(
       face as unknown as Parameters<typeof removeProviderProfile>[0],

+ 21 - 22
packages/client/ui-settings-models/tests/onboarding-dialog.client.spec.tsx

@@ -3,7 +3,7 @@
 import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import Schema from '@deepseek-ai/schemastery'
-import type { RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { JsonValue, RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-test-runtime'
 import { DeepSeekOnboardingDialog } from '../src/client/DeepSeekOnboardingDialog.tsx'
 import type { DeepSeekOnboardingDialogProps } from '../src/client/DeepSeekOnboardingDialog.tsx'
@@ -21,11 +21,12 @@ let nextRpc = 0
 function ok<T>(value: T): RpcResponse<T> {
   return { rpcId: `onboarding-${nextRpc++}` as never, result: { ok: true, value } }
 }
-function fail<T>(message: string): RpcResponse<T> {
-  return {
-    rpcId: `onboarding-${nextRpc++}` as never,
-    result: { ok: false, error: { code: 'internal', message, details: {} } },
-  }
+/** Credentials answers over the Remote carrier, which has no envelope. */
+function remoteOk<T>(value: T) {
+  return { ok: true as const, value }
+}
+function remoteFail(message: string) {
+  return { ok: false as const, error: { code: 'internal', message, details: {} } }
 }
 
 const DeepSeekConfig = Schema.object({
@@ -49,7 +50,7 @@ function deepSeekNamespace(apiKeyEnv: string | null): SettingsNamespaceView {
   const value = apiKeyEnv === null ? {} : { apiKeyEnv }
   return {
     ns: 'llm-deepseek',
-    schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as unknown,
+    schema: JSON.parse(JSON.stringify(DeepSeekConfig.toJSON())) as JsonValue,
     value,
     base: value,
     user: {},
@@ -81,12 +82,12 @@ function harness(options: {
   let fileConfigured = false
   const configured = options.configured ?? (() => fileConfigured)
   const apiKeyEnv = options.apiKeyEnv === undefined ? 'DEEPSEEK_API_KEY' : options.apiKeyEnv
-  const mutate = vi.fn(() => Promise.resolve(ok(deepSeekNamespace(apiKeyEnv))))
-  const set = vi.fn((_payload: { ref: string; value: string }) => {
+  const mutate = vi.fn(() => Promise.resolve(remoteOk(deepSeekNamespace(apiKeyEnv))))
+  const set = vi.fn((_ref: string, _value: string) => {
     if (options.setReject !== undefined) return Promise.reject(new Error(options.setReject))
-    if (options.setFailure !== undefined) return Promise.resolve(fail(options.setFailure))
+    if (options.setFailure !== undefined) return Promise.resolve(remoteFail(options.setFailure))
     fileConfigured = true
-    return Promise.resolve(ok({}))
+    return Promise.resolve(remoteOk(undefined))
   })
   const face = {
     llm: {
@@ -106,7 +107,7 @@ function harness(options: {
       },
     },
     settings: {
-      describe: () => Promise.resolve(ok({
+      describe: () => Promise.resolve(remoteOk({
         writable: options.settingsWritable ?? true,
         hasDocument: false,
         namespaces: options.settingsNamespace === false ? [] : [deepSeekNamespace(apiKeyEnv)],
@@ -115,18 +116,16 @@ function harness(options: {
     },
     credentials: {
       describe: () => options.describeFailure === undefined
-        ? Promise.resolve(ok({
-          credentials: {
-            DEEPSEEK_API_KEY: {
-              configured: configured(),
-              ...configured() && options.credential?.source !== undefined
-                ? { source: options.credential.source }
-                : {},
-              writable: options.credential?.writable ?? true,
-            },
+        ? Promise.resolve(remoteOk({
+          DEEPSEEK_API_KEY: {
+            configured: configured(),
+            ...configured() && options.credential?.source !== undefined
+              ? { source: options.credential.source }
+              : {},
+            writable: options.credential?.writable ?? true,
           },
         }))
-        : Promise.resolve(fail(options.describeFailure)),
+        : Promise.resolve(remoteFail(options.describeFailure)),
       set,
     },
   }

+ 39 - 29
packages/client/ui-settings-models/tests/provider-form.client.spec.tsx

@@ -4,7 +4,7 @@ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/re
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import Schema from '@deepseek-ai/schemastery'
 import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-test-runtime'
-import type { RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { JsonValue, RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import { ModelsSection, providerCopy } from '../src/client/ModelsSection.tsx'
 import type { ModelsSectionInjected, ModelsSectionProps } from '../src/client/ModelsSection.tsx'
 import { CustomProviderCard } from '../src/client/CustomProviderCard.tsx'
@@ -45,15 +45,22 @@ function ok<T>(value: T): RpcResponse<T> {
 function fail<T>(message: string, code: string): RpcResponse<T> {
   return { rpcId: `r-${nextRpc++}` as never, result: { ok: false, error: { code, message, details: {} } as never } }
 }
+/** Credentials answers over the Remote carrier, which has no envelope. */
+function remoteOk<T>(value: T) {
+  return { ok: true as const, value }
+}
+function remoteFail(message: string, code = 'credential-rejected') {
+  return { ok: false as const, error: { code, message, details: {} } }
+}
 
 function piAiNamespace(
-  providers: Record<string, unknown>,
-  userProviders: Record<string, unknown> = providers,
-  baseProviders: Record<string, unknown> = {},
+  providers: Record<string, JsonValue>,
+  userProviders: Record<string, JsonValue> = providers,
+  baseProviders: Record<string, JsonValue> = {},
 ): SettingsNamespaceView {
   return {
     ns: 'llm-pi-ai',
-    schema: JSON.parse(JSON.stringify(PiAiConfig.toJSON())) as unknown,
+    schema: JSON.parse(JSON.stringify(PiAiConfig.toJSON())) as JsonValue,
     // `value` is the effective section; `user` is only the layer this page
     // writes. They differ whenever a composition `base` supplies something.
     value: { providers },
@@ -66,11 +73,11 @@ function piAiNamespace(
 }
 
 function scriptedFace(options: {
-  providers?: Record<string, unknown>
+  providers?: Record<string, JsonValue>
   /** User layer, when it differs from the effective section. */
-  userProviders?: Record<string, unknown>
+  userProviders?: Record<string, JsonValue>
   /** Composition layer, for a route a `cordis.yml` pins rather than the page. */
-  baseProviders?: Record<string, unknown>
+  baseProviders?: Record<string, JsonValue>
   /** Routes the adapter reports as hand-declared; the rest come back as shipped. */
   declaredRoutes?: readonly string[]
   discover?: ReturnType<typeof vi.fn>
@@ -82,8 +89,8 @@ function scriptedFace(options: {
   }
   const namespace = piAiNamespace(providers, options.userProviders ?? providers, options.baseProviders ?? {})
   const discover = options.discover ?? vi.fn(() => Promise.resolve(ok({ models: [] })))
-  const mutate = options.mutate ?? vi.fn(() => Promise.resolve(ok(namespace)))
-  const set = options.set ?? vi.fn(() => Promise.resolve(ok({})))
+  const mutate = options.mutate ?? vi.fn(() => Promise.resolve(remoteOk(namespace)))
+  const set = options.set ?? vi.fn(() => Promise.resolve(remoteOk(undefined)))
   const face = {
     llm: {
       providers: vi.fn(() => Promise.resolve(ok({
@@ -100,15 +107,13 @@ function scriptedFace(options: {
       discoverModels: discover,
     },
     settings: {
-      describe: vi.fn(() => Promise.resolve(ok({ writable: true, namespaces: [namespace] }))),
-      update: vi.fn(),
-      replace: vi.fn(),
+      describe: vi.fn(() => Promise.resolve(remoteOk({ writable: true, namespaces: [namespace] }))),
       mutate,
     },
     credentials: {
-      describe: vi.fn((payload: { refs: string[] }) => Promise.resolve(ok({
-        credentials: Object.fromEntries(payload.refs.map(ref => [ref, { configured: false, writable: true }])),
-      }))),
+      describe: vi.fn((refs: string[]) => Promise.resolve(remoteOk(
+        Object.fromEntries(refs.map(ref => [ref, { configured: false, writable: true }])),
+      ))),
       set,
       unset: vi.fn(),
     },
@@ -132,11 +137,16 @@ function firstProbe(discover: ReturnType<typeof vi.fn>): unknown {
   return call
 }
 
-/** The first recorded settings write; fails the case when nothing was written. */
+/**
+ * The first recorded settings write, as one record. The Remote method takes
+ * three positional arguments; the cases read the write as a whole, so the
+ * regrouping lives here rather than in every assertion.
+ */
 function firstMutate(mutate: ReturnType<typeof vi.fn>): MutateCall {
-  const call = mutate.mock.calls[0]?.[0] as MutateCall | undefined
+  const call = mutate.mock.calls[0] as [string, MutateCall['ops'], number | undefined] | undefined
   if (call === undefined) throw new Error('no settings write was recorded')
-  return call
+  const [ns, ops, expectedRevision] = call
+  return { ns, ops, ...expectedRevision === undefined ? {} : { expectedRevision } }
 }
 
 async function mountSection(options: Parameters<typeof scriptedFace>[0] = {}) {
@@ -190,7 +200,7 @@ describe('protocolChoices', () => {
     const { namespace } = scriptedFace()
     expect(protocolChoices(namespace, settingsSchema)).toEqual(PROTOCOLS)
     expect(protocolChoices(undefined, settingsSchema)).toEqual([])
-    const plain = { ...namespace, schema: JSON.parse(JSON.stringify(Schema.object({}).toJSON())) as unknown }
+    const plain = { ...namespace, schema: JSON.parse(JSON.stringify(Schema.object({}).toJSON())) as JsonValue }
     expect(protocolChoices(plain, settingsSchema)).toEqual([])
     await Promise.resolve()
   })
@@ -734,7 +744,7 @@ describe('hand-declared providers', () => {
       // meanwhile makes this a conflict rather than an overwrite.
       expectedRevision: 7,
     })
-    expect(set).toHaveBeenCalledWith({ ref: 'ACME_GATEWAY_API_KEY', value: 'gw-key' })
+    expect(set).toHaveBeenCalledWith('ACME_GATEWAY_API_KEY', 'gw-key')
   })
 
   it('scopes each card to fields a provider can actually own', async () => {
@@ -901,8 +911,8 @@ describe('hand-declared providers', () => {
 
   it('retries only the key after the profile landed, and reports the provider on cancel', async () => {
     const set = vi.fn()
-      .mockResolvedValueOnce(fail('credential store is read-only', 'credential-rejected'))
-      .mockResolvedValueOnce(ok({}))
+      .mockResolvedValueOnce(remoteFail('credential store is read-only'))
+      .mockResolvedValueOnce(remoteOk(undefined))
     const { mutate, onClose } = mountCard({}, { set })
 
     fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
@@ -917,7 +927,7 @@ describe('hand-declared providers', () => {
     expect(onClose).not.toHaveBeenCalled()
     expect(mutate).toHaveBeenCalledTimes(1)
     // The key is stored trimmed, matching the editor.
-    expect(set).toHaveBeenNthCalledWith(1, { ref: 'ACME_API_KEY', value: 'gw-key' })
+    expect(set).toHaveBeenNthCalledWith(1, 'ACME_API_KEY', 'gw-key')
 
     // The provider exists now, so the fields describing it are settled and
     // only the key can still be corrected.
@@ -932,11 +942,11 @@ describe('hand-declared providers', () => {
     // first write superseded, so the Host would answer settings-conflict and
     // the key could never be stored from here at all.
     expect(mutate).toHaveBeenCalledTimes(1)
-    expect(set).toHaveBeenNthCalledWith(2, { ref: 'ACME_API_KEY', value: 'gw-key-2' })
+    expect(set).toHaveBeenNthCalledWith(2, 'ACME_API_KEY', 'gw-key-2')
   })
 
   it('reports the created provider when cancelled after its profile landed', async () => {
-    const set = vi.fn().mockResolvedValue(fail('nope', 'credential-rejected'))
+    const set = vi.fn().mockResolvedValue(remoteFail('nope'))
     const { onClose } = mountCard({}, { set })
 
     fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
@@ -1122,7 +1132,7 @@ describe('hand-declared providers', () => {
   })
 
   it('surfaces a refused write and a rejected transport without closing', async () => {
-    const refused = vi.fn(() => Promise.resolve(fail('read-only settings', 'settings-rejected')))
+    const refused = vi.fn(() => Promise.resolve(remoteFail('read-only settings', 'settings-rejected')))
     const { onClose } = mountCard({ api: { ...scriptedFace({ mutate: refused }).face } as never })
 
     fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
@@ -1150,7 +1160,7 @@ describe('hand-declared providers', () => {
   })
 
   it('reports a stored profile whose key write was refused', async () => {
-    const set = vi.fn(() => Promise.resolve(fail('credential is read-only', 'credential-rejected')))
+    const set = vi.fn(() => Promise.resolve(remoteFail('credential is read-only')))
     const { onClose } = mountCard({ api: { ...scriptedFace({ set }).face } as never })
 
     fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
@@ -1363,7 +1373,7 @@ describe('API key field', () => {
     fireEvent.click(screen.getByText(en.apply))
 
     await waitFor(() => { expect(set).toHaveBeenCalled() })
-    expect((set.mock.calls[0]?.[0] as { value: string }).value).toBe('sk-abc')
+    expect(set.mock.calls[0]?.[1]).toBe('sk-abc')
   })
 
   it('blocks the interrogation too, rather than spending a round trip on a refused key', async () => {

+ 2 - 2
packages/client/ui-settings-models/tests/readiness.client.spec.ts

@@ -1,10 +1,10 @@
 /** Pure first-run readiness projection over the shared Models join. */
 import { describe, expect, it } from 'vitest'
-import type { CredentialView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { CredentialInfo } from '@deepseek-ai/dsh-api-remotes/client'
 import type { ModelsSettingsState, ProviderRow } from '../src/client/store.ts'
 import { onboardingReadiness, providerUsable } from '../src/client/store.ts'
 
-const missingCredential: CredentialView = { configured: false, writable: true }
+const missingCredential: CredentialInfo = { configured: false, writable: true }
 
 function row(overrides: Partial<ProviderRow> = {}): ProviderRow {
   return {

+ 32 - 21
packages/client/ui-settings-models/tests/store.client.spec.ts

@@ -13,6 +13,17 @@ function fail<T>(message: string): RpcResponse<T> {
   return { rpcId: `r-${nextRpc++}` as never, result: { ok: false, error: { code: 'internal', message, details: {} } } }
 }
 
+/** Credentials answers over the Remote carrier, which has no envelope. */
+type RemoteAnswer<T> =
+  | { readonly ok: true; readonly value: T }
+  | { readonly ok: false; readonly error: { code: string; message: string; details: object } }
+function remoteOk<T>(value: T): RemoteAnswer<T> {
+  return { ok: true, value }
+}
+function remoteFail<T>(message: string): RemoteAnswer<T> {
+  return { ok: false, error: { code: 'internal', message, details: {} } }
+}
+
 const DIRECTORY = [
   { provider: 'deepseek-official', displayName: 'DeepSeek', settingsNs: 'llm-deepseek', settingsPath: [], active: true },
   { provider: 'openai', displayName: 'openai', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'openai'], active: true },
@@ -43,8 +54,8 @@ const NAMESPACES = [
 
 function api(overrides: {
   providers?: () => Promise<RpcResponse<{ providers: typeof DIRECTORY }>>
-  describeSettings?: () => Promise<RpcResponse<{ writable: boolean; namespaces: typeof NAMESPACES }>>
-  describeCredentials?: (refs: string[]) => Promise<RpcResponse<{ credentials: Record<string, unknown> }>>
+  describeSettings?: () => Promise<RemoteAnswer<{ writable: boolean; hasDocument: boolean; namespaces: typeof NAMESPACES }>>
+  describeCredentials?: (refs: readonly string[]) => Promise<RemoteAnswer<Record<string, unknown>>>
 } = {}) {
   const seenRefs: string[][] = []
   const face = {
@@ -53,19 +64,19 @@ function api(overrides: {
       models: () => Promise.resolve(ok({ groups: [], failures: [] })),
     },
     settings: {
-      describe: overrides.describeSettings ?? (() => Promise.resolve(ok({ writable: true, hasDocument: false, namespaces: NAMESPACES }))),
-      update: () => Promise.resolve(fail('unused')),
-      replace: () => Promise.resolve(fail('unused')),
+      describe: overrides.describeSettings
+        ?? (() => Promise.resolve(remoteOk({ writable: true, hasDocument: false, namespaces: NAMESPACES }))),
+      mutate: () => Promise.resolve(remoteFail('the store spec issues no writes')),
     },
     credentials: {
-      describe: (payload: { refs: string[] }) => {
-        seenRefs.push(payload.refs)
-        return (overrides.describeCredentials ?? (refs => Promise.resolve(ok({
-          credentials: Object.fromEntries(refs.map(ref => [ref, { configured: ref === 'OPENAI_API_KEY', writable: true }])),
-        }))))(payload.refs)
+      describe: (refs: readonly string[]) => {
+        seenRefs.push([...refs])
+        return (overrides.describeCredentials ?? (asked => Promise.resolve(remoteOk(
+          Object.fromEntries(asked.map(ref => [ref, { configured: ref === 'OPENAI_API_KEY', writable: true }])),
+        ))))(refs)
       },
-      set: () => Promise.resolve(ok({})),
-      unset: () => Promise.resolve(ok({})),
+      set: () => Promise.resolve(remoteOk(undefined)),
+      unset: () => Promise.resolve(remoteOk(undefined)),
     },
   }
   const wire = face as never
@@ -104,7 +115,7 @@ describe('ModelsSettingsStore', () => {
   })
 
   it('degrades the credential badge, not the page, when the credential domain fails', async () => {
-    const { face, mirror } = api({ describeCredentials: () => Promise.resolve(fail('no provider')) })
+    const { face, mirror } = api({ describeCredentials: () => Promise.resolve(remoteFail('no provider')) })
     const store = new ModelsSettingsStore(face, settingsSchema, mirror)
     await store.load()
     const state = store.store.getSnapshot()
@@ -173,7 +184,7 @@ describe('ModelsSettingsStore', () => {
 describe('edge joins', () => {
   it('treats a non-object profile as having no credential reference', async () => {
     const { face, mirror } = api({
-      describeSettings: () => Promise.resolve(ok({
+      describeSettings: () => Promise.resolve(remoteOk({
         writable: true,
         hasDocument: false,
         namespaces: [{
@@ -200,7 +211,7 @@ describe('edge joins', () => {
 
   it('describes the derived reference for a row whose profile names none', async () => {
     const { face, mirror, seenRefs } = api({
-      describeSettings: () => Promise.resolve(ok({
+      describeSettings: () => Promise.resolve(remoteOk({
         writable: true,
         hasDocument: false,
         namespaces: [{ ns: 'llm-pi-ai', schema: {}, value: { providers: {} }, applies: 'live' as const, secrets: [], revision: 0 }] as never,
@@ -210,9 +221,9 @@ describe('edge joins', () => {
           { provider: 'anthropic', displayName: 'anthropic', settingsNs: 'llm-pi-ai', settingsPath: ['providers', 'anthropic'], active: false },
         ] as never,
       })),
-      describeCredentials: refs => Promise.resolve(ok({
-        credentials: Object.fromEntries(refs.map(ref => [ref, { configured: true, writable: true }])),
-      })),
+      describeCredentials: refs => Promise.resolve(remoteOk(
+        Object.fromEntries(refs.map(ref => [ref, { configured: true, writable: true }])),
+      )),
     })
     const store = new ModelsSettingsStore(face, settingsSchema, mirror)
     await store.load()
@@ -226,7 +237,7 @@ describe('edge joins', () => {
   })
 
   it('surfaces a settings describe failure', async () => {
-    const { face, mirror } = api({ describeSettings: () => Promise.resolve(fail('settings down')) })
+    const { face, mirror } = api({ describeSettings: () => Promise.resolve(remoteFail('settings down')) })
     const store = new ModelsSettingsStore(face, settingsSchema, mirror)
     await store.load()
     expect(store.store.getSnapshot()).toMatchObject({ status: 'error', error: 'settings down' })
@@ -252,8 +263,8 @@ describe('edge joins', () => {
       describeSettings: () => {
         settingsCall += 1
         return Promise.resolve(settingsCall === 1
-          ? ok({ writable: true, hasDocument: false, namespaces: NAMESPACES })
-          : fail('settings refresh down'))
+          ? remoteOk({ writable: true, hasDocument: false, namespaces: NAMESPACES })
+          : remoteFail('settings refresh down'))
       },
     })
     const store = new ModelsSettingsStore(face, settingsSchema, mirror)

+ 5 - 4
packages/client/ui-settings-models/tests/welcome-notice.client.spec.tsx

@@ -29,8 +29,9 @@ afterEach(() => {
   document.getElementById('root')?.remove()
 })
 
-function response<T>(value: T) {
-  return { rpcId: 'welcome-rpc' as never, result: { ok: true as const, value } }
+/** The settings namespace answers over the Remote carrier, which has no envelope. */
+function remoteAnswer<T>(value: T) {
+  return { ok: true as const, value }
 }
 
 function welcomeView(value: unknown, revision = 0) {
@@ -53,7 +54,7 @@ const useSessionPendingInteraction: WelcomeNoticeProps['useSessionPendingInterac
 function mount(
   version?: string,
   mutateImpl: () => Promise<unknown> = () =>
-    Promise.resolve(response(welcomeView({ [WELCOME_NOTICE_ACK_FIELD]: WELCOME_NOTICE_VERSION }, 1))),
+    Promise.resolve(remoteAnswer(welcomeView({ [WELCOME_NOTICE_ACK_FIELD]: WELCOME_NOTICE_VERSION }, 1))),
 ) {
   const appRoot = document.createElement('div')
   appRoot.id = 'root'
@@ -61,7 +62,7 @@ function mount(
   const mutate = vi.fn(mutateImpl)
   const api = {
     settings: {
-      describe: () => Promise.resolve(response({
+      describe: () => Promise.resolve(remoteAnswer({
         writable: true,
         hasDocument: false,
         namespaces: [welcomeView(version === undefined ? {} : { [WELCOME_NOTICE_ACK_FIELD]: version })],

+ 8 - 9
packages/client/ui-settings-models/tests/welcome-store.client.spec.ts

@@ -1,5 +1,4 @@
 import { describe, expect, it, vi } from 'vitest'
-import type { RpcResponse } from '@deepseek-ai/dsh-api-remotes/client'
 import { Context } from '@deepseek-ai/cordis'
 import { SettingsSchemaService } from '@deepseek-ai/dsh-client-ui-settings/src/client/schema.ts'
 import { SettingsDescribeMirror } from '@deepseek-ai/dsh-client-ui-settings/src/client/settings-mirror.ts'
@@ -11,9 +10,9 @@ import {
 
 const schemaService = new SettingsSchemaService(new Context())
 
-let rpc = 0
-function ok<T>(value: T): RpcResponse<T> {
-  return { rpcId: `welcome-${rpc++}` as never, result: { ok: true, value } }
+/** The settings namespace answers over the Remote carrier, which has no envelope. */
+function ok<T>(value: T) {
+  return { ok: true as const, value }
 }
 
 function namespace(value: unknown = {}, revision = 0) {
@@ -91,11 +90,11 @@ describe('WelcomeNoticeStore', () => {
     await mirror.load()
     await controller.load()
     await expect(controller.acknowledge()).resolves.toBe(true)
-    expect(mutate).toHaveBeenCalledWith({
-      ns: WELCOME_NOTICE_SETTINGS_NAMESPACE,
-      ops: [{ op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: WELCOME_NOTICE_VERSION }],
-      expectedRevision: 3,
-    })
+    expect(mutate).toHaveBeenCalledWith(
+      WELCOME_NOTICE_SETTINGS_NAMESPACE,
+      [{ op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: WELCOME_NOTICE_VERSION }],
+      3,
+    )
     expect(controller.store.getSnapshot()).toMatchObject({ status: 'ready', acknowledged: true })
     // The write answer folded into the mirror; no re-read followed.
     expect(describeCall).toHaveBeenCalledTimes(1)

+ 0 - 3
packages/client/ui-settings-plugins/package.json

@@ -32,7 +32,6 @@
   "dsh": {
     "client": {
       "inject": [
-        "@deepseek-ai/dsh-client-connection",
         "@deepseek-ai/dsh-client-locale",
         "@deepseek-ai/dsh-client-ui-settings",
         "@deepseek-ai/dsh-api-remotes"
@@ -48,7 +47,6 @@
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
-    "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-ui-settings": "workspace:^",
     "@deepseek-ai/dsh-invariants": "workspace:^",
@@ -57,7 +55,6 @@
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
     "@deepseek-ai/dsh-api-remotes": "workspace:^",
-    "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-locale": "workspace:^",
     "@deepseek-ai/dsh-client-store": "workspace:^",
     "@deepseek-ai/dsh-client-test-runtime": "workspace:^",

+ 3 - 4
packages/client/ui-settings-plugins/src/client/index.ts

@@ -9,7 +9,6 @@
  * settings scope, which keeps them unaware of one another and of other tabs.
  */
 
-import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
 // Type-only: pulls the locale plugin's Context merge (ctx.locale).
 import type {} from '@deepseek-ai/dsh-client-locale/client'
 // Type-only: the settings shell's SlotMap merge (the 'settings.section' entry)
@@ -50,20 +49,20 @@ export type { WebSearchCardFace, WebSearchCardState } from './web-search-card-co
 const NS = 'settings.plugins'
 
 /** Required services (cordis fiber inject). */
-export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope']
+export const inject = ['slots', 'locale', 'remote', 'remote.credentials', 'settingsScope']
 
 /**
  * Mount the plugin configuration section and the cards this package ships.
  * @param ctx - the browser plugin context.
  */
 export function apply(ctx: ClientContext): void {
-  const { api } = ctx.get('connection') as ConnectionHandle
   const t = ctx.locale.bind(NS)
   ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-plugins: section dictionaries')
 
   const bash = new BashCardController(ctx.settingsScope.bind({ namespace: SHELL_NS }))
   const agentLoop = new AgentLoopCardController(ctx.settingsScope.bind({ namespace: AGENT_LOOP_NS }))
-  const webSearch = new WebSearchCardController(ctx.settingsScope.bind({ namespace: WEB_SEARCH_NS }), api)
+  const webSearch = new WebSearchCardController(
+    ctx.settingsScope.bind({ namespace: WEB_SEARCH_NS }), ctx.remote.credentials)
 
   // The credential a card reports is not part of any settings section, so its
   // scope publishes nothing when one is written. This is the only signal that

+ 12 - 9
packages/client/ui-settings-plugins/src/client/web-search-card-controller.ts

@@ -9,7 +9,7 @@
  * covers everything the card shows.
  */
 
-import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client'
+import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client'
 import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
 import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client'
 import {
@@ -39,13 +39,16 @@ export interface WebSearchSettings {
   maxUses?: number
 }
 
+/** The credentials Remote methods this card reads and writes through. */
+export type WebSearchCredentials = Pick<ClientRemote['credentials'], 'describe' | 'set'>
+
 /** What the credentials domain last reported, and for which reference. */
 interface CredentialState {
   /** Reference this answer describes; a stale response for another one is dropped. */
   ref: string
   /** Whether any layer supplies a value for it. */
   configured: boolean
-  /** Whether `credentials.set` can affect it; false disables the control. */
+  /** Whether `credentials/set` can affect it; false disables the control. */
   writable: boolean
 }
 
@@ -79,11 +82,11 @@ export class WebSearchCardController {
 
   /**
    * @param scope - the bound settings scope for the `web-search-deepseek` namespace.
-   * @param api - wire face used for the credential the section references.
+   * @param credentials - Remote face used for the credential the section references.
    */
   constructor(
     private readonly scope: SettingsScope<WebSearchSettings>,
-    private readonly api: Pick<IApiClient, 'credentials'>,
+    private readonly credentials: WebSearchCredentials,
   ) {
     this.form = new CardForm(
       scope,
@@ -122,16 +125,16 @@ export class WebSearchCardController {
       this.credential = { ref, configured: false, writable: true }
       this.store.set(this.projection())
     }
-    let response: Awaited<ReturnType<IApiClient['credentials']['describe']>>
+    let response: Awaited<ReturnType<WebSearchCredentials['describe']>>
     try {
-      response = await this.api.credentials.describe({ refs: [ref] })
+      response = await this.credentials.describe([ref])
     } catch (_credentialReadFailure) {
       // The card stays usable without this: the key control simply reports the
       // last state it knew, and a write still reaches the Host.
       return
     }
-    if (!response.result.ok || ref !== refOf(this.scope.getSnapshot())) return
-    const view = response.result.value.credentials[ref]
+    if (!response.ok || ref !== refOf(this.scope.getSnapshot())) return
+    const view = response.value[ref]
     const next: CredentialState = {
       ref,
       configured: view?.configured ?? false,
@@ -172,7 +175,7 @@ export class WebSearchCardController {
    */
   private async writeKey(value: string): Promise<boolean> {
     try {
-      await this.api.credentials.set({ ref: refOf(this.scope.getSnapshot()), value })
+      await this.credentials.set(refOf(this.scope.getSnapshot()), value)
     } catch (_credentialWriteFailure) {
       // Refusals surface through the re-read below: the Host is the only
       // authority on whether the key now exists.

+ 15 - 21
packages/client/ui-settings-plugins/tests/apply.client.spec.ts

@@ -26,30 +26,24 @@ async function bench(served?: string[]) {
   const locale = new LocaleRuntime(ctx)
   locale.setLocale('zh')
   ctx.provide('locale', locale)
-  const describeCredentials = vi.fn(() => Promise.resolve({ rpcId: 'c', result: { ok: false, error: {} } }))
+  const describeCredentials = vi.fn(() => Promise.resolve({ ok: false, error: { code: 'internal', message: 'no provider', details: {} } }))
   const describeSettings = vi.fn(() => Promise.resolve(served === undefined
-    ? { rpcId: 's', result: { ok: false, error: {} } }
+    ? { ok: false, error: { code: 'internal', message: 'no provider', details: {} } }
     : {
-      rpcId: 's',
-      result: {
-        ok: true,
-        value: {
-          writable: true,
-          hasDocument: true,
-          namespaces: served.map(ns => ({
-            ns, schema: {}, value: {}, applies: 'live', secrets: [], revision: 0,
-          })),
-        },
+      ok: true,
+      value: {
+        writable: true,
+        hasDocument: true,
+        namespaces: served.map(ns => ({
+          ns, schema: {}, value: {}, applies: 'live', secrets: [], revision: 0,
+        })),
       },
     }))
-  const remote = new TestRemote(ctx)
-  ctx.provide('connection', {
-    isLoopback: true,
-    api: {
-      settings: { describe: describeSettings },
-      credentials: { describe: describeCredentials },
-    },
-  } as never)
+  const remote = new TestRemote(ctx, {
+    credentials: { describe: describeCredentials, set: vi.fn() },
+    settings: { describe: describeSettings },
+  })
+  ctx.provide('connection', { isLoopback: true, api: {} } as never)
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   return { ctx, slots: ctx.get('slots') as SlotRegistry, describeCredentials, describeSettings, remote }
 }
@@ -63,7 +57,7 @@ function declareRoot(slots: SlotRegistry): () => void {
 
 describe('ui-settings-plugins apply', () => {
   it('declares the services it uses', () => {
-    expect(inject).toEqual(['slots', 'locale', 'connection', 'remote', 'settingsScope'])
+    expect(inject).toEqual(['slots', 'locale', 'remote', 'remote.credentials', 'settingsScope'])
   })
 
   it('registers one Plugins section and declares the tab and card slots', async () => {

+ 21 - 24
packages/client/ui-settings-plugins/tests/stores.client.spec.ts

@@ -30,11 +30,11 @@ function acceptWrites<T>(host: StubSettingsScope<T>): void {
 
 function credentialsApi(configured: boolean) {
   const describe = vi.fn(() => Promise.resolve({
-    rpcId: 'c-1' as never,
-    result: { ok: true as const, value: { credentials: { DEEPSEEK_API_KEY: { configured, writable: true } } } },
+    ok: true as const,
+    value: { DEEPSEEK_API_KEY: { configured, writable: true } },
   }))
-  const set = vi.fn(() => Promise.resolve({ rpcId: 'c-2' as never, result: { ok: true as const, value: {} } }))
-  return { api: { credentials: { describe, set } } as never, describe, set }
+  const set = vi.fn(() => Promise.resolve({ ok: true as const, value: undefined }))
+  return { api: { describe, set } as never, describe, set }
 }
 
 describe('CardForm', () => {
@@ -412,13 +412,13 @@ describe('WebSearchCardController', () => {
     expect(credentials.set).not.toHaveBeenCalled()
 
     credentials.describe.mockImplementation(() => Promise.resolve({
-      rpcId: 'c-1' as never,
-      result: { ok: true as const, value: { credentials: { DEEPSEEK_API_KEY: { configured: true, writable: true } } } },
+      ok: true as const,
+      value: { DEEPSEEK_API_KEY: { configured: true, writable: true } },
     }))
     face.save()
     await vi.waitFor(() => { expect(credentials.set).toHaveBeenCalled() })
 
-    expect(credentials.set).toHaveBeenCalledWith({ ref: 'DEEPSEEK_API_KEY', value: 'ds-secret' })
+    expect(credentials.set).toHaveBeenCalledWith('DEEPSEEK_API_KEY', 'ds-secret')
     expect(host.set).not.toHaveBeenCalled()
     await vi.waitFor(() => {
       expect(face.hooks.webSearchCard.getSnapshot()).toMatchObject({ dirty: false, apiKeyConfigured: true })
@@ -454,8 +454,8 @@ describe('WebSearchCardController', () => {
 
     // A key written on another surface reaches this card only through this signal.
     credentials.describe.mockImplementation(() => Promise.resolve({
-      rpcId: 'c-1' as never,
-      result: { ok: true as const, value: { credentials: { DEEPSEEK_API_KEY: { configured: true, writable: true } } } },
+      ok: true as const,
+      value: { DEEPSEEK_API_KEY: { configured: true, writable: true } },
     }))
     controller.refreshCredential('DEEPSEEK_API_KEY')
 
@@ -475,7 +475,7 @@ describe('WebSearchCardController', () => {
     face.save()
     await vi.waitFor(() => { expect(credentials.set).toHaveBeenCalled() })
 
-    expect(credentials.set).toHaveBeenCalledWith({ ref: 'SEARCH_KEY', value: 'ds-secret' })
+    expect(credentials.set).toHaveBeenCalledWith('SEARCH_KEY', 'ds-secret')
   })
 
   it('reports a key the Host did not store as a failed save', async () => {
@@ -497,7 +497,7 @@ describe('WebSearchCardController', () => {
     const host = stubSettingsScope<WebSearchSettings>()
     const describe = vi.fn(() => Promise.reject(new Error('offline')))
     const set = vi.fn(() => Promise.reject(new Error('offline')))
-    const controller = new WebSearchCardController(host.scope, { credentials: { describe, set } } as never)
+    const controller = new WebSearchCardController(host.scope, { describe, set })
     const face = controller.inject()
     await vi.waitFor(() => { expect(describe).toHaveBeenCalled() })
 
@@ -516,10 +516,10 @@ describe('WebSearchCardController', () => {
   it('ignores a credential read the Host refused', async () => {
     const host = stubSettingsScope<WebSearchSettings>()
     const describe = vi.fn(() => Promise.resolve({
-      rpcId: 'c-1' as never,
-      result: { ok: false as const, error: { code: 'credentials-unavailable', message: 'no provider' } },
+      ok: false as const,
+      error: { code: 'internal', message: 'no credential provider', details: {} },
     }))
-    const controller = new WebSearchCardController(host.scope, { credentials: { describe, set: vi.fn() } } as never)
+    const controller = new WebSearchCardController(host.scope, { describe, set: vi.fn() })
     await vi.waitFor(() => { expect(describe).toHaveBeenCalled() })
 
     expect(controller.inject().hooks.webSearchCard.getSnapshot().apiKeyConfigured).toBe(false)
@@ -546,16 +546,13 @@ describe('WebSearchCardController', () => {
 describe('ConfigurablePluginsTabController', () => {
   function settingsApi(namespaces: string[]) {
     const describe = vi.fn(() => Promise.resolve({
-      rpcId: 's-1' as never,
-      result: {
-        ok: true as const,
-        value: {
-          writable: true,
-          hasDocument: true,
-          namespaces: namespaces.map(ns => ({
-            ns, schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0,
-          })),
-        },
+      ok: true as const,
+      value: {
+        writable: true,
+        hasDocument: true,
+        namespaces: namespaces.map(ns => ({
+          ns, schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0,
+        })),
       },
     }))
     return { mirror: new SettingsDescribeMirror({ settings: { describe } } as never), describe }

+ 9 - 7
packages/client/ui-settings/src/client/index.ts

@@ -32,13 +32,15 @@ export type { SettingsScopeController, SettingsScopeBinder } from './settings-sc
 export type { SettingsScope, SettingsScopeSnapshot, SettingsScopeSpec } from './settings-contract.ts'
 export type { SettingsSchemaService } from './schema.ts'
 export type { SchemaNode } from './schema.ts'
-export type { SettingsDescribeFace, SettingsDescribeView, SettingsMirrorSnapshot } from './settings-mirror.ts'
+export type {
+  SettingsDescribeFace, SettingsDescribeView, SettingsMirrorSnapshot, SettingsRemote, SettingsWireFace,
+} from './settings-mirror.ts'
 
 /**
  * Required services: the wire handle for the mirror's reads and the forwarded
  * settings invalidation the mirror refreshes on.
  */
-export const inject = ['connection', 'remote']
+export const inject = ['connection', 'remote', 'remote.settings']
 
 /**
  * Provide the settings-namespace scope service over one shared describe
@@ -52,10 +54,10 @@ export const inject = ['connection', 'remote']
 export function apply(ctx: Context): void {
   const schema = new SettingsSchemaService(ctx)
   const connection = ctx.get('connection') as ConnectionHandle
-  const mirror = new SettingsDescribeMirror(
-    connection.api,
-    connection.isLoopback ? 'host' : 'memory',
-  )
+  // Captured once here, where `remote.settings` is declared in this plugin's
+  // own `inject`; the binder hands the same face to every scope it binds.
+  const wire = { settings: ctx.remote.settings }
+  const mirror = new SettingsDescribeMirror(wire, connection.isLoopback ? 'host' : 'memory')
   ctx.effect(() => {
     const disposers = [
       ctx.remote.$on('settings/document-updated', () => { void mirror.load() }),
@@ -68,5 +70,5 @@ export function apply(ctx: Context): void {
     void mirror.ensure()
     return () => { for (const dispose of disposers) dispose() }
   }, 'ui-settings: describe mirror invalidations')
-  new SettingsScopeBinder(ctx, { mirror, schema })
+  new SettingsScopeBinder(ctx, { mirror, schema, wire })
 }

+ 20 - 6
packages/client/ui-settings/src/client/settings-mirror.ts

@@ -9,10 +9,24 @@
  * through {@link SettingsDescribeMirror.acceptView}.
  */
 
-import type { IApiClient, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { ClientRemote, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
 
-type SettingsFace = Pick<IApiClient, 'settings'>
+/**
+ * The settings Remote methods browser configuration surfaces may reach: the
+ * redacted read plus merge, replacement, and path-addressed writes.
+ * Named once here so the consumers share one face instead of each re-deriving
+ * it from the namespace.
+ */
+export type SettingsRemote = Pick<ClientRemote['settings'], 'describe' | 'update' | 'replace' | 'mutate'>
+
+/** Wire face carrying the settings Remote namespace. */
+export interface SettingsWireFace {
+  /** The settings Remote namespace. */
+  settings: SettingsRemote
+}
+
+type SettingsFace = SettingsWireFace
 
 /** The full `settings.describe` answer the mirror serves. */
 export interface SettingsDescribeView {
@@ -180,10 +194,10 @@ export class SettingsDescribeMirror implements SettingsDescribeFace {
         const generation = ++this.generation
         let outcome: { view: SettingsDescribeView } | { failure: string }
         try {
-          const response = await this.api.settings.describe({})
-          outcome = response.result.ok
-            ? { view: response.result.value }
-            : { failure: response.result.error.message }
+          const response = await this.api.settings.describe()
+          outcome = response.ok
+            ? { view: response.value }
+            : { failure: response.error.message }
         } catch (error) {
           outcome = { failure: error instanceof Error ? error.message : String(error) }
         }

+ 21 - 15
packages/client/ui-settings/src/client/settings-scope.ts

@@ -9,7 +9,7 @@
 import { Service } from '@deepseek-ai/cordis'
 import type { Context } from '@deepseek-ai/cordis'
 import type {
-  ConnectionHandle, IApiClient, SettingsNamespaceView, SettingsPathOpView,
+  ConnectionHandle, JsonValue, SettingsNamespaceView, SettingsPathOpView,
 } from '@deepseek-ai/dsh-api-remotes/client'
 import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-store'
 // Type-only, and deliberately NOT `@deepseek-ai/dsh-api-remotes/client`: this
@@ -30,9 +30,9 @@ import type {} from '@deepseek-ai/dsh-api-remotes/types'
 import type {} from '@deepseek-ai/dsh-settings/types'
 import type { SettingsSchemaService } from './schema.ts'
 import type { SettingsScope, SettingsScopeSnapshot, SettingsScopeSpec } from './settings-contract.ts'
-import { SettingsDescribeMirror, type SettingsDescribeFace } from './settings-mirror.ts'
+import { SettingsDescribeMirror, type SettingsDescribeFace, type SettingsWireFace } from './settings-mirror.ts'
 
-type SettingsFace = Pick<IApiClient, 'settings'>
+type SettingsFace = SettingsWireFace
 
 /**
  * One namespace's derived view over the shared describe mirror, plus that
@@ -104,7 +104,7 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
    * @returns settlement after the write and any latest-write recovery read.
    */
   set(field: string, value: unknown): Promise<void> {
-    return this.write({ op: 'set', path: [field], value })
+    return this.write({ op: 'set', path: [field], value: value as JsonValue })
   }
 
   /**
@@ -123,25 +123,21 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
       const revision = this.pendingRevision ?? this.getSnapshot().revision
       let response: Awaited<ReturnType<SettingsFace['settings']['mutate']>>
       try {
-        response = await this.api.settings.mutate({
-          ns: this.spec.namespace,
-          ops: [op],
-          ...(revision === undefined ? {} : { expectedRevision: revision }),
-        })
+        response = await this.api.settings.mutate(this.spec.namespace, [op], revision)
       } catch (_settingsWriteFailure) {
         await this.recover(generation)
         return
       }
-      if (!response.result.ok) {
+      if (!response.ok) {
         await this.recover(generation)
         return
       }
       if (this.disposed) return
       if (generation === this.writeGeneration) {
         this.pendingRevision = undefined
-        this.mirror.acceptView(response.result.value)
+        this.mirror.acceptView(response.value)
       } else {
-        this.pendingRevision = response.result.value.revision
+        this.pendingRevision = response.value.revision
       }
     })
   }
@@ -235,16 +231,26 @@ declare module '@deepseek-ai/cordis' {
 export class SettingsScopeBinder extends Service {
   private readonly mirror: SettingsDescribeMirror
   private readonly schema: SettingsSchemaService
+  private readonly wire: SettingsWireFace
 
   /**
    * @param ctx - the providing plugin's context.
    * @param config - the shared describe mirror every bound scope derives from,
-   * plus the settings-owned schema operations.
+   * the settings-owned schema operations, and the settings Remote namespace the
+   * bound scopes write through. The namespace is captured here rather than read
+   * inside {@link bind}, because a Service reads `ctx` as its *consumer's*
+   * fiber: reading it there would make every caller declare `remote.settings`
+   * in its own `inject`.
    */
-  constructor(ctx: Context, config: { mirror: SettingsDescribeMirror; schema: SettingsSchemaService }) {
+  constructor(ctx: Context, config: {
+    mirror: SettingsDescribeMirror
+    schema: SettingsSchemaService
+    wire: SettingsWireFace
+  }) {
     super(ctx, 'settingsScope')
     this.mirror = config.mirror
     this.schema = config.schema
+    this.wire = config.wire
   }
 
   /**
@@ -272,7 +278,7 @@ export class SettingsScopeBinder extends Service {
     const ctx = this.ctx
     const connection = ctx.get('connection') as ConnectionHandle
     const controller = new SettingsScopeController<T>(
-      connection.api,
+      this.wire,
       spec,
       this.mirror,
       connection.isLoopback ? 'host' : 'memory',

+ 3 - 7
packages/client/ui-settings/tests/plugin.client.spec.ts

@@ -7,15 +7,11 @@ import { SettingsScopeBinder } from '../src/client/settings-scope.ts'
 
 function bench() {
   const describeCall = vi.fn().mockResolvedValue({
-    rpcId: 'plugin-bench' as never,
-    result: { ok: true, value: { writable: true, hasDocument: true, namespaces: [] } },
+    ok: true, value: { writable: true, hasDocument: true, namespaces: [] },
   })
   const ctx = new Context()
-  ctx.provide('connection', {
-    api: { settings: { describe: describeCall } },
-    isLoopback: true,
-  } as never)
-  const remote = new TestRemote(ctx)
+  ctx.provide('connection', { api: {}, isLoopback: true } as never)
+  const remote = new TestRemote(ctx, { settings: { describe: describeCall } })
   return { ctx, describeCall, remote, fiber: ctx.plugin({ inject: [...inject], apply }) }
 }
 

+ 14 - 17
packages/client/ui-settings/tests/settings-mirror.client.spec.ts

@@ -1,28 +1,25 @@
 import { describe, expect, it, vi } from 'vitest'
-import type { RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import { SettingsDescribeMirror, type SettingsDescribeView } from '../src/client/settings-mirror.ts'
 
-let rpc = 0
+/** What a Remote call answers with: no carrier envelope, and a free-form failure code. */
+type Answer<T> =
+  | { ok: true; value: T }
+  | { ok: false; error: { code: string; message: string; details: object } }
 
-function ok<T>(value: T): RpcResponse<T> {
-  return { rpcId: `mirror-${rpc++}` as never, result: { ok: true, value } }
+function ok<T>(value: T): Answer<T> {
+  return { ok: true, value }
 }
 
-function rejected<T>(message: string): RpcResponse<T> {
-  return {
-    rpcId: `mirror-${rpc++}` as never,
-    result: {
-      ok: false,
-      error: { code: 'settings-rejected', message, details: { ns: 'theme' } },
-    },
-  }
+function rejected<T>(message: string): Answer<T> {
+  return { ok: false, error: { code: 'settings-rejected', message, details: { ns: 'theme' } } }
 }
 
 function view(ns: string, revision = 0): SettingsNamespaceView {
   return { ns, schema: {}, value: { field: ns }, applies: 'live', secrets: [], revision }
 }
 
-function described(namespaces: SettingsNamespaceView[]): RpcResponse<SettingsDescribeView> {
+function described(namespaces: SettingsNamespaceView[]): Answer<SettingsDescribeView> {
   return ok({ writable: true, hasDocument: true, namespaces })
 }
 
@@ -34,7 +31,7 @@ function deferred<T>() {
 
 describe('SettingsDescribeMirror', () => {
   it('folds loads before the wire read into it, and mid-flight loads into one rerun', async () => {
-    const gate = deferred<RpcResponse<SettingsDescribeView>>()
+    const gate = deferred<Answer<SettingsDescribeView>>()
     const describeCall = vi.fn()
       .mockReturnValueOnce(gate.promise)
       .mockResolvedValue(described([view('theme', 1)]))
@@ -145,7 +142,7 @@ describe('SettingsDescribeMirror', () => {
   })
 
   it('starts no second run for a load issued inside the loading publish', async () => {
-    const gate = deferred<RpcResponse<SettingsDescribeView>>()
+    const gate = deferred<Answer<SettingsDescribeView>>()
     const describeCall = vi.fn().mockReturnValue(gate.promise)
     const mirror = new SettingsDescribeMirror({ settings: { describe: describeCall } } as never)
     let reentered = false
@@ -181,7 +178,7 @@ describe('SettingsDescribeMirror', () => {
   })
 
   it('re-reads after a folded write invalidates an in-flight document', async () => {
-    const slow = deferred<RpcResponse<SettingsDescribeView>>()
+    const slow = deferred<Answer<SettingsDescribeView>>()
     const describeCall = vi.fn()
       .mockResolvedValueOnce(described([view('theme', 4), view('locale', 1)]))
       .mockReturnValueOnce(slow.promise)
@@ -200,7 +197,7 @@ describe('SettingsDescribeMirror', () => {
   })
 
   it('re-reads after a pre-answer write invalidates the in-flight document', async () => {
-    const slow = deferred<RpcResponse<SettingsDescribeView>>()
+    const slow = deferred<Answer<SettingsDescribeView>>()
     const describeCall = vi.fn()
       .mockReturnValueOnce(slow.promise)
       .mockResolvedValueOnce(described([view('theme', 2)]))

+ 43 - 43
packages/client/ui-settings/tests/settings-scope.client.spec.ts

@@ -1,7 +1,7 @@
 import { Context } from '@deepseek-ai/cordis'
 import z from '@deepseek-ai/schemastery'
 import { describe, expect, it, vi } from 'vitest'
-import type { RpcResponse, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
+import type { JsonValue, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
 import { TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
 import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
 import { SettingsSchemaService } from '../src/client/schema.ts'
@@ -18,26 +18,25 @@ const ENVELOPE = z.object({
   preference: z.union(['light', 'dark', 'system']).default('system'),
 }).toJSON()
 
-let rpc = 0
+/** What a Remote call answers with: no carrier envelope, and a free-form failure code. */
+type Answer<T> =
+  | { ok: true; value: T }
+  | { ok: false; error: { code: string; message: string; details: object } }
 
-function ok<T>(value: T): RpcResponse<T> {
-  return { rpcId: `scope-${rpc++}` as never, result: { ok: true, value } }
+function ok<T>(value: T): Answer<T> {
+  return { ok: true, value }
 }
 
-function rejected<T>(): RpcResponse<T> {
-  return {
-    rpcId: `scope-${rpc++}` as never,
-    result: {
-      ok: false,
-      error: { code: 'settings-rejected', message: 'conflict', details: { ns: 'ui-test' } },
-    },
-  }
+function rejected<T>(): Answer<T> {
+  return { ok: false, error: { code: 'settings-rejected', message: 'conflict', details: { ns: 'ui-test' } } }
 }
 
-function view(value: unknown, revision = 0): SettingsNamespaceView {
+function view(value: JsonValue, revision = 0): SettingsNamespaceView {
   return {
     ns: 'ui-test',
-    schema: ENVELOPE,
+    // `toJSON()` already produced the wire envelope; its declared type is the
+    // schema builder's, so one cast names what the Host actually sends.
+    schema: ENVELOPE as unknown as JsonValue,
     value,
     applies: 'live',
     secrets: [],
@@ -45,7 +44,7 @@ function view(value: unknown, revision = 0): SettingsNamespaceView {
   }
 }
 
-function described(value: unknown, revision = 0) {
+function described(value: JsonValue, revision = 0) {
   return ok({ writable: true, hasDocument: true, namespaces: [view(value, revision)] })
 }
 
@@ -148,7 +147,7 @@ describe('SettingsScopeController', () => {
   })
 
   it('serializes rapid set writes, carries revisions, and publishes only the latest settlement', async () => {
-    const first = deferred<RpcResponse<SettingsNamespaceView>>()
+    const first = deferred<Answer<SettingsNamespaceView>>()
     const describeCall = vi.fn().mockResolvedValue(described({ preference: 'system' }, 4))
     const mutate = vi.fn()
       .mockReturnValueOnce(first.promise)
@@ -163,16 +162,16 @@ describe('SettingsScopeController', () => {
     await Promise.all([dark, light])
     expect(published.map(section => section?.preference)).toEqual([undefined, 'system', 'light'])
     expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'light' }, revision: 6 })
-    expect(mutate).toHaveBeenNthCalledWith(1, {
-      ns: 'ui-test',
-      ops: [{ op: 'set', path: ['preference'], value: 'dark' }],
-      expectedRevision: 4,
-    })
-    expect(mutate).toHaveBeenNthCalledWith(2, {
-      ns: 'ui-test',
-      ops: [{ op: 'set', path: ['preference'], value: 'light' }],
-      expectedRevision: 5,
-    })
+    expect(mutate).toHaveBeenNthCalledWith(1,
+      'ui-test',
+      [{ op: 'set', path: ['preference'], value: 'dark' }],
+      4,
+    )
+    expect(mutate).toHaveBeenNthCalledWith(2,
+      'ui-test',
+      [{ op: 'set', path: ['preference'], value: 'light' }],
+      5,
+    )
   })
 
   it('folds the latest write answer into the mirror so a sibling scope sees it', async () => {
@@ -202,10 +201,11 @@ describe('SettingsScopeController', () => {
     initial.resolve(described({ preference: 'system' }, 1))
     await loading
 
-    expect(mutate).toHaveBeenCalledWith({
-      ns: 'ui-test',
-      ops: [{ op: 'set', path: ['preference'], value: 'dark' }],
-    })
+    expect(mutate).toHaveBeenCalledWith(
+      'ui-test',
+      [{ op: 'set', path: ['preference'], value: 'dark' }],
+      undefined,
+    )
     expect(describeCall).toHaveBeenCalledTimes(2)
     expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'dark' }, revision: 2 })
   })
@@ -305,16 +305,16 @@ describe('SettingsScopeController', () => {
     await expect(scope.set('preference', 'light')).resolves.toBeUndefined()
 
     expect(mutate).toHaveBeenCalledTimes(2)
-    expect(mutate).toHaveBeenNthCalledWith(2, {
-      ns: 'ui-test',
-      ops: [{ op: 'set', path: ['preference'], value: 'light' }],
-      expectedRevision: 1,
-    })
+    expect(mutate).toHaveBeenNthCalledWith(2,
+      'ui-test',
+      [{ op: 'set', path: ['preference'], value: 'light' }],
+      1,
+    )
     expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'light' }, revision: 3 })
   })
 
   it('cancels queued and post-dispose writes while draining the in-flight mutation', async () => {
-    const first = deferred<RpcResponse<SettingsNamespaceView>>()
+    const first = deferred<Answer<SettingsNamespaceView>>()
     const mutate = vi.fn().mockReturnValue(first.promise)
     const describeCall = vi.fn()
     const { scope } = derivedScope({ describe: describeCall, mutate })
@@ -433,11 +433,11 @@ describe('SettingsScopeController', () => {
 
     await scope.unset('preference')
 
-    expect(mutate).toHaveBeenCalledWith({
-      ns: 'ui-test',
-      ops: [{ op: 'unset', path: ['preference'] }],
-      expectedRevision: 3,
-    })
+    expect(mutate).toHaveBeenCalledWith(
+      'ui-test',
+      [{ op: 'unset', path: ['preference'] }],
+      3,
+    )
     expect(scope.getSnapshot()).toMatchObject({ value: { preference: 'system' }, revision: 4 })
   })
 
@@ -465,7 +465,7 @@ describe('SettingsScopeBinder.bind', () => {
     let theme!: SettingsScope<UiTestSettings>
     let locale!: SettingsScope<UiTestSettings>
     new TestRemote(ctx)
-    await ctx.plugin(SettingsScopeBinder, { mirror, schema: settingsSchema }).await()
+    await ctx.plugin(SettingsScopeBinder, { mirror, schema: settingsSchema, wire: wire as never }).await()
     expect(ctx.settingsScope.describe()).toBe(mirror)
     const fiber = ctx.plugin({
       inject: ['connection', 'remote', 'settingsScope'],
@@ -493,7 +493,7 @@ describe('SettingsScopeBinder.bind', () => {
     ctx.provide('connection', { api: wire, isLoopback: false } as never)
     let scope!: SettingsScope<UiTestSettings>
     new TestRemote(ctx)
-    await ctx.plugin(SettingsScopeBinder, { mirror, schema: settingsSchema }).await()
+    await ctx.plugin(SettingsScopeBinder, { mirror, schema: settingsSchema, wire: wire as never }).await()
     const fiber = ctx.plugin({
       inject: ['connection', 'remote', 'settingsScope'],
       apply: (plugin: Context) => {

+ 7 - 13
packages/client/ui-theme/tests/apply.client.spec.ts

@@ -42,22 +42,16 @@ async function bench(isLoopback = true) {
     revision: 0,
   })
   const describe = vi.fn(() => Promise.resolve({
-    rpcId: 'theme-describe' as never,
-    result: {
-      ok: true as const,
-      value: { writable: true, hasDocument: true, namespaces: [namespace()] },
-    },
+    ok: true as const,
+    value: { writable: true, hasDocument: true, namespaces: [namespace()] },
   }))
-  const mutate = vi.fn((request: { ops: { path: string[]; value: unknown }[] }) => {
-    const op = request.ops[0]!
+  const mutate = vi.fn((_ns: string, ops: { path: string[]; value: unknown }[]) => {
+    const op = ops[0]!
     section[op.path[0]!] = op.value
-    return Promise.resolve({
-      rpcId: 'theme-mutate' as never,
-      result: { ok: true as const, value: namespace() },
-    })
+    return Promise.resolve({ ok: true as const, value: namespace() })
   })
-  ctx.provide('connection', { api: { settings: { describe, mutate } }, isLoopback } as never)
-  const events = new TestRemote(ctx)
+  ctx.provide('connection', { api: {}, isLoopback } as never)
+  const events = new TestRemote(ctx, { settings: { describe, mutate } })
   await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await()
   return {
     ctx, slots: ctx.get('slots') as SlotRegistry, locale, describe, mutate, events,

+ 0 - 1
packages/client/ui-workspace/tests/workspaces-service.client.spec.ts

@@ -186,7 +186,6 @@ class FakeApiClient implements IApiClient {
   declare readonly skills: IApiClient['skills']
   declare readonly agentPresets: IApiClient['agentPresets']
   declare readonly settings: IApiClient['settings']
-  declare readonly credentials: IApiClient['credentials']
   declare readonly llm: IApiClient['llm']
 
   readonly host: IApiClient['host'] = {

+ 2 - 0
packages/test-support/client-runtime/src/index.ts

@@ -42,6 +42,8 @@ export { domSnapshotSerializer, registerDomSnapshotSerializer } from './snapshot
 export { FixtureSession, TestSessions } from './sessions.ts'
 export { stubSettingsScope } from './settings-scope.ts'
 export type { StubSettingsScope } from './settings-scope.ts'
+export { scriptedSettingsRemote } from './settings-remote.ts'
+export type { ScriptedNamespace, ScriptedSettingsRemote } from './settings-remote.ts'
 export { TestWorkspaces } from './workspaces.ts'
 export { TestRemote } from './remote.ts'
 export {

+ 20 - 7
packages/test-support/client-runtime/src/remote.ts

@@ -4,12 +4,14 @@ import type { Context } from '@deepseek-ai/cordis'
 /**
  * Remote service test double for the forwarded-event path. Feature specs need
  * `ctx.remote.$on` to exist (their plugins inject `remote`) and need forwarded
- * Host events to reach those subscribers, but not the generated namespaces or
- * the wire — so this double implements subscription plus an explicit `emit`
- * driver available only on the concrete test object.
+ * Host events to reach those subscribers, but not the wire — so this double
+ * implements subscription plus an explicit `emit` driver available only on the
+ * concrete test object. A spec that also calls one namespace scripts it through
+ * the constructor rather than reaching the real Client Remote service.
  *
- * `$mount` rejects: a spec that reaches a generated namespace through this
- * double has outgrown it and needs the real Client Remote service.
+ * `$mount` rejects: a spec that needs a real generated contribution installed —
+ * codecs, descriptors, and the wire — has outgrown this double and needs the
+ * real Client Remote service.
  *
  * One deliberate asymmetry with production: a throwing listener propagates out
  * of the emit instead of being contained and logged, so a spec cannot lean on
@@ -20,11 +22,22 @@ export class TestRemote {
   private readonly subscriptions = new Map<string, Set<(...args: never[]) => void>>()
 
   /**
-   * Register the double as `ctx.remote`.
+   * Register the double as `ctx.remote`, plus one service per scripted
+   * namespace so a plugin injecting `remote.<name>` also unparks.
    * @param ctx - the spec's root Context.
+   * @param namespaces - scripted namespace faces reached as `ctx.remote.<name>`.
    */
-  constructor(ctx: Context) {
+  constructor(ctx: Context, namespaces: Readonly<Record<string, object>> = {}) {
+    for (const name of Object.keys(namespaces)) {
+      // A namespace named after one of the double's own members would replace
+      // it, and `$mount`'s rejection is the contract a spec relies on.
+      if (name in TestRemote.prototype || name === 'subscriptions') {
+        throw new TypeError(`TestRemote: scripted namespace "${name}" would shadow the double's own member`)
+      }
+    }
+    Object.assign(this, namespaces)
     ctx.provide('remote', this)
+    for (const [name, face] of Object.entries(namespaces)) ctx.provide(`remote.${name}`, face)
   }
 
   /**

+ 85 - 0
packages/test-support/client-runtime/src/settings-remote.ts

@@ -0,0 +1,85 @@
+/** Test double for the `settings` Remote namespace a bench's plugins inject. */
+import { vi } from 'vitest'
+
+/** The minimum a scripted namespace view carries for the double's own bookkeeping. */
+export interface ScriptedNamespace {
+  /** Namespace key the write addresses. */
+  ns: string
+}
+
+/** One scripted `settings` namespace face plus the controls a bench drives it with. */
+export interface ScriptedSettingsRemote<View extends ScriptedNamespace> {
+  /**
+   * The namespace face handed to `TestRemote` as `settings`. A plugin injecting
+   * `remote.settings` unparks on it, which is what most benches need; the
+   * describe answer is the same one the shared mirror would read.
+   */
+  settings: {
+    describe(): Promise<{ ok: true; value: { writable: boolean; hasDocument: boolean; namespaces: readonly View[] } }>
+    update(ns: string, patch: unknown, expectedRevision: number | undefined): Promise<
+      | { ok: true; value: View }
+      | { ok: false; error: { code: string; message: string; details: object } }
+    >
+    replace(ns: string, section: unknown, expectedRevision: number | undefined): Promise<
+      | { ok: true; value: View }
+      | { ok: false; error: { code: string; message: string; details: object } }
+    >
+    mutate(ns: string, ops: unknown, expectedRevision: number | undefined): Promise<
+      | { ok: true; value: View }
+      | { ok: false; error: { code: string; message: string; details: object } }
+    >
+  }
+  /** Spy behind `settings.update`, for argument assertions. */
+  update: ReturnType<typeof vi.fn>
+  /** Spy behind `settings.replace`, for argument assertions. */
+  replace: ReturnType<typeof vi.fn>
+  /** Spy behind `settings.mutate`, for argument assertions. */
+  mutate: ReturnType<typeof vi.fn>
+  /**
+   * Replace what the next describe answers with, as a Host commit would.
+   * @param namespaces - the namespace views to serve from now on.
+   */
+  publish(namespaces: readonly View[]): void
+}
+
+/**
+ * Build a scripted `settings` Remote namespace for a bench. Each write answers
+ * with the addressed namespace unchanged, so a bench that only needs its
+ * plugins to activate scripts nothing; one asserting a write reads the
+ * corresponding spy or replaces the face.
+ * @param namespaces - namespace views the first describe answers with.
+ * @param options - deployment facts the describe answer reports.
+ * @returns the face and its controls.
+ */
+export function scriptedSettingsRemote<View extends ScriptedNamespace>(
+  namespaces: readonly View[] = [],
+  options: { writable?: boolean; hasDocument?: boolean } = {},
+): ScriptedSettingsRemote<View> {
+  let served = namespaces
+  const writable = options.writable ?? true
+  const hasDocument = options.hasDocument ?? false
+  const answer = (ns: string) => {
+    const view = served.find(candidate => candidate.ns === ns)
+    return Promise.resolve(view === undefined
+      ? {
+        ok: false as const,
+        error: { code: 'settings-rejected', message: `no scripted namespace "${ns}"`, details: { ns } },
+      }
+      : { ok: true as const, value: view })
+  }
+  const update = vi.fn((ns: string, _patch: unknown, _expectedRevision: number | undefined) => answer(ns))
+  const replace = vi.fn((ns: string, _section: unknown, _expectedRevision: number | undefined) => answer(ns))
+  const mutate = vi.fn((ns: string, _ops: unknown, _expectedRevision: number | undefined) => answer(ns))
+  return {
+    settings: {
+      describe: () => Promise.resolve({ ok: true as const, value: { writable, hasDocument, namespaces: served } }),
+      update: (ns, patch, expectedRevision) => update(ns, patch, expectedRevision),
+      replace: (ns, section, expectedRevision) => replace(ns, section, expectedRevision),
+      mutate: (ns, ops, expectedRevision) => mutate(ns, ops, expectedRevision),
+    },
+    update,
+    replace,
+    mutate,
+    publish(next) { served = next },
+  }
+}

+ 55 - 0
packages/test-support/client-runtime/tests/remote.client.spec.ts

@@ -6,6 +6,7 @@
 import { Context } from '@deepseek-ai/cordis'
 import { describe, expect, it } from 'vitest'
 import { TestRemote } from '../src/remote.ts'
+import { scriptedSettingsRemote } from '../src/settings-remote.ts'
 
 describe('TestRemote', () => {
   it('delivers a forwarded event to its subscribers and stops after disposal', async () => {
@@ -40,4 +41,58 @@ describe('TestRemote', () => {
     await expect(remote.$mount()).rejects.toThrow('needs the real Client Remote service')
     await ctx.fiber.dispose()
   })
+
+  it('reaches a scripted namespace as ctx.remote.<name> and as its own service', async () => {
+    const ctx = new Context()
+    const credentials = { describe: () => Promise.resolve({ ok: true as const, value: {} }) }
+    const remote = new TestRemote(ctx, { credentials })
+
+    expect((remote as unknown as { credentials: unknown }).credentials).toBe(credentials)
+    expect(ctx.get('remote.credentials')).toBe(credentials)
+    await ctx.fiber.dispose()
+  })
+
+  it('refuses a scripted namespace that would shadow one of its own members', async () => {
+    const ctx = new Context()
+    // Accepting this would replace the very refusal the case above pins.
+    expect(() => new TestRemote(ctx, { $mount: {} })).toThrow('would shadow')
+    expect(() => new TestRemote(ctx, { subscriptions: {} })).toThrow('would shadow')
+    await ctx.fiber.dispose()
+  })
+})
+
+describe('scriptedSettingsRemote', () => {
+  it('serves, writes, and replaces its scripted namespace list', async () => {
+    const first = { ns: 'first', revision: 1 }
+    const second = { ns: 'second', revision: 2 }
+    const remote = scriptedSettingsRemote([first])
+
+    await expect(remote.settings.describe()).resolves.toEqual({
+      ok: true,
+      value: { writable: true, hasDocument: false, namespaces: [first] },
+    })
+    await expect(remote.settings.update('first', {}, undefined)).resolves.toEqual({ ok: true, value: first })
+    await expect(remote.settings.replace('missing', {}, undefined)).resolves.toMatchObject({
+      ok: false,
+      error: { code: 'settings-rejected', details: { ns: 'missing' } },
+    })
+    await expect(remote.settings.mutate('first', [], undefined)).resolves.toEqual({ ok: true, value: first })
+    expect(remote.update).toHaveBeenCalledWith('first', {}, undefined)
+    expect(remote.replace).toHaveBeenCalledWith('missing', {}, undefined)
+    expect(remote.mutate).toHaveBeenCalledWith('first', [], undefined)
+
+    remote.publish([second])
+    await expect(remote.settings.describe()).resolves.toEqual({
+      ok: true,
+      value: { writable: true, hasDocument: false, namespaces: [second] },
+    })
+  })
+
+  it('reports explicit deployment facts', async () => {
+    const remote = scriptedSettingsRemote([], { writable: false, hasDocument: true })
+    await expect(remote.settings.describe()).resolves.toEqual({
+      ok: true,
+      value: { writable: false, hasDocument: true, namespaces: [] },
+    })
+  })
 })