From 73f77fcc4740959c745e2861112f8c3ca27f06cc Mon Sep 17 00:00:00 2001 From: 7Sageer <7sageer@djwcb.cn> Date: Fri, 17 Jul 2026 18:50:26 +0800 Subject: [PATCH] fix(agent-core-v2): follow symlinks when loading AGENTS.md --- .changeset/fix-agents-md-symlink.md | 5 +++++ .../src/agent/profile/context.ts | 2 +- .../os/backends/node-local/hostFsService.ts | 5 +++-- .../src/os/interface/hostFileSystem.ts | 3 ++- .../test/agent/profile/context.test.ts | 22 ++++++++++++++++++- 5 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 .changeset/fix-agents-md-symlink.md diff --git a/.changeset/fix-agents-md-symlink.md b/.changeset/fix-agents-md-symlink.md new file mode 100644 index 0000000000..c33b30f6f8 --- /dev/null +++ b/.changeset/fix-agents-md-symlink.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix AGENTS.md files installed as symbolic links being ignored by the web backend. diff --git a/packages/agent-core-v2/src/agent/profile/context.ts b/packages/agent-core-v2/src/agent/profile/context.ts index 59f845c18f..9a3ad74669 100644 --- a/packages/agent-core-v2/src/agent/profile/context.ts +++ b/packages/agent-core-v2/src/agent/profile/context.ts @@ -197,7 +197,7 @@ async function pathExists(deps: ProfileContextDeps, path: string): Promise { try { - const stat = await deps.fs.stat(path); + const stat = await deps.fs.stat(path, { followSymlinks: true }); return stat.isFile; } catch { return false; diff --git a/packages/agent-core-v2/src/os/backends/node-local/hostFsService.ts b/packages/agent-core-v2/src/os/backends/node-local/hostFsService.ts index 8d911ce5d7..b4db3dce2d 100644 --- a/packages/agent-core-v2/src/os/backends/node-local/hostFsService.ts +++ b/packages/agent-core-v2/src/os/backends/node-local/hostFsService.ts @@ -14,6 +14,7 @@ import { mkdir, realpath as nodeRealpath, rm, + stat as nodeStat, writeFile, } from 'node:fs/promises'; @@ -187,9 +188,9 @@ export class HostFileSystem implements IHostFileSystem { } } - async stat(path: string): Promise { + async stat(path: string, options?: { followSymlinks?: boolean }): Promise { try { - const s = await lstat(path); + const s = options?.followSymlinks === true ? await nodeStat(path) : await lstat(path); return { isFile: s.isFile(), isDirectory: s.isDirectory(), diff --git a/packages/agent-core-v2/src/os/interface/hostFileSystem.ts b/packages/agent-core-v2/src/os/interface/hostFileSystem.ts index 9f1eae9983..abcbd71945 100644 --- a/packages/agent-core-v2/src/os/interface/hostFileSystem.ts +++ b/packages/agent-core-v2/src/os/interface/hostFileSystem.ts @@ -44,7 +44,8 @@ export interface IHostFileSystem { options?: { encoding?: BufferEncoding; errors?: TextDecodeErrors }, ): AsyncGenerator; createExclusive(path: string, data: Uint8Array): Promise; - stat(path: string): Promise; + /** Stats the entry itself (Node `lstat` semantics) unless `followSymlinks` resolves links to their target. */ + stat(path: string, options?: { followSymlinks?: boolean }): Promise; readdir(path: string): Promise; mkdir(path: string, options?: { readonly recursive?: boolean }): Promise; remove(path: string): Promise; diff --git a/packages/agent-core-v2/test/agent/profile/context.test.ts b/packages/agent-core-v2/test/agent/profile/context.test.ts index 4a5d28d608..15fbfd770b 100644 --- a/packages/agent-core-v2/test/agent/profile/context.test.ts +++ b/packages/agent-core-v2/test/agent/profile/context.test.ts @@ -1,4 +1,4 @@ -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'pathe'; @@ -75,6 +75,26 @@ describe('loadAgentsMd user-level discovery', () => { }); }); +describe('loadAgentsMd symlinked files', () => { + it('follows symlinks when loading user-level and project-level AGENTS.md', async () => { + const targetDir = await mkdtemp(join(tmpdir(), 'kimi-agents-target-')); + extraDirs.push(targetDir); + const brandTarget = join(targetDir, 'brand-AGENTS.md'); + const projectTarget = join(targetDir, 'project-AGENTS.md'); + await writeFile(brandTarget, 'brand via symlink', 'utf-8'); + await writeFile(projectTarget, 'project via symlink', 'utf-8'); + + await mkdir(join(homeDir, '.kimi-code'), { recursive: true }); + await symlink(brandTarget, join(homeDir, '.kimi-code', 'AGENTS.md')); + await symlink(projectTarget, join(workDir, 'AGENTS.md')); + + const result = await loadAgentsMd({ fs, homeDir }, workDir); + + expect(result).toContain('brand via symlink'); + expect(result).toContain('project via symlink'); + }); +}); + describe('loadAgentsMd brand home (KIMI_CODE_HOME)', () => { let brandHome: string;