Skip to content

Commit 7856c03

Browse files
authored
fix: avoid runtime check rpc (#432)
1 parent be0da5f commit 7856c03

10 files changed

Lines changed: 102 additions & 85 deletions

File tree

apps/kimi-code/src/cli/run-prompt.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ export async function runPrompt(
110110
removeTerminationCleanup = installPromptTerminationCleanup(promptProcess, cleanupPromptRun);
111111

112112
try {
113-
await harness.checkRuntimeEnvironment();
114113
await harness.ensureConfigFile();
115114
const config = await harness.getConfig();
116115
const { session, resumed, restorePermission, telemetryModel, goalModel } =

apps/kimi-code/src/cli/run-shell.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ export async function runShell(
3434
runOptions: { readonly migrateOnly?: boolean } = {},
3535
): Promise<void> {
3636
const startedAt = Date.now();
37+
const configStartedAt = startedAt;
38+
let tuiConfig: TuiConfig;
39+
let configWarning: string | undefined;
40+
try {
41+
tuiConfig = await loadTuiConfig();
42+
} catch (error) {
43+
if (!(error instanceof TuiConfigParseError)) throw error;
44+
tuiConfig = error.fallback;
45+
configWarning = error.message;
46+
}
47+
48+
// Resolve `theme = "auto"` against the live terminal once, before pi-tui
49+
// grabs stdin. Explicit `dark` / `light` skip detection.
50+
const resolvedTheme = tuiConfig.theme === 'auto' ? await detectTerminalTheme() : tuiConfig.theme;
51+
3752
const workDir = process.cwd();
3853
const telemetryBootstrap = createCliTelemetryBootstrap();
3954
const telemetryClient: TelemetryClient = {
@@ -63,22 +78,6 @@ export async function runShell(
6378
platform: `${process.platform}/${process.arch}`,
6479
workDir,
6580
});
66-
await harness.checkRuntimeEnvironment();
67-
68-
const configStartedAt = Date.now();
69-
let tuiConfig: TuiConfig;
70-
let configWarning: string | undefined;
71-
try {
72-
tuiConfig = await loadTuiConfig();
73-
} catch (error) {
74-
if (!(error instanceof TuiConfigParseError)) throw error;
75-
tuiConfig = error.fallback;
76-
configWarning = error.message;
77-
}
78-
79-
// Resolve `theme = "auto"` against the live terminal once, before pi-tui
80-
// grabs stdin. Explicit `dark` / `light` skip detection.
81-
const resolvedTheme = tuiConfig.theme === 'auto' ? await detectTerminalTheme() : tuiConfig.theme;
8281

8382
await harness.ensureConfigFile();
8483
const migrationPlan = await detectPendingMigration({

apps/kimi-code/test/cli/goal-prompt.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ const mocks = vi.hoisted(() => {
112112
mainEvent,
113113
experimentalFeatures: [{ id: 'goal_command', enabled: true }],
114114
sessions: [] as Array<{ readonly id: string; readonly workDir: string }>,
115-
harnessCheckRuntimeEnvironment: vi.fn(async () => undefined),
116115
};
117116
});
118117

@@ -123,7 +122,6 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => {
123122
createKimiHarness: () => ({
124123
homeDir: '/tmp/kimi-goal-home',
125124
auth: { getCachedAccessToken: vi.fn() },
126-
checkRuntimeEnvironment: mocks.harnessCheckRuntimeEnvironment,
127125
ensureConfigFile: vi.fn(),
128126
getConfig: vi.fn(async () => ({ providers: {}, defaultModel: 'k2', telemetry: true })),
129127
getExperimentalFeatures: vi.fn(async () => mocks.experimentalFeatures),

apps/kimi-code/test/cli/run-prompt.test.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ const mocks = vi.hoisted(() => {
4646
agentEvent,
4747
mainEvent,
4848
kimiHarnessConstructor: vi.fn(),
49-
harnessCheckRuntimeEnvironment: vi.fn(async () => undefined),
5049
harnessEnsureConfigFile: vi.fn(),
5150
harnessGetConfig: vi.fn(
5251
async (): Promise<{ providers: {}; defaultModel?: string; telemetry: boolean }> => ({
@@ -90,7 +89,6 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => {
9089
return {
9190
homeDir,
9291
auth: { getCachedAccessToken: mocks.harnessGetCachedAccessToken },
93-
checkRuntimeEnvironment: mocks.harnessCheckRuntimeEnvironment,
9492
ensureConfigFile: mocks.harnessEnsureConfigFile,
9593
getConfig: mocks.harnessGetConfig,
9694
getExperimentalFeatures: mocks.harnessGetExperimentalFeatures,
@@ -189,7 +187,6 @@ describe('runPrompt', () => {
189187
mocks.resolveKimiHome.mockImplementation(
190188
(homeDir?: string) => homeDir ?? '/tmp/kimi-code-test-home',
191189
);
192-
mocks.harnessCheckRuntimeEnvironment.mockResolvedValue(undefined);
193190
mocks.harnessCreatesDeviceIdOnConstruction = false;
194191
});
195192

@@ -202,10 +199,6 @@ describe('runPrompt', () => {
202199
expect(mocks.kimiHarnessConstructor).toHaveBeenCalledWith(
203200
expect.objectContaining({ skillDirs: ['/skills'], uiMode: 'print' }),
204201
);
205-
expect(mocks.harnessCheckRuntimeEnvironment).toHaveBeenCalledOnce();
206-
expect(mocks.harnessCheckRuntimeEnvironment.mock.invocationCallOrder[0]).toBeLessThan(
207-
mocks.harnessEnsureConfigFile.mock.invocationCallOrder[0]!,
208-
);
209202
expect(mocks.harnessCreateSession).toHaveBeenCalledWith({
210203
workDir: process.cwd(),
211204
model: 'k2',
@@ -221,19 +214,18 @@ describe('runPrompt', () => {
221214
expect(mocks.harnessClose).toHaveBeenCalled();
222215
});
223216

224-
it('stops prompt startup when runtime environment check fails', async () => {
217+
it('stops prompt startup when session creation fails', async () => {
225218
const stdout = writer();
226219
const stderr = writer();
227-
mocks.harnessCheckRuntimeEnvironment.mockRejectedValueOnce(new Error('Git Bash missing'));
220+
mocks.harnessCreateSession.mockRejectedValueOnce(new Error('Git Bash missing'));
228221

229222
await expect(runPrompt(opts(), '1.2.3-test', { stdout, stderr })).rejects.toThrow(
230223
'Git Bash missing',
231224
);
232225

233-
expect(mocks.harnessCheckRuntimeEnvironment).toHaveBeenCalledOnce();
234-
expect(mocks.harnessEnsureConfigFile).not.toHaveBeenCalled();
235-
expect(mocks.harnessGetConfig).not.toHaveBeenCalled();
236-
expect(mocks.harnessCreateSession).not.toHaveBeenCalled();
226+
expect(mocks.harnessEnsureConfigFile).toHaveBeenCalledOnce();
227+
expect(mocks.harnessGetConfig).toHaveBeenCalledOnce();
228+
expect(mocks.harnessCreateSession).toHaveBeenCalledOnce();
237229
expect(mocks.session.prompt).not.toHaveBeenCalled();
238230
expect(mocks.harnessClose).toHaveBeenCalledOnce();
239231
});

apps/kimi-code/test/cli/run-shell.test.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const mocks = vi.hoisted(() => {
3131
loadTuiConfig: vi.fn(),
3232
detectTerminalTheme: vi.fn(),
3333
kimiHarnessConstructor: vi.fn(),
34-
harnessCheckRuntimeEnvironment: vi.fn(async () => undefined),
3534
harnessEnsureConfigFile: vi.fn(),
3635
harnessGetConfig: vi.fn(async () => ({
3736
providers: {},
@@ -82,7 +81,6 @@ vi.mock('@moonshot-ai/kimi-code-sdk', async (importOriginal) => {
8281
getCachedAccessToken: mocks.harnessGetCachedAccessToken,
8382
},
8483
ensureConfigFile: mocks.harnessEnsureConfigFile,
85-
checkRuntimeEnvironment: mocks.harnessCheckRuntimeEnvironment,
8684
getConfig: mocks.harnessGetConfig,
8785
close: mocks.harnessClose,
8886
track: mocks.harnessTrack,
@@ -151,7 +149,6 @@ describe('runShell', () => {
151149
defaultModel: 'k2',
152150
telemetry: true,
153151
});
154-
mocks.harnessCheckRuntimeEnvironment.mockResolvedValue(undefined);
155152
mocks.tuiGetStartupMcpMs.mockResolvedValue(0);
156153
mocks.tuiGetCurrentSessionId.mockReturnValue('');
157154
mocks.tuiHasSessionContent.mockReturnValue(false);
@@ -194,10 +191,6 @@ describe('runShell', () => {
194191
}),
195192
}),
196193
);
197-
expect(mocks.harnessCheckRuntimeEnvironment).toHaveBeenCalledOnce();
198-
expect(mocks.harnessCheckRuntimeEnvironment.mock.invocationCallOrder[0]).toBeLessThan(
199-
mocks.harnessEnsureConfigFile.mock.invocationCallOrder[0]!,
200-
);
201194
expect(mocks.harnessEnsureConfigFile).toHaveBeenCalledOnce();
202195
expect(mocks.harnessEnsureConfigFile.mock.invocationCallOrder[0]).toBeLessThan(
203196
mocks.harnessGetConfig.mock.invocationCallOrder[0]!,
@@ -251,38 +244,6 @@ describe('runShell', () => {
251244
});
252245
});
253246

254-
it('stops startup when runtime environment check fails', async () => {
255-
mocks.loadTuiConfig.mockResolvedValue({
256-
theme: 'dark',
257-
editorCommand: null,
258-
notifications: { enabled: true, condition: 'unfocused' },
259-
});
260-
mocks.harnessCheckRuntimeEnvironment.mockRejectedValueOnce(new Error('Git Bash missing'));
261-
262-
await expect(
263-
runShell(
264-
{
265-
session: undefined,
266-
continue: false,
267-
yolo: false,
268-
auto: false,
269-
plan: false,
270-
model: undefined,
271-
outputFormat: undefined,
272-
prompt: undefined,
273-
skillsDirs: [],
274-
},
275-
'1.2.3-test',
276-
),
277-
).rejects.toThrow('Git Bash missing');
278-
279-
expect(mocks.harnessCheckRuntimeEnvironment).toHaveBeenCalledOnce();
280-
expect(mocks.harnessEnsureConfigFile).not.toHaveBeenCalled();
281-
expect(mocks.harnessGetConfig).not.toHaveBeenCalled();
282-
expect(mocks.kimiTuiConstructor).not.toHaveBeenCalled();
283-
expect(mocks.tuiStart).not.toHaveBeenCalled();
284-
});
285-
286247
it('tracks first launch when device id creation reports first launch', async () => {
287248
mocks.loadTuiConfig.mockResolvedValue({
288249
theme: 'dark',

packages/agent-core/src/rpc/core-api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,6 @@ type SessionAPIWithId = WithSessionId<SessionAPI>;
357357

358358
export interface CoreAPI extends SessionAPIWithId {
359359
getCoreInfo: (payload: EmptyPayload) => CoreInfo;
360-
checkRuntimeEnvironment: (payload: EmptyPayload) => void;
361360
getExperimentalFeatures: (payload: EmptyPayload) => readonly ExperimentalFeatureState[];
362361
getKimiConfig: (payload: GetKimiConfigPayload) => KimiConfig;
363362
setKimiConfig: (payload: SetKimiConfigPayload) => KimiConfig;

packages/agent-core/src/rpc/core-impl.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,6 @@ export class KimiCore implements PromisableMethods<CoreAPI> {
176176
this.sdk = rpcClient(this);
177177
}
178178

179-
async checkRuntimeEnvironment(_: EmptyPayload): Promise<void> {
180-
await this.getKaos();
181-
}
182-
183179
async createSession(input: CreateSessionPayload): Promise<SessionSummary> {
184180
const options = input;
185181
const workDir = requiredWorkDir('createSession', options.workDir);

packages/agent-core/test/harness/runtime.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
22
import { tmpdir } from 'node:os';
33
import { join } from 'pathe';
44

5+
import type { Kaos } from '@moonshot-ai/kaos';
56
import { afterEach, describe, expect, it, vi } from 'vitest';
67

78
import {
@@ -10,6 +11,7 @@ import {
1011
createRPC,
1112
ErrorCodes,
1213
KimiCore,
14+
KimiError,
1315
type ApprovalResponse,
1416
type CoreAPI,
1517
type SDKAPI,
@@ -21,6 +23,7 @@ import {
2123
} from '../../src/logging/logger';
2224
import { resolveLoggingConfig } from '../../src/logging/resolve-config';
2325
import type { OAuthTokenProviderResolver } from '../../src/session/provider-manager';
26+
import { testKaos } from '../fixtures/test-kaos';
2427

2528
function requiredFlagEnv(id: string): string {
2629
const def = FLAG_DEFINITIONS.find((item) => item.id === id);
@@ -39,6 +42,16 @@ function experimentalFeatureEnabled(core: KimiCore, id: string): boolean | undef
3942
return core.getExperimentalFeatures().find((feature) => feature.id === id)?.enabled;
4043
}
4144

45+
function setCoreKaos(core: KimiCore, kaos: Promise<Kaos>): void {
46+
(core as unknown as { kaos?: Promise<Kaos> }).kaos = kaos;
47+
}
48+
49+
function rejectedKaos(error: Error): Promise<Kaos> {
50+
const promise = Promise.reject(error) as Promise<Kaos>;
51+
promise.catch(() => undefined);
52+
return promise;
53+
}
54+
4255
describe('KimiCore runtime config', () => {
4356
let tmp: string;
4457

@@ -284,6 +297,75 @@ max_context_size = 100000
284297
expect(mainAgent?.config.modelAlias).toBe('default-mock');
285298
});
286299

300+
it('rejects createSession when shell runtime initialization fails', async () => {
301+
tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-'));
302+
const homeDir = join(tmp, 'home');
303+
const workDir = join(tmp, 'work');
304+
await mkdir(homeDir, { recursive: true });
305+
await mkdir(workDir, { recursive: true });
306+
await writeFile(join(homeDir, 'config.toml'), baseModelConfig());
307+
308+
const [coreRpc, sdkRpc] = createRPC<CoreAPI, SDKAPI>();
309+
const core = new KimiCore(coreRpc, { homeDir });
310+
const rpc = await sdkRpc({
311+
emitEvent: vi.fn(),
312+
requestApproval: vi.fn(async (): Promise<ApprovalResponse> => ({ decision: 'rejected' })),
313+
requestQuestion: vi.fn(async () => null),
314+
toolCall: vi.fn(async () => ({ output: '' })),
315+
});
316+
setCoreKaos(
317+
core,
318+
rejectedKaos(
319+
new KimiError(ErrorCodes.SHELL_GIT_BASH_NOT_FOUND, 'Git Bash missing'),
320+
),
321+
);
322+
323+
await expect(
324+
rpc.createSession({
325+
id: 'ses_runtime_shell_missing_create',
326+
workDir,
327+
model: 'default-mock',
328+
}),
329+
).rejects.toMatchObject({ code: ErrorCodes.SHELL_GIT_BASH_NOT_FOUND });
330+
expect(core.sessions.has('ses_runtime_shell_missing_create')).toBe(false);
331+
});
332+
333+
it('rejects resumeSession when shell runtime initialization fails', async () => {
334+
tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-'));
335+
const homeDir = join(tmp, 'home');
336+
const workDir = join(tmp, 'work');
337+
await mkdir(homeDir, { recursive: true });
338+
await mkdir(workDir, { recursive: true });
339+
await writeFile(join(homeDir, 'config.toml'), baseModelConfig());
340+
341+
const [coreRpc, sdkRpc] = createRPC<CoreAPI, SDKAPI>();
342+
const core = new KimiCore(coreRpc, { homeDir });
343+
const rpc = await sdkRpc({
344+
emitEvent: vi.fn(),
345+
requestApproval: vi.fn(async (): Promise<ApprovalResponse> => ({ decision: 'rejected' })),
346+
requestQuestion: vi.fn(async () => null),
347+
toolCall: vi.fn(async () => ({ output: '' })),
348+
});
349+
setCoreKaos(core, Promise.resolve(testKaos));
350+
const created = await rpc.createSession({
351+
id: 'ses_runtime_shell_missing_resume',
352+
workDir,
353+
model: 'default-mock',
354+
});
355+
await rpc.closeSession({ sessionId: created.id });
356+
setCoreKaos(
357+
core,
358+
rejectedKaos(
359+
new KimiError(ErrorCodes.SHELL_GIT_BASH_NOT_FOUND, 'Git Bash missing'),
360+
),
361+
);
362+
363+
await expect(rpc.resumeSession({ sessionId: created.id })).rejects.toMatchObject({
364+
code: ErrorCodes.SHELL_GIT_BASH_NOT_FOUND,
365+
});
366+
expect(core.sessions.has(created.id)).toBe(false);
367+
});
368+
287369
it('reloads an active session with fresh runtime services from config.toml', async () => {
288370
tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-'));
289371
const homeDir = join(tmp, 'home');

packages/node-sdk/src/kimi-harness.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,6 @@ export class KimiHarness {
210210
await this.ensureConfigFileImpl();
211211
}
212212

213-
async checkRuntimeEnvironment(): Promise<void> {
214-
await this.rpc.checkRuntimeEnvironment();
215-
}
216-
217213
async setConfig(patch: KimiConfigPatch): Promise<KimiConfig> {
218214
return this.rpc.setConfig(patch);
219215
}

packages/node-sdk/src/rpc.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,6 @@ export abstract class SDKRpcClientBase {
9999

100100
protected abstract getRpc(): Promise<ResolvedCoreAPI>;
101101

102-
async checkRuntimeEnvironment(): Promise<void> {
103-
const rpc = await this.getRpc();
104-
return rpc.checkRuntimeEnvironment({});
105-
}
106-
107102
async createSession(input: CreateSessionOptions): Promise<SessionSummary> {
108103
const rpc = await this.getRpc();
109104
const { planMode, ...coreInput } = input;

0 commit comments

Comments
 (0)