|
@@ -8,6 +8,7 @@ import { stubSettingsScope, type StubSettingsScope } from '@deepseek-ai/dsh-clie
|
|
|
import { CardForm, numberField, textField } from '../src/client/card-form.ts'
|
|
import { CardForm, numberField, textField } from '../src/client/card-form.ts'
|
|
|
import { AgentLoopCardController, type AgentLoopSettings } from '../src/client/agent-loop-card-controller.ts'
|
|
import { AgentLoopCardController, type AgentLoopSettings } from '../src/client/agent-loop-card-controller.ts'
|
|
|
import { BashCardController, type BashSettings } from '../src/client/bash-card-controller.ts'
|
|
import { BashCardController, type BashSettings } from '../src/client/bash-card-controller.ts'
|
|
|
|
|
+import { SettingsDescribeMirror } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
|
import { ConfigurablePluginsTabController } from '../src/client/tab-store.ts'
|
|
import { ConfigurablePluginsTabController } from '../src/client/tab-store.ts'
|
|
|
import { WebSearchCardController, type WebSearchSettings } from '../src/client/web-search-card-controller.ts'
|
|
import { WebSearchCardController, type WebSearchSettings } from '../src/client/web-search-card-controller.ts'
|
|
|
|
|
|
|
@@ -555,7 +556,7 @@ describe('ConfigurablePluginsTabController', () => {
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
}))
|
|
}))
|
|
|
- return { api: { settings: { describe } } as never, describe }
|
|
|
|
|
|
|
+ return { mirror: new SettingsDescribeMirror({ settings: { describe } } as never), describe }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/** Slot ledger stand-in: one stored entry per registered card key. */
|
|
/** Slot ledger stand-in: one stored entry per registered card key. */
|
|
@@ -565,9 +566,9 @@ describe('ConfigurablePluginsTabController', () => {
|
|
|
|
|
|
|
|
it('dispatches the served namespaces a card claims, in card registration order', async () => {
|
|
it('dispatches the served namespaces a card claims, in card registration order', async () => {
|
|
|
const settings = settingsApi(['bash', 'ui-theme', 'agent-loop'])
|
|
const settings = settingsApi(['bash', 'ui-theme', 'agent-loop'])
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => ledger('agent-loop', 'bash'))
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => ledger('agent-loop', 'bash'))
|
|
|
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ await settings.mirror.ensure()
|
|
|
|
|
|
|
|
// ui-theme is served but claimed by no card here — another surface owns
|
|
// ui-theme is served but claimed by no card here — another surface owns
|
|
|
// it. The order is the cards', not the Host's: plugin activation can
|
|
// it. The order is the cards', not the Host's: plugin activation can
|
|
@@ -578,9 +579,9 @@ describe('ConfigurablePluginsTabController', () => {
|
|
|
|
|
|
|
|
it('never dispatches a card whose namespace this deployment does not serve', async () => {
|
|
it('never dispatches a card whose namespace this deployment does not serve', async () => {
|
|
|
const settings = settingsApi(['bash'])
|
|
const settings = settingsApi(['bash'])
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => ledger('bash', 'web-search-deepseek'))
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => ledger('bash', 'web-search-deepseek'))
|
|
|
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ await settings.mirror.ensure()
|
|
|
|
|
|
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual(['bash'])
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual(['bash'])
|
|
|
})
|
|
})
|
|
@@ -588,8 +589,8 @@ describe('ConfigurablePluginsTabController', () => {
|
|
|
it('takes a card registered after the read without asking the Host again', async () => {
|
|
it('takes a card registered after the read without asking the Host again', async () => {
|
|
|
const settings = settingsApi(['bash'])
|
|
const settings = settingsApi(['bash'])
|
|
|
let entries = ledger()
|
|
let entries = ledger()
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => entries)
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => entries)
|
|
|
|
|
+ await settings.mirror.ensure()
|
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual([])
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual([])
|
|
|
|
|
|
|
|
entries = ledger('bash')
|
|
entries = ledger('bash')
|
|
@@ -599,34 +600,33 @@ describe('ConfigurablePluginsTabController', () => {
|
|
|
expect(settings.describe).toHaveBeenCalledOnce()
|
|
expect(settings.describe).toHaveBeenCalledOnce()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('keeps the namespaces it knew when a read fails', async () => {
|
|
|
|
|
|
|
+ it('keeps the namespaces it knew when a refresh fails', async () => {
|
|
|
const settings = settingsApi(['bash'])
|
|
const settings = settingsApi(['bash'])
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => ledger('bash'))
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => ledger('bash'))
|
|
|
|
|
+ await settings.mirror.ensure()
|
|
|
settings.describe.mockRejectedValueOnce(new Error('offline'))
|
|
settings.describe.mockRejectedValueOnce(new Error('offline'))
|
|
|
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ await settings.mirror.load()
|
|
|
|
|
|
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual(['bash'])
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual(['bash'])
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('publishes nothing once disposed, and never claims it was answered', async () => {
|
|
|
|
|
|
|
+ it('stops following the mirror once disposed, and never claims it was answered', async () => {
|
|
|
const settings = settingsApi(['bash'])
|
|
const settings = settingsApi(['bash'])
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => ledger('bash'))
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => ledger('bash'))
|
|
|
|
|
|
|
|
controller.dispose()
|
|
controller.dispose()
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ await settings.mirror.load()
|
|
|
|
|
|
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot())
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot())
|
|
|
.toEqual({ loaded: false, namespaces: [] })
|
|
.toEqual({ loaded: false, namespaces: [] })
|
|
|
- expect(settings.describe).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('ignores a slot-ledger change that arrives after disposal', async () => {
|
|
it('ignores a slot-ledger change that arrives after disposal', async () => {
|
|
|
const settings = settingsApi(['bash'])
|
|
const settings = settingsApi(['bash'])
|
|
|
let entries = ledger()
|
|
let entries = ledger()
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => entries)
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => entries)
|
|
|
|
|
+ await settings.mirror.ensure()
|
|
|
|
|
|
|
|
controller.dispose()
|
|
controller.dispose()
|
|
|
entries = ledger('bash')
|
|
entries = ledger('bash')
|
|
@@ -635,33 +635,11 @@ describe('ConfigurablePluginsTabController', () => {
|
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual([])
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual([])
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('drops a read a newer one superseded', async () => {
|
|
|
|
|
- // The section re-reads on every settings-document invalidation, so a slow
|
|
|
|
|
- // first answer must not overwrite the newer one that already landed.
|
|
|
|
|
- const settings = settingsApi(['bash'])
|
|
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => ledger('bash', 'agent-loop'))
|
|
|
|
|
- const slow = Promise.withResolvers<unknown>()
|
|
|
|
|
- settings.describe.mockReturnValueOnce(slow.promise as never)
|
|
|
|
|
- const stale = controller.load()
|
|
|
|
|
-
|
|
|
|
|
- await controller.load()
|
|
|
|
|
- expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual(['bash'])
|
|
|
|
|
- slow.resolve({
|
|
|
|
|
- rpcId: 's-0',
|
|
|
|
|
- result: { ok: true, value: { writable: true, hasDocument: true, namespaces: [
|
|
|
|
|
- { ns: 'agent-loop', schema: {}, value: {}, applies: 'live', secrets: [], revision: 0 },
|
|
|
|
|
- ] } },
|
|
|
|
|
- })
|
|
|
|
|
- await stale
|
|
|
|
|
-
|
|
|
|
|
- expect(controller.inject().hooks.configurablePlugins.getSnapshot().namespaces).toEqual(['bash'])
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
it('reports the Host answered even when it serves nothing this tab shows', async () => {
|
|
it('reports the Host answered even when it serves nothing this tab shows', async () => {
|
|
|
const settings = settingsApi(['ui-theme'])
|
|
const settings = settingsApi(['ui-theme'])
|
|
|
- const controller = new ConfigurablePluginsTabController(settings.api, () => ledger('bash'))
|
|
|
|
|
|
|
+ const controller = new ConfigurablePluginsTabController(settings.mirror, () => ledger('bash'))
|
|
|
|
|
|
|
|
- await controller.load()
|
|
|
|
|
|
|
+ await settings.mirror.ensure()
|
|
|
|
|
|
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot())
|
|
expect(controller.inject().hooks.configurablePlugins.getSnapshot())
|
|
|
.toEqual({ loaded: true, namespaces: [] })
|
|
.toEqual({ loaded: true, namespaces: [] })
|