Merge remote-tracking branch 'origin/master' into worktree/fix-1463-rich-content-bridge
This commit is contained in:
@@ -321,12 +321,11 @@ describe('settings domain', () => {
|
||||
expect(opened).toEqual([])
|
||||
})
|
||||
|
||||
it('serves model-provider and explicitly allowlisted Web namespaces only', async () => {
|
||||
// The settings seam is general: any plugin may register a namespace for
|
||||
// its own configuration. The Web configuration plane remains opt-in, so a
|
||||
// future internal plugin cannot become remotely configurable just by
|
||||
// registering; locale, permission, conversation, theme, and the product
|
||||
// onboarding namespace are intentionally admitted by this surface.
|
||||
it('serves every registered namespace, including one this repository never named', async () => {
|
||||
// Registering IS the exposure: a plugin distributed outside this
|
||||
// repository configures itself from the browser without a change here.
|
||||
// The plane stays loopback-only and secret-redacted, and which surface
|
||||
// renders a namespace is the browser's decision, not this proxy's.
|
||||
const ctx = await harness()
|
||||
ctx.settings.register(NS, AdapterConfig)
|
||||
ctx.settings.register(settingsNamespace('some-other-plugin'), z.object({ secretPath: z.string() }))
|
||||
@@ -357,8 +356,8 @@ describe('settings domain', () => {
|
||||
|
||||
const value = expectOk(await api.settings.describe(request({})))
|
||||
expect(value.namespaces.map(view => view.ns)).toEqual([
|
||||
'llm-deepseek', 'permission', 'ui-theme', 'locale', 'ui-conversation',
|
||||
'shell', 'agent-loop', 'web-search-deepseek',
|
||||
'llm-deepseek', 'some-other-plugin', 'permission', 'ui-theme', 'locale',
|
||||
'ui-conversation', 'shell', 'agent-loop', 'web-search-deepseek',
|
||||
])
|
||||
const permission = expectOk(await api.settings.mutate(request({
|
||||
ns: 'permission',
|
||||
@@ -396,16 +395,13 @@ describe('settings domain', () => {
|
||||
})))
|
||||
expect(webSearch.value).toEqual({ baseURL: 'https://search.test/v1' })
|
||||
|
||||
for (const response of [
|
||||
await api.settings.update(request({ ns: 'some-other-plugin', patch: { secretPath: '/etc/shadow' } })),
|
||||
await api.settings.replace(request({ ns: 'some-other-plugin', section: {} })),
|
||||
]) {
|
||||
const error = expectErr(response)
|
||||
expect(error.code).toBe('settings-not-exposed')
|
||||
expect(error.details).toEqual({ ns: 'some-other-plugin' })
|
||||
}
|
||||
// The write never reached the seam.
|
||||
expect(ctx.settings.describe().find(d => String(d.ns) === 'some-other-plugin')?.value).toEqual({})
|
||||
const other = expectOk(await api.settings.update(request({
|
||||
ns: 'some-other-plugin',
|
||||
patch: { secretPath: '/etc/shadow' },
|
||||
})))
|
||||
expect(other.value).toEqual({ secretPath: '/etc/shadow' })
|
||||
expect(ctx.settings.describe().find(d => String(d.ns) === 'some-other-plugin')?.value)
|
||||
.toEqual({ secretPath: '/etc/shadow' })
|
||||
})
|
||||
|
||||
it('serves product preference namespaces without invalidating the model catalog', async () => {
|
||||
@@ -445,13 +441,17 @@ describe('settings domain', () => {
|
||||
.toEqual({ default: 'minimal' })
|
||||
})
|
||||
|
||||
it('refuses even a model-provider namespace once its directory entry is gone', async () => {
|
||||
it('keeps serving a provider namespace whose directory entry is gone', async () => {
|
||||
// The configurable-provider directory says what the Models page can offer,
|
||||
// not what a user may configure: a dormant route's stored section is still
|
||||
// theirs to edit, and losing the entry must not strand it.
|
||||
const ctx = await harness({ configurableProviders: false })
|
||||
ctx.settings.register(NS, AdapterConfig)
|
||||
const api = createApiProxy(ctx, DEFAULTS)
|
||||
expect(expectOk(await api.settings.describe(request({}))).namespaces).toEqual([])
|
||||
expect(expectErr(await api.settings.update(request({ ns: 'llm-deepseek', patch: { baseURL: 'https://x' } }))).code)
|
||||
.toBe('settings-not-exposed')
|
||||
expect(expectOk(await api.settings.describe(request({}))).namespaces.map(view => view.ns))
|
||||
.toEqual(['llm-deepseek'])
|
||||
expect(expectOk(await api.settings.update(request({ ns: 'llm-deepseek', patch: { baseURL: 'https://x' } }))).value)
|
||||
.toMatchObject({ baseURL: 'https://x' })
|
||||
})
|
||||
|
||||
it('forwards a provider settings change for model-catalog consumers', async () => {
|
||||
@@ -551,19 +551,18 @@ describe('settings domain', () => {
|
||||
expect(error.details).toEqual({ ns })
|
||||
})
|
||||
|
||||
it('answers an unregistered namespace exactly like an unexposed one', async () => {
|
||||
// Deliberately indistinguishable: separating "does not exist" from
|
||||
// "exists but is not yours to configure" would let a caller enumerate the
|
||||
// registered namespaces one probe at a time.
|
||||
it('answers an unregistered namespace as the seam does, and a malformed one alike', async () => {
|
||||
// A name no registration answers and a name no registration could answer
|
||||
// fold into the same rejection: the proxy adds no boundary of its own, so
|
||||
// the seam's own refusal is the whole answer.
|
||||
const ctx = await harness()
|
||||
ctx.settings.register(NS, AdapterConfig)
|
||||
ctx.settings.register(settingsNamespace('some-other-plugin'), z.object({ secretPath: z.string() }))
|
||||
const api = createApiProxy(ctx, DEFAULTS)
|
||||
const unknown = expectErr(await api.settings.update(request({ ns: 'unknown-ns', patch: {} })))
|
||||
const unexposed = expectErr(await api.settings.update(request({ ns: 'some-other-plugin', patch: {} })))
|
||||
expect(unknown.code).toBe('settings-not-exposed')
|
||||
expect(unexposed.code).toBe(unknown.code)
|
||||
expect(unexposed.message.replace('some-other-plugin', 'unknown-ns')).toBe(unknown.message)
|
||||
const malformed = expectErr(await api.settings.update(request({ ns: 'Not A Namespace', patch: {} })))
|
||||
expect(unknown.code).toBe('settings-rejected')
|
||||
expect(unknown.message).toContain('is not registered')
|
||||
expect(malformed.code).toBe(unknown.code)
|
||||
})
|
||||
|
||||
it('maps a read-only provider refusal onto the same rejection', async () => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* turn/end cleared it.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
@@ -285,6 +285,45 @@ describe('mux live view computation', () => {
|
||||
expect(page.map(event => event.seq)).toEqual(page.map((_event, index) => third.seq + index))
|
||||
})
|
||||
|
||||
it('paginates a message with many provenance sources without variadic argument expansion', async () => {
|
||||
const { ctx } = await harness()
|
||||
const api = createApiProxy(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
||||
const session = ctx.sessions.create()
|
||||
ctx.agents.register({ id: session.id, session, status: 'idle', ctx } as Agent)
|
||||
session.append('turn/start', { turn: 1 })
|
||||
const sources = Array.from({ length: 128 }, (_unused, index) => session.append('assistant/chunk', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
chunk: { type: 'text-delta', index, text: 'x' },
|
||||
}).seq)
|
||||
const message = session.append('assistant/message', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: createMessage({
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'x'.repeat(sources.length) }],
|
||||
source: { kind: 'model', provider: 'p', model: 'm' },
|
||||
}),
|
||||
}, { surfaceOp: 'append', sourceEventSeqs: sources })
|
||||
|
||||
const scalarMin = Math.min
|
||||
const min = vi.spyOn(Math, 'min').mockImplementation((...values) => {
|
||||
if (values.length > 2) throw new RangeError('variadic minimum rejected by regression harness')
|
||||
return scalarMin(...values)
|
||||
})
|
||||
try {
|
||||
const response = await api.sessions.history({
|
||||
rpcId: RpcId('t-hist-large-provenance'),
|
||||
payload: { sessionId: session.id, maxMessages: 1 },
|
||||
})
|
||||
if (!response.result.ok) throw new Error('unreachable')
|
||||
expect(response.result.value.events.map(entry => entry.event.seq)).toEqual([...sources, message.seq])
|
||||
expect(response.result.value.hasMore).toBe(true)
|
||||
} finally {
|
||||
min.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('drops a disposed session from the live open-call table (result after dispose gets no view)', async () => {
|
||||
const { ctx } = await harness()
|
||||
const api = createApiProxy(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' })
|
||||
|
||||
Reference in New Issue
Block a user