diff --git a/packages/core/agent-core/tests/agent-core.spec.ts b/packages/core/agent-core/tests/agent-core.spec.ts index 75f0f5eef9..08bea5a573 100644 --- a/packages/core/agent-core/tests/agent-core.spec.ts +++ b/packages/core/agent-core/tests/agent-core.spec.ts @@ -43,6 +43,27 @@ async function mount(config?: agentCore.Config): Promise { } } +async function withIsolatedSkillHomes(run: () => Promise): Promise { + 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') diff --git a/packages/core/skill/tests/skill.spec.ts b/packages/core/skill/tests/skill.spec.ts index ef5d314190..183954ccdf 100644 --- a/packages/core/skill/tests/skill.spec.ts +++ b/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() + failStatPaths = new Set() override async resolve(path: string): Promise { if (this.failResolvePaths.has(path)) throw new Error('resolve failed') @@ -31,6 +32,7 @@ class TestFileSystem extends FileSystem { } override async stat(target: FsTarget): Promise { + 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']) diff --git a/packages/ui/acp-agent/tests/acp-agent.spec.ts b/packages/ui/acp-agent/tests/acp-agent.spec.ts index 98b5cee517..fd38670713 100644 --- a/packages/ui/acp-agent/tests/acp-agent.spec.ts +++ b/packages/ui/acp-agent/tests/acp-agent.spec.ts @@ -30,6 +30,28 @@ async function isolatedSkillsConfig(): Promise(run: () => Promise): Promise { + 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([]) diff --git a/packages/ui/stdio-agent/tests/stdio-agent.spec.ts b/packages/ui/stdio-agent/tests/stdio-agent.spec.ts index ba19bf9718..1eb32e62fe 100644 --- a/packages/ui/stdio-agent/tests/stdio-agent.spec.ts +++ b/packages/ui/stdio-agent/tests/stdio-agent.spec.ts @@ -36,6 +36,28 @@ async function isolatedSkillsConfig(): Promise(run: () => Promise): Promise { + 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 —