Quellcode durchsuchen

test: cover skill config defaults

Yichen Jiang vor 3 Monaten
Ursprung
Commit
ad14b210bf

+ 35 - 0
packages/core/agent-core/tests/agent-core.spec.ts

@@ -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')

+ 4 - 0
packages/core/skill/tests/skill.spec.ts

@@ -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'])

+ 36 - 0
packages/ui/acp-agent/tests/acp-agent.spec.ts

@@ -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([])

+ 36 - 0
packages/ui/stdio-agent/tests/stdio-agent.spec.ts

@@ -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 —