diff --git a/.changeset/fix-windows-acp-paths.md b/.changeset/fix-windows-acp-paths.md new file mode 100644 index 000000000..16d9f9aa8 --- /dev/null +++ b/.changeset/fix-windows-acp-paths.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/acp-adapter": patch +"@moonshot-ai/kimi-code": patch +--- + +Fix ACP file reads and edits for Windows workspaces opened through IDE clients. diff --git a/packages/acp-adapter/src/kaos-acp.ts b/packages/acp-adapter/src/kaos-acp.ts index 14337d64d..70ed8614f 100644 --- a/packages/acp-adapter/src/kaos-acp.ts +++ b/packages/acp-adapter/src/kaos-acp.ts @@ -122,11 +122,12 @@ export class AcpKaos implements Kaos { path: string, _options?: { encoding?: BufferEncoding; errors?: 'strict' | 'replace' | 'ignore' }, ): Promise { + const rpcPath = this.toClientPath(path); try { - const resp = await this.conn.readTextFile({ sessionId: this.sessionId, path }); + const resp = await this.conn.readTextFile({ sessionId: this.sessionId, path: rpcPath }); return resp.content; } catch (err) { - throw wrapKaosError(`acp: readTextFile failed for ${path}`, err); + throw wrapKaosError(`acp: readTextFile failed for ${rpcPath}`, err); } } @@ -221,13 +222,19 @@ export class AcpKaos implements Kaos { } private async acpWrite(path: string, content: string): Promise { + const rpcPath = this.toClientPath(path); try { - await this.conn.writeTextFile({ sessionId: this.sessionId, path, content }); + await this.conn.writeTextFile({ sessionId: this.sessionId, path: rpcPath, content }); } catch (err) { - throw wrapKaosError(`acp: writeTextFile failed for ${path}`, err); + throw wrapKaosError(`acp: writeTextFile failed for ${rpcPath}`, err); } } + private toClientPath(path: string): string { + if (this.inner.pathClass() !== 'win32') return path; + return path.replaceAll('/', '\\'); + } + // ── process execution: delegate to inner ─────────────────────────── exec(...args: string[]): Promise { diff --git a/packages/acp-adapter/test/kaos-acp.test.ts b/packages/acp-adapter/test/kaos-acp.test.ts index b313f9063..4564f2c6d 100644 --- a/packages/acp-adapter/test/kaos-acp.test.ts +++ b/packages/acp-adapter/test/kaos-acp.test.ts @@ -82,7 +82,8 @@ interface MockInnerKaos extends Kaos { }; } -function makeMockInner(): MockInnerKaos { +function makeMockInner(opts?: { pathClass?: 'posix' | 'win32' }): MockInnerKaos { + const pathClass = opts?.pathClass ?? 'posix'; const spy = { pathClassCalls: 0, normpathCalls: [] as string[], @@ -107,7 +108,7 @@ function makeMockInner(): MockInnerKaos { osEnv: { os: 'linux', shell: 'bash' } as unknown as Environment, pathClass: () => { spy.pathClassCalls += 1; - return 'posix'; + return pathClass; }, normpath: (p: string) => { spy.normpathCalls.push(p); @@ -231,6 +232,31 @@ describe('AcpKaos', () => { expect((err as Error).message).toContain('rpc died'); } }); + + it('uses win32-native separators for ACP file RPC paths', async () => { + const conn = makeMockConn({ + readHandler: async () => ({ content: 'HELLO' }), + }); + const inner = makeMockInner({ pathClass: 'win32' }); + const kaos = new AcpKaos(conn.asConn(), 's1', inner); + + await kaos.readText('G:/python-code/render_with_mult_gpu/README.md'); + await kaos.writeText('G:/python-code/render_with_mult_gpu/README.md', 'updated'); + + expect(conn.readCalls).toEqual([ + { + sessionId: 's1', + path: 'G:\\python-code\\render_with_mult_gpu\\README.md', + }, + ]); + expect(conn.writeCalls).toEqual([ + { + sessionId: 's1', + path: 'G:\\python-code\\render_with_mult_gpu\\README.md', + content: 'updated', + }, + ]); + }); }); describe('readBytes', () => {