Download packages/agent-core-v2/test/features/skill/activateSkill.test.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 6.85 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/agent-core-v2/test/features/skill/activateSkill.test.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/agent-core-v2/test/features/skill/activateSkill.test.ts
-
curl -L -o activateSkill.test.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/agent-core-v2/test/features/skill/activateSkill.test.ts
6.85 kB
| import { afterEach, describe, expect, it } from 'vitest'; | |
| import { InMemorySkillCatalog } from '#/features/skill/catalog/registry'; | |
| import { stubSkill } from './catalog/stubs'; | |
| import { createTestAgent, skillServices, type TestAgentContext } from '../../harness'; | |
| describe('activateSkill', () => { | |
| let ctx: TestAgentContext; | |
| afterEach(async () => { | |
| try { | |
| await ctx.expectResumeMatches(); | |
| } finally { | |
| await ctx.dispose(); | |
| } | |
| }); | |
| function agentWithCommitSkill(): TestAgentContext { | |
| const catalog = new InMemorySkillCatalog(); | |
| catalog.register(stubSkill('commit', { content: '# Commit body' })); | |
| return createTestAgent(skillServices(catalog)); | |
| } | |
| it('launches a turn with the rendered skill prompt and returns its id', async () => { | |
| ctx = agentWithCommitSkill(); | |
| ctx.mockNextResponse({ type: 'text', text: 'committed' }); | |
| const launched = await ctx.rpc.activateSkill({ name: 'commit', args: '-m fix' }); | |
| expect(launched?.turn_id).toBe(0); | |
| await ctx.untilTurnEnd(); | |
| const llmInput = JSON.stringify(ctx.llmInputs()); | |
| expect(llmInput).toContain('skill-loaded'); | |
| expect(llmInput).toContain('# Commit body'); | |
| expect(llmInput).toContain('ARGUMENTS: -m fix'); | |
| }); | |
| it('rejects for an unknown skill instead of failing silently', async () => { | |
| ctx = agentWithCommitSkill(); | |
| await expect(ctx.rpc.activateSkill({ name: 'missing' })).rejects.toThrow(/not found/i); | |
| }); | |
| }); | |
| describe('promptWithSkills', () => { | |
| let ctx: TestAgentContext; | |
| afterEach(async () => { | |
| try { | |
| await ctx.expectResumeMatches(); | |
| } finally { | |
| await ctx.dispose(); | |
| } | |
| }); | |
| function agentWithSkills(): TestAgentContext { | |
| const catalog = new InMemorySkillCatalog(); | |
| catalog.register(stubSkill('review', { content: '# Review body' })); | |
| catalog.register(stubSkill('security', { content: '# Security body' })); | |
| return createTestAgent(skillServices(catalog)); | |
| } | |
| it('bundles every skill into the prompt message and launches exactly one turn', async () => { | |
| ctx = agentWithSkills(); | |
| ctx.mockNextResponse({ type: 'text', text: 'done' }); | |
| const launched = await ctx.rpc.promptWithSkills({ | |
| input: [{ type: 'text', text: 'Review this change.' }], | |
| skills: [{ name: 'review' }, { name: 'security' }], | |
| }); | |
| expect(launched.turn_id).toBe(0); | |
| expect(launched.prompt_id).toBeTruthy(); | |
| expect(launched.state).toBe('running'); | |
| await ctx.untilTurnEnd(); | |
| expect(ctx.llmCalls).toHaveLength(1); | |
| const llmInput = JSON.stringify(ctx.llmInputs()); | |
| expect(llmInput).toContain('# Review body'); | |
| expect(llmInput).toContain('# Security body'); | |
| expect(llmInput).toContain('Review this change.'); | |
| const messages = ctx.context.get(); | |
| const promptMessage = messages.find((message) => message.origin?.kind === 'user'); | |
| expect(messages.filter((message) => message.origin?.kind === 'skill_activation')).toHaveLength( | |
| 0, | |
| ); | |
| expect(promptMessage?.origin).toMatchObject({ | |
| kind: 'user', | |
| skillActivations: [{ skillName: 'review' }, { skillName: 'security' }], | |
| }); | |
| const texts = promptMessage?.content | |
| .filter((part) => part.type === 'text') | |
| .map((part) => part.text); | |
| expect(texts?.[0]).toContain('# Review body'); | |
| expect(texts?.[1]).toContain('# Security body'); | |
| expect(texts?.[2]).toContain('Review this change.'); | |
| const events = ctx.allEvents.filter( | |
| (event) => | |
| event.type === '[rpc]' && | |
| (event.event === 'skill.activated' || event.event === 'turn.started'), | |
| ); | |
| expect(events.map((event) => event.event)).toEqual([ | |
| 'skill.activated', | |
| 'skill.activated', | |
| 'turn.started', | |
| ]); | |
| expect( | |
| events | |
| .slice(0, 2) | |
| .map((event) => (event.args as { readonly skillName?: string }).skillName), | |
| ).toEqual(['review', 'security']); | |
| const started = events[2]?.args as { readonly prompt?: string }; | |
| expect(started.prompt).toBe('Review this change.'); | |
| }); | |
| it('rejects the whole submission when any skill is unknown', async () => { | |
| ctx = agentWithSkills(); | |
| await expect( | |
| ctx.rpc.promptWithSkills({ | |
| input: [{ type: 'text', text: 'Review this change.' }], | |
| skills: [{ name: 'review' }, { name: 'missing' }], | |
| }), | |
| ).rejects.toThrow(/not found/i); | |
| expect(ctx.llmCalls).toHaveLength(0); | |
| expect(ctx.context.get()).toHaveLength(0); | |
| expect( | |
| ctx.allEvents.some((event) => event.type === '[rpc]' && event.event === 'skill.activated'), | |
| ).toBe(false); | |
| }); | |
| it('rejects a grouped submission with an empty prompt message', async () => { | |
| ctx = agentWithSkills(); | |
| await expect( | |
| ctx.rpc.promptWithSkills({ | |
| input: [], | |
| skills: [{ name: 'review' }], | |
| }), | |
| ).rejects.toThrow(/non-empty prompt/i); | |
| expect(ctx.llmCalls).toHaveLength(0); | |
| expect(ctx.context.get()).toHaveLength(0); | |
| expect( | |
| ctx.allEvents.some((event) => event.type === '[rpc]' && event.event === 'skill.activated'), | |
| ).toBe(false); | |
| }); | |
| it('rejects a grouped submission without any skills', async () => { | |
| ctx = agentWithSkills(); | |
| await expect( | |
| ctx.rpc.promptWithSkills({ | |
| input: [{ type: 'text', text: 'Review this change.' }], | |
| skills: [], | |
| }), | |
| ).rejects.toThrow(/at least one skill/i); | |
| expect(ctx.llmCalls).toHaveLength(0); | |
| expect(ctx.context.get()).toHaveLength(0); | |
| expect( | |
| ctx.allEvents.some((event) => event.type === '[rpc]' && event.event === 'skill.activated'), | |
| ).toBe(false); | |
| }); | |
| it('undoes the bundled prompt as a single anchor', async () => { | |
| ctx = agentWithSkills(); | |
| await ctx.restorePersisted(); | |
| ctx.mockNextResponse({ type: 'text', text: 'done' }); | |
| await ctx.rpc.promptWithSkills({ | |
| input: [{ type: 'text', text: 'Review this change.' }], | |
| skills: [{ name: 'review' }, { name: 'security' }], | |
| }); | |
| await ctx.untilTurnEnd(); | |
| expect(ctx.context.get().length).toBeGreaterThan(0); | |
| const undone = await ctx.rpc.undoHistory({ count: 1 }); | |
| expect(undone).toBe(1); | |
| expect(ctx.context.get()).toHaveLength(0); | |
| }); | |
| it('accepts later prompt_id reuse of a bundled prompt id as a pure correlation id', async () => { | |
| ctx = agentWithSkills(); | |
| ctx.mockNextResponse({ type: 'text', text: 'done' }); | |
| const launched = await ctx.rpc.promptWithSkills({ | |
| input: [{ type: 'text', text: 'Review this change.' }], | |
| skills: [{ name: 'review' }], | |
| }); | |
| await ctx.untilTurnEnd(); | |
| ctx.mockNextResponse({ type: 'text', text: 'again done' }); | |
| const reused = await ctx.rpc.prompt({ | |
| input: [{ type: 'text', text: 'again' }], | |
| promptId: launched.prompt_id, | |
| }); | |
| expect(reused).toEqual({ turn_id: 1 }); | |
| await ctx.untilTurnEnd(); | |
| }); | |
| }); | |