From 88f2f0aaecda19812f7ef7d384a8f62e65386b1b Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:12:24 +0800 Subject: [PATCH] test(api): complete migrated Remote coverage --- docs/module-graph.i18n.yaml | 4 +- docs/module-graph.md | 3 +- docs/module-graph.zh.md | 3 +- .../tests/session-models.host.spec.ts | 4 +- .../session-open-workspace-path.host.spec.ts | 56 ++++++++++- .../tests/session-skills.host.spec.ts | 37 +++++++ .../session-controller/tests/test-remote.ts | 3 + .../tests/settings-controller.host.spec.ts | 96 +++++++++++++++++++ .../tests/store.client.spec.ts | 15 +++ .../tests/apply.client.spec.ts | 8 +- .../tests/stores.client.spec.ts | 36 +++---- 11 files changed, 234 insertions(+), 31 deletions(-) diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index 73c0c75403..00810b4551 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/module-graph.md -module-graph.md: e0376d865fac1505cce48f4f3a678a11730ecd0e -module-graph.zh.md: 72af3b9a52764e0ae8ed2b7cef910eeedefa6c6c +module-graph.md: aebb6883280d5edc48355e83936cb8793529fc2a +module-graph.zh.md: 43607e291b632d1df6cd00a59e182027dc548008 diff --git a/docs/module-graph.md b/docs/module-graph.md index e0376d865f..aebb688328 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -549,6 +549,7 @@ flowchart TD pkg_file_reference --> pkg_invariants pkg_time_context --> pkg_agent pkg_time_context --> pkg_invariants + pkg_time_context --> pkg_llm pkg_time_context --> pkg_session pkg_message_feedback --> pkg_brand pkg_message_feedback --> pkg_invariants @@ -1789,7 +1790,7 @@ flowchart TD | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) | | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 72af3b9a52..43607e291b 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -551,6 +551,7 @@ flowchart TD pkg_file_reference --> pkg_invariants pkg_time_context --> pkg_agent pkg_time_context --> pkg_invariants + pkg_time_context --> pkg_llm pkg_time_context --> pkg_session pkg_message_feedback --> pkg_brand pkg_message_feedback --> pkg_invariants @@ -1791,7 +1792,7 @@ flowchart TD | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) | | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | diff --git a/packages/api/session-controller/tests/session-models.host.spec.ts b/packages/api/session-controller/tests/session-models.host.spec.ts index d203585459..bc67a94ca6 100644 --- a/packages/api/session-controller/tests/session-models.host.spec.ts +++ b/packages/api/session-controller/tests/session-models.host.spec.ts @@ -305,9 +305,9 @@ describe('Web session model selection', () => { model: 'private-preview', reasoningEffort: ReasoningEffortId('max'), }) - createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'deepseek-official', model: 'deepseek-chat' }), cwd: '/tmp' }) + const remote = createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'deepseek-official', model: 'deepseek-chat' }), cwd: '/tmp' }) - const catalog = await buildModelCatalog(ctx) + const catalog = expectValue(await remote.modelCatalog()) expect(currentSelection(ctx, sessionId)).toEqual({ provider: 'deepseek-official', model: 'private-preview', diff --git a/packages/api/session-controller/tests/session-open-workspace-path.host.spec.ts b/packages/api/session-controller/tests/session-open-workspace-path.host.spec.ts index f8c89efd5e..ca7a78c589 100644 --- a/packages/api/session-controller/tests/session-open-workspace-path.host.spec.ts +++ b/packages/api/session-controller/tests/session-open-workspace-path.host.spec.ts @@ -2,7 +2,11 @@ import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import { describe, expect, it, vi } from 'vitest' -import { createSessionTestRemote, testSessionPersistence } from './test-remote.ts' +import { + createSessionTestController, + createSessionTestRemote, + testSessionPersistence, +} from './test-remote.ts' async function context(): Promise { const ctx = new Context() @@ -92,4 +96,54 @@ describe('session/openWorkspacePath', () => { await expect(remote.openWorkspacePath({ sessionId, path: 'result.html' }, aborted.signal)) .resolves.toMatchObject({ ok: false, error: { code: 'cancelled' } }) }) + + it('classifies inspection cancellation and non-session failures', async () => { + const ctx = await context() + const controller = createSessionTestController(ctx, { + defaultModelSelection: () => ({ provider: 'p', model: 'm' }), + cwd: '/default', + }) + const inspect = vi.spyOn(controller, 'inspect') + const aborted = new AbortController() + inspect.mockImplementationOnce(async () => { + aborted.abort(new Error('cancelled')) + throw new Error('inspection stopped') + }) + await expect(controller.openWorkspacePath({ + sessionId: SessionId('inspection-cancelled'), path: 'result.html', + }, aborted.signal)).rejects.toMatchObject({ failure: { code: 'cancelled' } }) + + inspect.mockRejectedValueOnce('storage offline') + await expect(controller.openWorkspacePath({ + sessionId: SessionId('inspection-failed'), path: 'result.html', + }, new AbortController().signal)).rejects.toMatchObject({ + failure: { code: 'internal', message: expect.stringContaining('storage offline') }, + }) + }) + + it('classifies opener cancellation and non-Error failures', async () => { + const ctx = await context() + const sessionId = SessionId('open-error-kinds') + ctx.sessions.create(sessionId, { meta: { cwd: '/workspace/project' } }) + const aborted = new AbortController() + const openPath = vi.fn() + .mockImplementationOnce(async () => { + aborted.abort(new Error('cancelled')) + throw new Error('opening stopped') + }) + .mockRejectedValueOnce('desktop unavailable') + const controller = createSessionTestController(ctx, { + defaultModelSelection: () => ({ provider: 'p', model: 'm' }), + cwd: '/default', + openPath, + }) + + await expect(controller.openWorkspacePath({ sessionId, path: 'first.html' }, aborted.signal)) + .rejects.toMatchObject({ failure: { code: 'cancelled' } }) + await expect(controller.openWorkspacePath({ + sessionId, path: 'second.html', + }, new AbortController().signal)).rejects.toMatchObject({ + failure: { code: 'internal', message: 'path open failed: desktop unavailable' }, + }) + }) }) diff --git a/packages/api/session-controller/tests/session-skills.host.spec.ts b/packages/api/session-controller/tests/session-skills.host.spec.ts index e0640ddfe6..32d9957d4b 100644 --- a/packages/api/session-controller/tests/session-skills.host.spec.ts +++ b/packages/api/session-controller/tests/session-skills.host.spec.ts @@ -188,4 +188,41 @@ describe('SessionSkillCatalog', () => { failure: { code: 'internal', message: expect.stringContaining('skill registry is absent') }, }) }) + + it('rejects observations without projections or a project cwd', async () => { + const ctx = await context() + const sessionId = SessionId('incomplete-skills') + const withoutProjections = { ...observation(sessionId, { cwd: '/project' }), projections: undefined } + const observeSession = vi.fn() + .mockResolvedValueOnce(withoutProjections) + .mockResolvedValueOnce(observation(sessionId)) + ctx.provide('sessionQuery', { observeSession } as never) + const catalog = new SessionSkillCatalog(ctx) + + await expect(catalog.list({ sessionId }, new AbortController().signal)) + .rejects.toMatchObject({ + failure: { code: 'internal', message: expect.stringContaining('projected Session observation') }, + }) + await expect(catalog.list({ sessionId }, new AbortController().signal)) + .rejects.toMatchObject({ + failure: { code: 'internal', message: expect.stringContaining('has no project cwd') }, + }) + }) + + it('classifies a provider listing failure', async () => { + const ctx = await context() + const sessionId = SessionId('failed-skills') + ctx.provide('sessionQuery', { + observeSession: () => Promise.resolve(observation(sessionId, { cwd: '/project' })), + } as never) + ctx.provide('skills', { + list: () => Promise.reject(new Error('catalog offline')), + } as never) + const catalog = new SessionSkillCatalog(ctx) + + await expect(catalog.list({ sessionId }, new AbortController().signal)) + .rejects.toMatchObject({ + failure: { code: 'internal', message: 'skill listing failed: Error: catalog offline' }, + }) + }) }) diff --git a/packages/api/session-controller/tests/test-remote.ts b/packages/api/session-controller/tests/test-remote.ts index f5766382cb..77e813b324 100644 --- a/packages/api/session-controller/tests/test-remote.ts +++ b/packages/api/session-controller/tests/test-remote.ts @@ -19,6 +19,7 @@ import { } from '@deepseek-ai/dsh-typert-protocol' import SessionController from '../src/index.ts' import type { + ModelCatalog, SessionAttachmentRequest, SessionAttachmentValue, SessionCancelRequest, @@ -54,6 +55,7 @@ export interface TestSessionRemote { search(request: SessionSearchRequest, signal?: AbortSignal): Promise> create(request: SessionCreateRequest): Promise> selectModel(request: SessionSelectModelRequest): Promise> + modelCatalog(): Promise> rename(request: SessionRenameRequest): Promise> fork(request: SessionForkRequest): Promise> prompt(request: SessionPromptRequest, signal?: AbortSignal): Promise> @@ -241,6 +243,7 @@ export function createSessionTestRemote( ), create: request => remoteResult(() => direct.create(request)), selectModel: request => remoteResult(() => direct.selectModel(request)), + modelCatalog: () => remoteResult(() => direct.modelCatalog()), rename: request => remoteResult(() => direct.rename(request)), fork: request => remoteResult(() => direct.fork(request)), prompt: (request, signal = new AbortController().signal) => remoteResult( diff --git a/packages/api/settings-controller/tests/settings-controller.host.spec.ts b/packages/api/settings-controller/tests/settings-controller.host.spec.ts index 4b3b973eae..d43232ae62 100644 --- a/packages/api/settings-controller/tests/settings-controller.host.spec.ts +++ b/packages/api/settings-controller/tests/settings-controller.host.spec.ts @@ -1,6 +1,11 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import z from '@deepseek-ai/schemastery' +import { + InvalidPresetIdError, + PresetExistsError, + UnknownPresetError, +} from '@deepseek-ai/dsh-agent-presets' import { settingsNamespace } from '@deepseek-ai/dsh-settings' import type { SettingsDescriptor, SettingsNamespace } from '@deepseek-ai/dsh-settings' import { TypertRemoteFailure, remoteMethods } from '@deepseek-ai/dsh-typert-protocol' @@ -306,6 +311,32 @@ describe('the settings Remote namespace a configuration page calls', () => { }) }) + it('classifies cancellation while preparing or opening the settings document', async () => { + const preparing = new Context() + await preparing.plugin(DocumentSettings) + const prepareAbort = new AbortController() + vi.spyOn(preparing.settings, 'prepareDocument').mockImplementation(async () => { + prepareAbort.abort(new Error('cancelled')) + throw new Error('preparation stopped') + }) + const preparingController = new SettingsController(preparing) + await expect(preparingController.openSettingsDocument(prepareAbort.signal)) + .rejects.toMatchObject({ failure: { code: 'cancelled' } }) + + const opening = new Context() + await opening.plugin(DocumentSettings) + vi.spyOn(opening.settings, 'prepareDocument').mockResolvedValue('/tmp/settings.yaml') + const openAbort = new AbortController() + const openingController = new SettingsController(opening, {}, { + openTextFile: async () => { + openAbort.abort(new Error('cancelled')) + throw new Error('opening stopped') + }, + }) + await expect(openingController.openSettingsDocument(openAbort.signal)) + .rejects.toMatchObject({ failure: { code: 'cancelled' } }) + }) + it('opens a user Agent preset directory or returns its path without a native opener', async () => { const ctx = new Context() ctx.provide('agentPresets', { @@ -331,6 +362,21 @@ describe('the settings Remote namespace a configuration page calls', () => { .resolves.toEqual({ opened: false, path: '/presets/mine' }) }) + it('covers native-open detection defaults and explicit overrides', () => { + const fromInjectedOpener = new SettingsController(new Context(), {}, { + openPath: () => Promise.resolve(), + }) + expect((fromInjectedOpener as unknown as { canOpenPath: () => boolean }).canOpenPath()).toBe(true) + + const detected = new SettingsController(new Context()) + expect(typeof (detected as unknown as { canOpenPath: () => boolean }).canOpenPath()).toBe('boolean') + + const override = vi.fn(() => false) + const overridden = new SettingsController(new Context(), {}, { canOpenPath: override }) + expect((overridden as unknown as { canOpenPath: () => boolean }).canOpenPath()).toBe(false) + expect(override).toHaveBeenCalledOnce() + }) + it('refuses a shipped Agent preset and a missing preset provider', async () => { const ctx = new Context() ctx.provide('agentPresets', { @@ -346,4 +392,54 @@ describe('the settings Remote namespace a configuration page calls', () => { await expect(missing.openAgentPresetDirectory('mine', new AbortController().signal)) .rejects.toMatchObject({ failure: { code: 'agent-preset-not-found' } }) }) + + it('rejects an empty Agent preset id before resolving a provider', async () => { + const resolve = vi.fn() + const ctx = new Context() + ctx.provide('agentPresets', { resolve } as never) + const controller = new SettingsController(ctx) + + await expect(controller.openAgentPresetDirectory('', new AbortController().signal)) + .rejects.toMatchObject({ failure: { code: 'bad-request' } }) + expect(resolve).not.toHaveBeenCalled() + }) + + it.each([ + [new UnknownPresetError('missing', ['standard']), 'agent-preset-not-found'], + [new InvalidPresetIdError('../bad'), 'agent-preset-invalid'], + [new PresetExistsError('taken'), 'agent-preset-invalid'], + [new TypertRemoteFailure({ code: 'cancelled', message: 'cancelled', details: {} }), 'cancelled'], + ['unexpected preset failure', 'internal'], + ] as const)('maps Agent preset resolution failure %#', async (error, code) => { + const ctx = new Context() + ctx.provide('agentPresets', { resolve: () => Promise.reject(error) } as never) + const controller = new SettingsController(ctx) + + await expect(controller.openAgentPresetDirectory('mine', new AbortController().signal)) + .rejects.toMatchObject({ failure: { code } }) + }) + + it('classifies cancellation and non-Error failures from the preset opener', async () => { + const ctx = new Context() + ctx.provide('agentPresets', { + resolve: (id: string) => Promise.resolve({ + id, trust: 'user', path: `/presets/${id}/agent.cordis.yml`, + }), + } as never) + const abort = new AbortController() + const openPath = vi.fn() + .mockImplementationOnce(async () => { + abort.abort(new Error('cancelled')) + throw new Error('opening stopped') + }) + .mockRejectedValueOnce('desktop unavailable') + const controller = new SettingsController(ctx, { nativeOpen: true }, { openPath }) + + await expect(controller.openAgentPresetDirectory('first', abort.signal)) + .rejects.toMatchObject({ failure: { code: 'cancelled' } }) + await expect(controller.openAgentPresetDirectory('second', new AbortController().signal)) + .rejects.toMatchObject({ + failure: { code: 'internal', message: 'path open failed: desktop unavailable' }, + }) + }) }) diff --git a/packages/client/ui-settings-models/tests/store.client.spec.ts b/packages/client/ui-settings-models/tests/store.client.spec.ts index 8ce1724b32..5f9aced5bb 100644 --- a/packages/client/ui-settings-models/tests/store.client.spec.ts +++ b/packages/client/ui-settings-models/tests/store.client.spec.ts @@ -181,6 +181,21 @@ describe('ModelsSettingsStore', () => { expect(store.store.getSnapshot().status).toBe('ready') }) + it('surfaces a configurable-provider directory failure', async () => { + const { face, mirror } = api() + const llm = (face as unknown as { + llm: { listConfigurableProviders: () => Promise> } + }).llm + llm.listConfigurableProviders = () => Promise.resolve(remoteFail('configuration directory down')) + const store = new ModelsSettingsStore(face, settingsSchema, mirror) + + await store.load() + + expect(store.store.getSnapshot()).toMatchObject({ + status: 'error', error: 'configuration directory down', + }) + }) + it('lets the newest load win over a stale slow response', async () => { let release: (() => void) | undefined const gate = new Promise((resolve) => { release = resolve }) diff --git a/packages/client/ui-settings-plugins/tests/apply.client.spec.ts b/packages/client/ui-settings-plugins/tests/apply.client.spec.ts index 6d118856ef..6bc979abc6 100644 --- a/packages/client/ui-settings-plugins/tests/apply.client.spec.ts +++ b/packages/client/ui-settings-plugins/tests/apply.client.spec.ts @@ -29,7 +29,7 @@ async function bench(served?: string[]) { ctx.provide('locale', locale) const describeCredentials = vi.fn(() => Promise.resolve({ ok: false, error: { code: 'internal', message: 'no provider', details: {} } })) const models = vi.fn(() => Promise.resolve({ - rpcId: 'm', result: { ok: true, value: { groups: [], failures: [] } }, + ok: true as const, value: { groups: [], failures: [] }, })) const describeSettings = vi.fn(() => Promise.resolve(served === undefined ? { ok: false, error: { code: 'internal', message: 'no provider', details: {} } } @@ -45,11 +45,11 @@ async function bench(served?: string[]) { })) const remote = new TestRemote(ctx, { credentials: { describe: describeCredentials, set: vi.fn() }, + session: { modelCatalog: models }, settings: { describe: describeSettings }, }) ctx.provide('connection', { isLoopback: true, - api: { llm: { models } }, } as never) await ctx.plugin({ inject: [...settingsInject], apply: settingsApply }).await() return { @@ -66,7 +66,9 @@ function declareRoot(slots: SlotRegistry): () => void { describe('ui-settings-plugins apply', () => { it('declares the services it uses', () => { - expect(inject).toEqual(['slots', 'locale', 'connection', 'remote', 'remote.credentials', 'settingsScope']) + expect(inject).toEqual([ + 'slots', 'locale', 'connection', 'remote', 'remote.credentials', 'remote.session', 'settingsScope', + ]) }) it('registers one Plugins section and declares the tab and card slots', async () => { diff --git a/packages/client/ui-settings-plugins/tests/stores.client.spec.ts b/packages/client/ui-settings-plugins/tests/stores.client.spec.ts index 28c26e73cd..2fefbb0cc8 100644 --- a/packages/client/ui-settings-plugins/tests/stores.client.spec.ts +++ b/packages/client/ui-settings-plugins/tests/stores.client.spec.ts @@ -65,12 +65,11 @@ function modelsApi(options: { error?: string } = {}) { const models = vi.fn(() => Promise.resolve({ - rpcId: 'm-1' as never, - result: options.error === undefined + ...(options.error === undefined ? { ok: true as const, value: { groups: options.groups ?? [], failures: options.failures ?? [] } } - : { ok: false as const, error: { code: 'internal_error' as never, message: options.error } }, + : { ok: false as const, error: { code: 'internal' as const, message: options.error, details: {} } }), })) - return { api: { llm: { models } } as never, models } + return { api: { modelCatalog: models } as never, models } } function deferred() { @@ -662,15 +661,14 @@ describe('SubagentModelSelectionCardController', () => { const refreshed = deferred() const models = vi.fn() .mockResolvedValueOnce({ - rpcId: 'catalog-1', - result: { ok: true, value: { + ok: true, value: { groups: [{ id: 'alpha', name: 'Alpha', models: [{ id: 'fast', name: 'Fast' }] }], failures: [], - } }, + }, }) .mockImplementationOnce(() => refreshed.promise) const controller = new SubagentModelSelectionCardController( - host.scope, { llm: { models } } as never, + host.scope, { modelCatalog: models } as never, ) const face = controller.inject() const state = () => face.hooks.subagentModelSelectionCard.getSnapshot() @@ -684,8 +682,7 @@ describe('SubagentModelSelectionCardController', () => { candidates: [expect.objectContaining({ key: 'alpha\0fast', selected: true })], }) refreshed.resolve({ - rpcId: 'catalog-2', - result: { ok: true, value: { groups: [], failures: [] } }, + ok: true, value: { groups: [], failures: [] }, } as never) await vi.waitFor(() => { expect(state().catalogStatus).toBe('ready') }) expect(state().candidates).toEqual([ @@ -738,21 +735,19 @@ describe('SubagentModelSelectionCardController', () => { }) const models = vi.fn() .mockResolvedValueOnce({ - rpcId: 'catalog-1', - result: { ok: true, value: { + ok: true, value: { groups: [{ id: 'alpha', name: 'Alpha', models: [{ id: 'fast', name: 'Fast' }] }], failures: [], - } }, + }, }) .mockResolvedValueOnce({ - rpcId: 'catalog-2', - result: { ok: true, value: { + ok: true, value: { groups: [{ id: 'beta', name: 'Beta', models: [{ id: 'new', name: 'New' }] }], failures: [], - } }, + }, }) const controller = new SubagentModelSelectionCardController( - host.scope, { llm: { models } } as never, + host.scope, { modelCatalog: models } as never, ) const state = () => controller.inject().hooks.subagentModelSelectionCard.getSnapshot() await vi.waitFor(() => { expect(state().candidates[0]?.provider).toBe('alpha') }) @@ -807,7 +802,7 @@ describe('SubagentModelSelectionCardController', () => { const pending = deferred() const models = vi.fn(() => pending.promise) - const controller = new SubagentModelSelectionCardController(host.scope, { llm: { models } } as never) + const controller = new SubagentModelSelectionCardController(host.scope, { modelCatalog: models } as never) const face = controller.inject() face.toggleEnabled() face.retryCatalog() @@ -819,14 +814,13 @@ describe('SubagentModelSelectionCardController', () => { const pendingResolve = deferred() const resolving = new SubagentModelSelectionCardController( host.scope, - { llm: { models: () => pendingResolve.promise } } as never, + { modelCatalog: () => pendingResolve.promise } as never, ) const resolvingFace = resolving.inject() resolvingFace.toggleEnabled() resolving.dispose() pendingResolve.resolve({ - rpcId: 'late' as never, - result: { ok: true, value: { groups: [], failures: [] } }, + ok: true, value: { groups: [], failures: [] }, } as never) await pendingResolve.promise })