From 807e0e95a5332fef9de577d0f8023f37fd5a308f Mon Sep 17 00:00:00 2001 From: Wang Hengzhi Date: Wed, 2 Sep 2026 07:37:58 +0000 Subject: [PATCH] fix remote control multi endpoint still use default credentials --- .../src/cli/sub/web/remote-control.ts | 15 ++++- .../test/cli/web/remote-control.test.ts | 56 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/apps/kimi-code/src/cli/sub/web/remote-control.ts b/apps/kimi-code/src/cli/sub/web/remote-control.ts index 42cd221f980..088ede3e84c 100644 --- a/apps/kimi-code/src/cli/sub/web/remote-control.ts +++ b/apps/kimi-code/src/cli/sub/web/remote-control.ts @@ -7,6 +7,9 @@ import { createKimiDeviceId, FileTokenStorage, KIMI_CODE_PROVIDER_NAME, + kimiCodeEnvBaseUrl, + kimiCodeEnvOAuthHost, + resolveKimiCodeOAuthKey, resolveKimiTokenStorageName, } from '@moonshot-ai/kimi-code-oauth'; import { WebSocket, type RawData } from 'ws'; @@ -294,8 +297,18 @@ export async function startRemoteControl( throw new Error('Remote Control requires local server authentication.'); } const storage = new FileTokenStorage(join(options.homeDir, 'credentials')); + // Resolve the credential slot the same way login and the runtime provider + // do: with KIMI_CODE_OAUTH_HOST / KIMI_CODE_BASE_URL overrides the token + // lives in an env-scoped slot (kimi-code-env-), and reading only the + // default slot would pick up a credential for the wrong environment. const token = await storage.load( - resolveKimiTokenStorageName({ providerName: KIMI_CODE_PROVIDER_NAME }), + resolveKimiTokenStorageName({ + providerName: KIMI_CODE_PROVIDER_NAME, + oauthKey: resolveKimiCodeOAuthKey({ + oauthHost: kimiCodeEnvOAuthHost(), + baseUrl: kimiCodeEnvBaseUrl(), + }), + }), ); if (token?.refreshToken === undefined || token.refreshToken.length === 0) { throw new Error('Remote Control requires a Kimi login. Run `kimi login` first.'); diff --git a/apps/kimi-code/test/cli/web/remote-control.test.ts b/apps/kimi-code/test/cli/web/remote-control.test.ts index 56cf347a377..ebdcf29e1fe 100644 --- a/apps/kimi-code/test/cli/web/remote-control.test.ts +++ b/apps/kimi-code/test/cli/web/remote-control.test.ts @@ -8,6 +8,7 @@ import { join } from 'node:path'; import { FileTokenStorage, KIMI_CODE_PROVIDER_NAME, + resolveKimiCodeOAuthKey, resolveKimiTokenStorageName, type TokenInfo, } from '@moonshot-ai/kimi-code-oauth'; @@ -243,6 +244,61 @@ describe('Remote Control tunnel', () => { ).rejects.toThrow(/DEVICE_LIMIT_EXCEEDED.*membership allows 3 devices/); }); + it('loads the env-scoped credential when OAuth env overrides are set', async () => { + const oauthHost = 'https://auth.dev.example.test'; + const baseUrl = 'https://api.dev.example.test/coding/v1'; + vi.stubEnv('KIMI_CODE_OAUTH_HOST', oauthHost); + vi.stubEnv('KIMI_CODE_BASE_URL', baseUrl); + const homeDir = mkdtempSync(join(tmpdir(), 'kimi-rc-env-')); + cleanups.push(() => rmSync(homeDir, { recursive: true, force: true })); + const storage = new FileTokenStorage(join(homeDir, 'credentials')); + // The default (production) slot holds a credential for a different + // environment; the dev login wrote to the env-scoped slot instead. + await storage.save(resolveKimiTokenStorageName({ providerName: KIMI_CODE_PROVIDER_NAME }), { + ...TOKEN, + refreshToken: 'prod-refresh-token', + }); + await storage.save( + resolveKimiTokenStorageName({ + providerName: KIMI_CODE_PROVIDER_NAME, + oauthKey: resolveKimiCodeOAuthKey({ oauthHost, baseUrl }), + }), + { ...TOKEN, refreshToken: 'dev-refresh-token' }, + ); + const relay = await startAuthRelay(); + let handle: RemoteControlHandle | undefined; + cleanups.push(async () => handle?.close()); + + handle = await startRemoteControl({ + homeDir, + localOrigin: 'http://127.0.0.1:1', + localServerToken: 'local-server-token', + relayOrigin: `http://127.0.0.1:${relay.port}/coding-relay`, + stderr: { write: () => true }, + }); + + const bearerTokens = relay.requests.map( + (request) => request.authorization ?? request.protocol?.replace('kimi-code.bearer.', ''), + ); + expect(bearerTokens).toContain('dev-refresh-token'); + expect(bearerTokens).not.toContain('prod-refresh-token'); + }); + + it('ignores the default credential slot when OAuth env overrides are set', async () => { + vi.stubEnv('KIMI_CODE_OAUTH_HOST', 'https://auth.dev.example.test'); + const homeDir = await createRemoteControlHome(TOKEN.refreshToken); + + await expect( + startRemoteControl({ + homeDir, + localOrigin: 'http://127.0.0.1:1', + localServerToken: 'local-server-token', + relayOrigin: 'http://127.0.0.1:1', + stderr: { write: () => true }, + }), + ).rejects.toThrow('Remote Control requires a Kimi login'); + }); + it('uses only Authorization when the refresh token is not a valid subprotocol token', async () => { const homeDir = await createRemoteControlHome('invalid/token='); const relay = await startAuthRelay();