test: cover skill config defaults

This commit is contained in:
Yichen Jiang
2026-07-05 21:46:38 +08:00
parent c2b0278185
commit ad14b210bf
4 changed files with 111 additions and 0 deletions

View File

@@ -43,6 +43,27 @@ async function mount(config?: agentCore.Config): Promise<Context> {
}
}
async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
const oldDshHome = process.env.DSH_HOME
const oldAgentsHome = process.env.DSH_AGENTS_HOME
process.env.DSH_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-core-home-'))
process.env.DSH_AGENTS_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-core-agents-'))
try {
return await run()
} finally {
if (oldDshHome === undefined) {
delete process.env.DSH_HOME
} else {
process.env.DSH_HOME = oldDshHome
}
if (oldAgentsHome === undefined) {
delete process.env.DSH_AGENTS_HOME
} else {
process.env.DSH_AGENTS_HOME = oldAgentsHome
}
}
}
describe('dsh-agent-core bundle', () => {
it('brings up the full providerless spine', async () => {
const ctx = await mount()
@@ -100,6 +121,20 @@ describe('dsh-agent-core bundle', () => {
await ctx.fiber.dispose()
})
it('uses the default skill config when apply is called directly without skills', async () => {
await withIsolatedSkillHomes(async () => {
const ctx = new Context()
agentCore.apply(ctx, { agents: [] })
await new Promise(resolve => setTimeout(resolve, 50))
expect(ctx.skills).toBeDefined()
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(expect.arrayContaining([
'dsh-plugin-creator',
'dsh-skill-creator',
]))
await ctx.fiber.dispose()
})
})
it('re-exports the loop config schema as its own', () => {
expect(agentCore.Config).toBeDefined()
expect(agentCore.name).toBe('agent-core')

View File

@@ -24,6 +24,7 @@ async function writeFlatSkill(root: string, name: string, description: string, b
class TestFileSystem extends FileSystem {
listDirCalls = 0
failResolvePaths = new Set<string>()
failStatPaths = new Set<string>()
override async resolve(path: string): Promise<FsTarget> {
if (this.failResolvePaths.has(path)) throw new Error('resolve failed')
@@ -31,6 +32,7 @@ class TestFileSystem extends FileSystem {
}
override async stat(target: FsTarget): Promise<FsInfo | undefined> {
if (this.failStatPaths.has(target.displayPath)) throw new Error('stat failed')
try {
const fs = await import('node:fs/promises')
const info = await fs.stat(target.displayPath)
@@ -429,6 +431,7 @@ describe('SkillService', () => {
const root = join(home, '.dsh/skills')
await writeFlatSkill(root, 'text-skill', 'Text skill', 'Text body.')
await writeFlatSkill(root, 'resolve-fail', 'Resolve fail', 'Resolve body.')
await writeFlatSkill(root, 'stat-fail', 'Stat fail', 'Stat body.')
await mkdir(join(root, 'empty-dir'), { recursive: true })
await mkdir(join(root, 'directory-skill/SKILL.md'), { recursive: true })
await writeFile(join(root, 'binary-skill.md'), Buffer.concat([
@@ -441,6 +444,7 @@ describe('SkillService', () => {
await ctx.plugin(TestFileSystem)
const fs = ctx.fs as TestFileSystem
fs.failResolvePaths.add(join(root, 'resolve-fail.md'))
fs.failStatPaths.add(join(root, 'stat-fail.md'))
await ctx.plugin(SkillService, { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), installSystemSkills: false })
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(['text-skill'])

View File

@@ -30,6 +30,28 @@ async function isolatedSkillsConfig(): Promise<NonNullable<acpAgent.Config['skil
return { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), installSystemSkills: false }
}
async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
const oldDshHome = process.env.DSH_HOME
const oldAgentsHome = process.env.DSH_AGENTS_HOME
const home = await mkdtemp(join(tmpdir(), 'dsh-acp-agent-default-skills-'))
process.env.DSH_HOME = join(home, '.dsh')
process.env.DSH_AGENTS_HOME = join(home, '.agents')
try {
return await run()
} finally {
if (oldDshHome === undefined) {
delete process.env.DSH_HOME
} else {
process.env.DSH_HOME = oldDshHome
}
if (oldAgentsHome === undefined) {
delete process.env.DSH_AGENTS_HOME
} else {
process.env.DSH_AGENTS_HOME = oldAgentsHome
}
}
}
describe('dsh-acp-agent composition', () => {
it('brings up the spine + persistence + the ACP bridge', async () => {
const ctx = await mount({ model: 'mock', systemPrompt: 'hi', persistenceRoot: '/tmp/dsh-acp-agent-test', skills: await isolatedSkillsConfig() })
@@ -54,6 +76,20 @@ describe('dsh-acp-agent composition', () => {
await ctx.fiber.dispose()
})
it('uses default skill config when apply is called directly without skills', async () => {
await withIsolatedSkillHomes(async () => {
const ctx = new Context()
acpAgent.apply(ctx, { model: 'mock', systemPrompt: 'hi' })
await new Promise(resolve => setTimeout(resolve, 50))
expect(ctx.skills).toBeDefined()
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(expect.arrayContaining([
'dsh-plugin-creator',
'dsh-skill-creator',
]))
await ctx.fiber.dispose()
})
})
it('forwards skill config into agent-core', async () => {
const ctx = await mount({ model: 'mock', systemPrompt: 'hi', skills: await isolatedSkillsConfig() })
expect(await ctx.skills.list()).toEqual([])

View File

@@ -36,6 +36,28 @@ async function isolatedSkillsConfig(): Promise<NonNullable<stdioAgent.Config['sk
return { dshHome: join(home, '.dsh'), agentsHome: join(home, '.agents'), installSystemSkills: false }
}
async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
const oldDshHome = process.env.DSH_HOME
const oldAgentsHome = process.env.DSH_AGENTS_HOME
const home = await mkdtemp(join(tmpdir(), 'dsh-stdio-agent-default-skills-'))
process.env.DSH_HOME = join(home, '.dsh')
process.env.DSH_AGENTS_HOME = join(home, '.agents')
try {
return await run()
} finally {
if (oldDshHome === undefined) {
delete process.env.DSH_HOME
} else {
process.env.DSH_HOME = oldDshHome
}
if (oldAgentsHome === undefined) {
delete process.env.DSH_AGENTS_HOME
} else {
process.env.DSH_AGENTS_HOME = oldAgentsHome
}
}
}
describe('dsh-stdio-agent app', () => {
it('composes the spine + front-door cluster and pre-creates the main agent', async () => {
const ctx = await mount({ model: 'mock', systemPrompt: 'hi', persistenceRoot: '/tmp/dsh-stdio-agent-spec', skills: await isolatedSkillsConfig() })
@@ -63,6 +85,20 @@ describe('dsh-stdio-agent app', () => {
await ctx.fiber.dispose()
})
it('uses default skill config when apply is called directly without skills', async () => {
await withIsolatedSkillHomes(async () => {
const ctx = new Context()
stdioAgent.apply(ctx, { model: 'mock', systemPrompt: 'hi' })
await new Promise(resolve => setTimeout(resolve, 80))
expect(ctx.skills).toBeDefined()
expect((await ctx.skills.list()).map(skill => skill.name)).toEqual(expect.arrayContaining([
'dsh-plugin-creator',
'dsh-skill-creator',
]))
await ctx.fiber.dispose()
})
})
it('forwards resumeSessionId onto the pre-created agent when set', async () => {
// A resume id defers agent creation until persistence loads; with no backing
// session the resume is contained + logged, so no `main` agent registers —