Files
deepseek-harness/packages/preset/agent-presets/tests/discovery.spec.ts
Yichen Jiang 18fe174897 feat(agent-presets): compose each session's agent from a preset cordis.yml
A preset is a directory holding one `agent.cordis.yml`. Mounting it under an
agent's scope context during `setup(agentCtx)` gives that one session its own
tools and prompt sections while every other live session keeps its own.

No registry gains a tier. `dsh-tools` and `dsh-system-prompt` already file
registrations into the calling context's scope layer, and entry contexts chain
to the context a subtree was plugged into, so a composition mounted under
`agent.ctx` is that agent's alone and unwinds with it.

The mount audits itself because a directly-plugged subtree is absent from
`ctx.loader.entries()` and no boot audit covers it. It rejects an unscoped
target, a row that never became usable, and a row that published a service into
the root service realm — that last one is process-global rather than
per-session, and its collision with the next session surfaces as an unhandled
rejection `setup` never observes, leaving a half-composed agent that looks
healthy. The package invariant re-checks that rule on every service
notification, since a row publishing from a timer would escape a one-shot audit.

Raises the `packages/README.md` word ceiling from 920 to 980: the group table
must enumerate every group, and the new `preset/` row is necessary content.

Design: .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md
2026-08-06 21:03:18 +08:00

78 lines
2.9 KiB
TypeScript

import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { COMPOSITION_FILE, discoverPresets, scanRoot } from '@deepseek-ai/dsh-agent-presets'
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), 'fixtures')
const SYSTEM = { path: join(FIXTURES, 'system'), trust: 'system' as const }
const USER = { path: join(FIXTURES, 'user'), trust: 'user' as const }
describe('preset discovery', () => {
it('reports one preset per directory holding a composition, ordered by id', async () => {
const found = await scanRoot(SYSTEM)
expect(found.map(preset => preset.id)).toEqual(['minimal', 'standard'])
expect(found[0]).toEqual({
id: 'minimal',
trust: 'system',
path: join(SYSTEM.path, 'minimal', COMPOSITION_FILE),
})
})
it('skips a directory that holds no composition file', async () => {
const found = await scanRoot(USER)
expect(found.map(preset => preset.id)).not.toContain('not-a-preset')
})
it('records the root trust on every preset it discovers', async () => {
const found = await scanRoot(USER)
expect(found.every(preset => preset.trust === 'user')).toBe(true)
})
it('lets the earlier root win a duplicate id', async () => {
const found = await discoverPresets([SYSTEM, USER])
const standard = found.filter(preset => preset.id === 'standard')
expect(standard).toHaveLength(1)
expect(standard[0]?.trust).toBe('system')
})
it('treats an absent root as supplying no presets', async () => {
const found = await scanRoot({ path: join(FIXTURES, 'no-such-root'), trust: 'user' })
expect(found).toEqual([])
})
it('ignores a plain file sitting beside the preset directories', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-presets-'))
await writeFile(join(root, 'stray.yml'), '- id: x\n')
await mkdir(join(root, 'real'))
await writeFile(join(root, 'real', COMPOSITION_FILE), '[]\n')
const found = await scanRoot({ path: root, trust: 'user' })
expect(found.map(preset => preset.id)).toEqual(['real'])
})
it('reports a root it cannot read rather than treating it as empty', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-presets-'))
const notADirectory = join(root, 'file-as-root')
await writeFile(notADirectory, 'not a directory\n')
await expect(scanRoot({ path: notADirectory, trust: 'user' }))
.rejects.toThrow(/cannot read preset root/)
})
it('expands a leading tilde in a root path', async () => {
// `~` alone resolves to the home directory, which exists but holds no
// preset directories; the point is that it did not throw on a literal `~`.
const found = await scanRoot({ path: '~/.dsh-agent-presets-absent', trust: 'user' })
expect(found).toEqual([])
})
})