fix(agent-core): resolve user skills from OS home directory, not kimi home (#72)

* fix(agent-core): resolve user skills from OS home directory, not kimi home

KimiCore incorrectly used the kimi home directory (e.g. ~/.kimi-code) as
userHomeDir, causing the skill scanner to look for user skills under
~/.kimi-code/.agents/skills/ instead of ~/.agents/skills/. Only the
builtin mcp-config skill was found.

Fix by always setting userHomeDir to homedir() (the actual OS home),
independent of the homeDir / KIMI_CODE_HOME options that control where
session data is stored.

* chore: add changeset for skill user home dir fix

* test(node-sdk): align SDK skill test with OS home resolution fix

* Apply suggestion from @liruifengv

Signed-off-by: liruifengv <liruifeng1024@gmail.com>

---------

Signed-off-by: liruifengv <liruifeng1024@gmail.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
This commit is contained in:
happy wang 2026-05-26 17:50:38 +08:00 committed by GitHub
parent bddc60f0e9
commit 0ce0072cb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 16 additions and 11 deletions

View file

@ -0,0 +1,6 @@
---
"@moonshot-ai/agent-core": patch
"@moonshot-ai/kimi-code": patch
---
Fix user skills in ~/.agents/ not being loaded.

View file

@ -114,9 +114,8 @@ export class KimiCore implements PromisableMethods<CoreAPI> {
protected readonly rpcClient: CoreRPCClient,
options: KimiCoreOptions = {},
) {
const explicitHomeDir = options.homeDir ?? process.env['KIMI_CODE_HOME'];
this.homeDir = resolveKimiHome(options.homeDir);
this.userHomeDir = explicitHomeDir ?? homedir();
this.userHomeDir = homedir();
this.configPath = resolveConfigPath({
homeDir: this.homeDir,
configPath: options.configPath,

View file

@ -100,7 +100,7 @@ describe('HarnessAPI session skills', () => {
expect(JSON.stringify(skills)).not.toContain('Your tool list contains one synthetic tool');
});
it('uses the explicit core home as user skill home instead of the process home', async () => {
it('resolves user skills from the OS home directory, not from the kimi home', async () => {
const processHome = join(tmp, 'process-home');
vi.stubEnv('HOME', processHome);
await writeUserSkill(processHome, 'real-home-only', 'Real home skill');
@ -110,11 +110,11 @@ describe('HarnessAPI session skills', () => {
const names = new Set((await rpc.listSkills({ sessionId: created.id })).map((skill) => skill.name));
expect(names.has('sandbox-only')).toBe(true);
expect(names.has('real-home-only')).toBe(false);
expect(names.has('real-home-only')).toBe(true);
expect(names.has('sandbox-only')).toBe(false);
});
it('uses KIMI_CODE_HOME as user skill home when core home comes from env', async () => {
it('resolves user skills from the OS home directory even when KIMI_CODE_HOME is set', async () => {
const processHome = join(tmp, 'env-process-home');
vi.stubEnv('HOME', processHome);
vi.stubEnv('KIMI_CODE_HOME', homeDir);
@ -125,8 +125,8 @@ describe('HarnessAPI session skills', () => {
const names = new Set((await rpc.listSkills({ sessionId: created.id })).map((skill) => skill.name));
expect(names.has('env-sandbox-only')).toBe(true);
expect(names.has('env-real-home-only')).toBe(false);
expect(names.has('env-real-home-only')).toBe(true);
expect(names.has('env-sandbox-only')).toBe(false);
});
it('activates an inline skill through core and records display origin metadata', async () => {

View file

@ -192,7 +192,7 @@ describe('Session skills', () => {
}
});
it('uses KIMI_CODE_HOME as the user skill home through the SDK harness', async () => {
it('resolves user skills from the OS home directory, independently of KIMI_CODE_HOME', async () => {
const homeDir = await makeTempDir(tempDirs, 'kimi-sdk-skills-home-');
const processHome = await makeTempDir(tempDirs, 'kimi-sdk-skills-process-home-');
const workDir = await makeTempDir(tempDirs, 'kimi-sdk-skills-work-');
@ -206,8 +206,8 @@ describe('Session skills', () => {
const session = await harness.createSession({ id: 'ses_sdk_skill_env_home', workDir });
const names = new Set((await session.listSkills()).map((skill) => skill.name));
expect(names.has('sdk-sandbox-only')).toBe(true);
expect(names.has('sdk-real-home-only')).toBe(false);
expect(names.has('sdk-real-home-only')).toBe(true);
expect(names.has('sdk-sandbox-only')).toBe(false);
} finally {
await harness.close();
}