Files
deepseek-harness/packages/client/ui-agent-preset/tests/settings-store.spec.ts
T
Yichen Jiang d099a24cb1 fix(web): resume the preset the log records, and serialize the switch
Six review findings on the select surface, all reachable from the wire:

**Resume read the header, not the log.** The switch was recorded as
`agent-preset/selected` and every projection resolved from it, but `agentFor`
still composed from `inspected.meta.agentPreset` — the value written once at
creation. A blank session that switched and then ran turns came back after a
restart under the ORIGINAL preset, restoring that history under the tool set it
was not produced with, which is the mismatch this feature exists to prevent.
`inspected` already carries the events.

**Cold summaries dropped the preset entirely.** `summarizeCold` hand-copied
three header fields and omitted the fourth, so a restored session reported no
preset and the picker showed the deployment default. It now uses the same
projection the attached path does.

**`select` had no gate.** Two concurrent selects both passed the blank check;
the second `unmountPresetFor` then found no record, because the first had
already removed it, and both mounts installed into one agent layer. Selects on
one session now queue, and the blank check is re-read inside the queue. This is
not turn admission — a `session.prompt` racing a switch is the agent loop's to
reserve — but it closes the select-versus-select tear-down.

**A same-id restore was skipped.** The roster is a live directory, so "the same
inputs that worked a moment ago" does not hold: a changed file is exactly how a
same-id reselect fails, and skipping the restore left the agent with no
composition at all.

**`writable` was dead state**, initialized true and never set, so the row could
never disable. It now carries `settings.describe`'s bit — a browser that may
not write settings sees the current default and no control, rather than one
whose write answers `settings-not-exposed`.

**`list` was documented as id-ordered.** It is root-precedence order with each
root's own presets sorted, first root to supply an id winning.
2026-08-07 11:45:10 +08:00

244 lines
9.4 KiB
TypeScript

