feat(web): expose the agent-preset roster over the API

`agentPreset.list` gives a browser the deployment's roster so it can offer a
choice when starting a session. Each row carries the id, its `trust`, and
whether it is the current default.

`trust` is on the wire deliberately: a `user` preset is exactly as privileged
as the plugins it names, so a surface that offers one alongside a shipped
preset can say which is which rather than presenting both as vetted.

The domain is read-only. A preset is a composition on disk, so authoring one
is a filesystem act rather than an RPC; and a deployment composing no presets
answers with an empty roster rather than an error, because sharing the host
composition is a valid deployment.

The RPC map made every registration site a type error, so the route, the
response-schema table, the service delegate, and the browser fixture are all
wired rather than only the ones I remembered.
This commit is contained in:
Yichen Jiang
2026-08-04 00:15:37 +08:00
parent 8d06b2d576
commit e6fe32b3c3
16 changed files with 159 additions and 2 deletions
@@ -235,3 +235,30 @@ describe('a capability the session\'s preset mounts', () => {
expect(failure.error.message).toContain('neither this session')
})
})
describe('agentPreset.list', () => {
it('marks the default and carries each preset\'s trust', async () => {
const { api } = await harness(['standard', 'core-web'])
const response = await api.agentPresets.list(request({}))
expect(response.result.ok).toBe(true)
if (!response.result.ok) throw new Error('unreachable')
expect(response.result.value.presets).toEqual([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
])
})
it('answers with an empty roster when the deployment composes no presets', async () => {
const { api } = await harness()
const response = await api.agentPresets.list(request({}))
// Composing no presets is a valid deployment, not an error: every session
// then shares the host composition and the browser offers no choice.
expect(response.result.ok).toBe(true)
if (!response.result.ok) throw new Error('unreachable')
expect(response.result.value.presets).toEqual([])
})
})