Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-8-authoring

# Conflicts:
#	packages/client/ui-agent-preset/tests/settings-store.spec.ts
#	packages/preset/agent-presets/tests/mount.spec.ts
This commit is contained in:
Yichen Jiang
2026-08-07 16:40:36 +08:00
2 changed files with 46 additions and 0 deletions

View File

@@ -229,6 +229,18 @@ describe('unary round trip', () => {
expect(appended.result.ok).toBe(true)
})
it('routes the agent-preset roster and switch through the wire', async () => {
const c = client(scriptedApi())
const listed = await c.agentPresets.list({})
expect(listed.result).toEqual({ ok: true, value: { presets: [] } })
// The switch carries the session it is about: the host refuses one whose
// conversation has started, and it can only know which by id.
const selected = await c.agentPresets.select({ sessionId: sid('s1'), agentPreset: 'standard' })
expect(selected.result).toEqual({ ok: true, value: { agentPreset: 'standard' } })
})
it('passes business errors through as 200 + err result, not a throw', async () => {
const api = scriptedApi({
sessions: {

View File

@@ -392,6 +392,40 @@ describe('replacing a composition', () => {
await expect(local.agentPresets.recompose(handle.agent.ctx, 'broken'))
.rejects.toThrow(/failed to mount/)
it('reports the switch failure when the restore fails too', async () => {
// A preset root this test owns, so removing the composition mid-flight
// cannot disturb the shipped fixtures.
const root = await mkdtemp(join(tmpdir(), 'dsh-preset-restore-'))
const seeded: [string, string][] = [['first', `- id: only\n name: ${join(FIXTURES, 'plugins', 'contribute.js')}\n config:\n tool: only\n`], ['broken', '- id: nope\n name: ./does-not-exist.js\n']]
for (const [id, body] of seeded) {
await mkdir(join(root, id))
await writeFile(join(root, id, COMPOSITION_FILE), body)
}
const scoped = new Context()
scoped.baseUrl = pathToFileURL(FIXTURES).href + '/'
await scoped.plugin(Loader)
scoped.loader.builtins.include = Include
await scoped.plugin(LlmService)
await scoped.plugin(SessionStore)
await scoped.plugin(SystemPrompt, { persona: '' })
await scoped.plugin(ToolRegistry)
await scoped.plugin(AgentRegistry)
await scoped.plugin(AgentLoop, { agents: [] })
await scoped.plugin(AgentPresets, { default: 'first', roots: [{ path: root, trust: 'user' as const }] })
const handle = await scoped.agents.create({
sessionId: SessionId('sess-restore-gone'),
setup: async (agentCtx: Context) => void await scoped.agentPresets.mount(agentCtx, 'first'),
})
// The roster is a live directory: the composition the agent came from can
// be gone by the time the restore reaches for it.
await rm(join(root, 'first'), { recursive: true })
await expect(scoped.agentPresets.recompose(handle.agent.ctx, 'broken'))
.rejects.toThrow(/failed to mount/)
// The switch failure is the actionable one; the restore's is swallowed.
expect(toolNames(scoped, handle.agent)).toEqual([])
})
it('refuses an unscoped context', async () => {