mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-22 15:44:46 +00:00
230 lines
6.8 KiB
TypeScript
230 lines
6.8 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { dirname, join } from 'pathe';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { localKaos } from '@moonshot-ai/kaos';
|
|
import type { ProviderConfig } from '@moonshot-ai/kosong';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { ProviderManager } from '../../src/providers/provider-manager';
|
|
import type { ResolvedAgentProfile } from '../../src/profile';
|
|
import type { SDKSessionRPC } from '../../src/rpc';
|
|
import { Session } from '../../src/session';
|
|
import { SessionAPIImpl } from '../../src/session/rpc';
|
|
import { createScriptedGenerate } from '../agent/harness/scripted-generate';
|
|
import { recordingTelemetry, type TelemetryRecord } from '../fixtures/telemetry';
|
|
|
|
const MOCK_PROVIDER = {
|
|
type: 'kimi',
|
|
apiKey: 'test-key',
|
|
model: 'mock-model',
|
|
} as const satisfies ProviderConfig;
|
|
|
|
const OS_ENV = {
|
|
osKind: 'Linux',
|
|
osArch: 'arm64',
|
|
osVersion: 'test',
|
|
shellPath: '/bin/bash',
|
|
shellName: 'bash',
|
|
} as const;
|
|
|
|
const here = import.meta.dirname;
|
|
const mcpStdioFixture = join(here, '..', 'mcp', 'fixtures', 'mock-stdio-server.mjs');
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
for (const dir of tempDirs.splice(0)) {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('Session.init', () => {
|
|
it('runs an isolated system-trigger turn and records the latest AGENTS as a system reminder', async () => {
|
|
const workDir = await makeTempDir();
|
|
const sessionDir = await makeTempDir();
|
|
await mkdir(join(workDir, '.git'));
|
|
await writeFile(join(workDir, 'AGENTS.md'), 'latest project instructions', 'utf-8');
|
|
|
|
const events: Array<Record<string, unknown>> = [];
|
|
const scripted = createScriptedGenerate();
|
|
const session = new Session({
|
|
id: 'test-init',
|
|
runtime: { kaos: localKaos, osEnv: OS_ENV },
|
|
homedir: sessionDir,
|
|
cwd: workDir,
|
|
rpc: createSessionRpc(events),
|
|
skills: { explicitDirs: [join(workDir, 'missing-skills')] },
|
|
providerManager: testProviderManager(),
|
|
});
|
|
const { agent: mainAgent } = await session.createAgent(
|
|
{ type: 'main', generate: scripted.generate },
|
|
testProfile(),
|
|
);
|
|
mainAgent.config.update({
|
|
modelAlias: 'mock-model',
|
|
thinkingLevel: 'off',
|
|
});
|
|
mainAgent.tools.setActiveTools([]);
|
|
events.length = 0;
|
|
scripted.mockNextResponse({
|
|
type: 'text',
|
|
text: 'Explored the project structure, identified the build and test commands, mapped the module layout, and wrote a comprehensive summary into AGENTS.md covering architecture, conventions, and the developer workflow for future agents.',
|
|
});
|
|
|
|
await session.generateAgentsMd();
|
|
|
|
expect(session.agents.size).toBe(2);
|
|
expect(session.agents.get('main')).toBe(mainAgent);
|
|
expect(events).toContainEqual(
|
|
expect.objectContaining({
|
|
type: 'subagent.spawned',
|
|
agentId: 'main',
|
|
subagentId: 'agent-0',
|
|
subagentName: 'coder',
|
|
parentToolCallId: 'generate-agents-md',
|
|
}),
|
|
);
|
|
expect(events).toContainEqual(
|
|
expect.objectContaining({
|
|
type: 'turn.started',
|
|
agentId: 'agent-0',
|
|
origin: { kind: 'system_trigger', name: 'init' },
|
|
}),
|
|
);
|
|
expect(events).toContainEqual(
|
|
expect.objectContaining({
|
|
type: 'subagent.completed',
|
|
agentId: 'main',
|
|
subagentId: 'agent-0',
|
|
parentToolCallId: 'generate-agents-md',
|
|
contextTokens: expect.any(Number),
|
|
}),
|
|
);
|
|
expect(scripted.calls[0]?.history).toMatchObject([
|
|
{
|
|
role: 'user',
|
|
content: [
|
|
expect.objectContaining({
|
|
text: expect.stringContaining('Task requirements:'),
|
|
}),
|
|
],
|
|
},
|
|
]);
|
|
|
|
const contextText = mainAgent.context.history
|
|
.flatMap((message) => message.content)
|
|
.map((part) => (part.type === 'text' ? part.text : ''))
|
|
.join('\n');
|
|
expect(contextText).toContain('<system-reminder>');
|
|
expect(contextText).toContain('Latest AGENTS.md file content:');
|
|
expect(contextText).toContain('latest project instructions');
|
|
expect(contextText).not.toContain('Task requirements:');
|
|
});
|
|
|
|
it('tracks connected and failed MCP server totals after initial load', async () => {
|
|
const workDir = await makeTempDir();
|
|
const sessionDir = await makeTempDir();
|
|
const records: TelemetryRecord[] = [];
|
|
const session = new Session({
|
|
runtime: { kaos: localKaos, osEnv: OS_ENV },
|
|
homedir: sessionDir,
|
|
cwd: workDir,
|
|
rpc: createSessionRpc([]),
|
|
providerManager: testProviderManager(),
|
|
mcpConfig: {
|
|
servers: {
|
|
connected: {
|
|
transport: 'stdio',
|
|
command: process.execPath,
|
|
args: [mcpStdioFixture],
|
|
},
|
|
failed: {
|
|
transport: 'stdio',
|
|
command: '/this/path/does/not/exist/anywhere',
|
|
},
|
|
disabled: {
|
|
transport: 'stdio',
|
|
command: process.execPath,
|
|
args: [mcpStdioFixture],
|
|
enabled: false,
|
|
},
|
|
},
|
|
},
|
|
telemetry: recordingTelemetry(records),
|
|
});
|
|
|
|
try {
|
|
await session.mcp.waitForInitialLoad();
|
|
await expect(new SessionAPIImpl(session).getMcpStartupMetrics({})).resolves.toEqual({
|
|
durationMs: expect.any(Number),
|
|
});
|
|
|
|
expect(records).toContainEqual({
|
|
event: 'mcp_connected',
|
|
properties: {
|
|
server_count: 1,
|
|
total_count: 2,
|
|
},
|
|
});
|
|
expect(records).toContainEqual({
|
|
event: 'mcp_failed',
|
|
properties: {
|
|
failed_count: 1,
|
|
total_count: 2,
|
|
},
|
|
});
|
|
} finally {
|
|
await session.close();
|
|
}
|
|
}, 20000);
|
|
});
|
|
|
|
async function makeTempDir(): Promise<string> {
|
|
const dir = await mkdtemp(join(tmpdir(), 'kimi-core-init-'));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
function testProviderManager(): ProviderManager {
|
|
return new ProviderManager({
|
|
config: {
|
|
providers: {
|
|
test: {
|
|
type: MOCK_PROVIDER.type,
|
|
apiKey: MOCK_PROVIDER.apiKey,
|
|
},
|
|
},
|
|
models: {
|
|
[MOCK_PROVIDER.model]: {
|
|
provider: 'test',
|
|
model: MOCK_PROVIDER.model,
|
|
maxContextSize: 1_000_000,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
function testProfile(): ResolvedAgentProfile {
|
|
return {
|
|
name: 'test',
|
|
systemPrompt: () => '<system-prompt>',
|
|
tools: [],
|
|
};
|
|
}
|
|
|
|
function createSessionRpc(events: Array<Record<string, unknown>>): SDKSessionRPC {
|
|
return {
|
|
emitEvent: vi.fn(async (event) => {
|
|
events.push(event);
|
|
}),
|
|
requestApproval: vi.fn(async () => ({ decision: 'cancelled' })),
|
|
requestQuestion: vi.fn(async () => null),
|
|
toolCall: vi.fn(async () => ({
|
|
output: 'custom tools are not supported in this test',
|
|
isError: true,
|
|
})),
|
|
} as SDKSessionRPC;
|
|
}
|