/**
* The agent-preset settings controller: it derives both the options and the
* current default from one roster call, writes only the `default` field, and
* treats an empty roster as "this deployment composes no presets" rather than
* as a failure.
*/
import { describe, expect, it } from 'vitest'
import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client'
import {
AGENT_PRESET_SETTINGS_NS, AgentPresetSettingsController,
} from '../src/client/settings-store.ts'
import { AgentPresetSeatController } from '../src/client/seat-store.ts'
interface Recorded { ns: string; patch: unknown }
/** A client whose roster and write outcome the test controls. */
function fakeApi(
presets: { id: string; trust: 'system' | 'user'; isDefault: boolean }[],
options: { writes?: Recorded[]; failWrite?: string; failList?: string; readOnly?: boolean } = {},
): IApiClient {
return {
agentPresets: {
list: () => Promise.resolve(options.failList === undefined
? { rpcId: 'r', result: { ok: true as const, value: { presets } } }
: { rpcId: 'r', result: { ok: false as const, error: { code: 'internal', message: options.failList, details: {} } } }),
},
settings: {
// Loopback-only in production; 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: [] },
},
}),
update: (payload: { ns: string; patch: unknown }) => {
options.writes?.push({ ns: payload.ns, patch: payload.patch })
if (options.failWrite !== undefined) {
return Promise.resolve({ rpcId: 'r', result: { ok: false as const, error: { code: 'internal', message: options.failWrite, details: {} } } })
}
// A committed write moves the roster's default, exactly as the host does.
for (const preset of presets) {
preset.isDefault = preset.id === (payload.patch as { default?: string }).default
}
return Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: {} } })
},
},
} as unknown as IApiClient
}
describe('the agent-preset settings controller', () => {
it('disables the control when this browser may not write settings', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
], { readOnly: true }))
await controller.load()
// `settings.describe` is loopback-only and reports a read-only provider;
// offering a control whose write answers `settings-not-exposed` would
// promise a switch the host refuses.
expect(controller.store.getSnapshot().writable).toBe(false)
expect(controller.store.getSnapshot().currentValue).toBe('standard')
})
it('derives options and the current default from one roster call', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'mine', trust: 'user', isDefault: false },
]))
await controller.load()
const state = controller.store.getSnapshot()
expect(state.status).toBe('ready')
expect(state.currentValue).toBe('standard')
expect(state.options).toEqual([
{ id: 'standard', trust: 'system' },
{ id: 'mine', trust: 'user' },
])
})
it('reports an empty roster as unavailable, not as an error', async () => {
const controller = new AgentPresetSettingsController(fakeApi([]))
await controller.load()
// A deployment composing no presets is valid: every session shares the
// host composition and the row renders nothing.
expect(controller.store.getSnapshot().status).toBe('unavailable')
expect(controller.store.getSnapshot().error).toBeNull()
})
it('writes only the default field, into the agent-presets namespace', async () => {
const writes: Recorded[] = []
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
], { writes }))
await controller.load()
await controller.select('core-web')
expect(writes).toEqual([{ ns: AGENT_PRESET_SETTINGS_NS, patch: { default: 'core-web' } }])
expect(controller.store.getSnapshot().currentValue).toBe('core-web')
})
it('restores the previous value and surfaces the message when the write fails', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
], { failWrite: 'read-only settings' }))
await controller.load()
await controller.select('core-web')
const state = controller.store.getSnapshot()
expect(state.currentValue).toBe('standard')
expect(state.error).toBe('read-only settings')
expect(state.status).toBe('ready')
})
it('ignores a pick that is already the default', async () => {
const writes: Recorded[] = []
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
], { writes }))
await controller.load()
await controller.select('standard')
expect(writes).toEqual([])
})
it('surfaces a roster failure without claiming the deployment has no presets', async () => {
const controller = new AgentPresetSettingsController(fakeApi([], { failList: 'host down' }))
await controller.load()
const state = controller.store.getSnapshot()
expect(state.status).toBe('error')
expect(state.error).toBe('host down')
})
})
describe('the composer seat controller', () => {
/** A seat over a fixed session summary. */
function seat(
presets: { id: string; trust: 'system' | 'user'; isDefault: boolean }[],
summary: { blank: boolean; agentPreset?: string } | undefined,
options: { writes?: Recorded[]; failSelect?: string } = {},
): AgentPresetSeatController {
const api = {
agentPresets: {
list: () => Promise.resolve({ rpcId: 'r', result: { ok: true as const, value: { presets } } }),
select: (payload: { agentPreset: string }) => {
options.writes?.push({ ns: 'select', patch: payload.agentPreset })
return Promise.resolve(options.failSelect === undefined
? { rpcId: 'r', result: { ok: true as const, value: { agentPreset: payload.agentPreset } } }
: { rpcId: 'r', result: { ok: false as const, error: { code: 'agent-preset-locked', message: options.failSelect, details: {} } } })
},
},
} as unknown as IApiClient
return new AgentPresetSeatController(api, 's1' as never, () => summary)
}
const ROSTER: { id: string; trust: 'system' | 'user'; isDefault: boolean }[] = [
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
]
it('shows what the session runs, not the deployment default', async () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'core-web' })
await controller.load()
// A resumed session runs what it was created with; showing `standard`
// because it is the current default would be a lie about this session.
expect(controller.store.getSnapshot().current).toBe('core-web')
expect(controller.store.getSnapshot().switchable).toBe(true)
})
it('falls back to the roster default when the session records none', async () => {
const controller = seat(ROSTER, { blank: true })
await controller.load()
expect(controller.store.getSnapshot().current).toBe('standard')
})
it('is not switchable once the conversation has started', async () => {
const controller = seat(ROSTER, { blank: false, agentPreset: 'standard' })
await controller.load()
expect(controller.store.getSnapshot().switchable).toBe(false)
})
it('refuses to switch a session that already started', async () => {
const writes: Recorded[] = []
const controller = seat(ROSTER, { blank: false, agentPreset: 'standard' }, { writes })
await controller.load()
await controller.select('core-web')
// The host enforces the same rule; the seat simply never asks.
expect(writes).toEqual([])
expect(controller.store.getSnapshot().current).toBe('standard')
})
it('switches a blank session and keeps the host\'s answer', async () => {
const writes: Recorded[] = []
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { writes })
await controller.load()
await controller.select('core-web')
expect(writes).toEqual([{ ns: 'select', patch: 'core-web' }])
expect(controller.store.getSnapshot().current).toBe('core-web')
})
it('restores the previous value when the host rejects the switch', async () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { failSelect: 'already started' })
await controller.load()
await controller.select('core-web')
const state = controller.store.getSnapshot()
expect(state.current).toBe('standard')
expect(state.error).toBe('already started')
})
it('reports no options when the session is unknown to the list yet', async () => {
const controller = seat([], undefined)
await controller.load()
expect(controller.store.getSnapshot().options).toEqual([])
expect(controller.store.getSnapshot().switchable).toBe(false)
})
})