Files
airi/services/minecraft/src/debug/mcp-repl-server.test.ts
T
Rin 08afe0f2aa feat(minecraft): add context boundary system with auto-summarization to prevent unbounded conversation history growth
Introduces task context boundaries via enterContext()/exitContext() REPL functions. Active context messages remain in full; completed contexts are summarized and archived. Adds [CONTEXT_HISTORY] prefix message with archived summaries, auto-trim when active context exceeds 30 messages, and auto-collapse when >10 summaries accumulate. Provides history query runtime (recent/search/playerChats/
2026-02-19 00:25:06 +08:00

188 lines
6.2 KiB
TypeScript

import type { Brain } from '../cognitive/conscious/brain'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { McpReplServer } from './mcp-repl-server'
const mocks = vi.hoisted(() => ({
resource: vi.fn(),
tool: vi.fn(),
connect: vi.fn(),
McpServerConstructor: vi.fn(),
}))
vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => {
return {
McpServer: class MockMcpServer {
constructor(config: any) {
mocks.McpServerConstructor(config)
return {
resource: mocks.resource,
tool: mocks.tool,
connect: mocks.connect,
}
}
},
ResourceTemplate: class MockResourceTemplate {
uri: string
constructor(uri: string) {
this.uri = uri
}
},
}
})
describe('mcpReplServer', () => {
let brain: Brain
let server: McpReplServer
beforeEach(() => {
vi.clearAllMocks()
// Mock Brain
brain = {
getDebugSnapshot: vi.fn().mockReturnValue({
isProcessing: false,
queueLength: 0,
turnCounter: 1,
givenUp: false,
paused: false,
contextView: 'test context',
conversationHistory: [],
llmLogEntries: [],
}),
executeDebugRepl: vi.fn().mockResolvedValue({ result: 'success' }),
injectDebugEvent: vi.fn().mockResolvedValue(undefined),
getReplState: vi.fn().mockReturnValue({ variables: [], updatedAt: 0 }),
getLastLlmInput: vi.fn().mockReturnValue({
systemPrompt: 'sys',
userMessage: 'user',
messages: [
{ role: 'system', content: 'sys' },
{ role: 'user', content: 'user' },
],
conversationHistory: [],
updatedAt: 0,
attempt: 1,
}),
getLlmLogs: vi.fn().mockReturnValue([{ id: 1, text: 'log' }]),
getLlmTrace: vi.fn().mockReturnValue([{
id: 1,
turnId: 1,
content: 'await skip()',
messageCount: 2,
estimatedTokens: 10,
}]),
} as unknown as Brain
server = new McpReplServer(brain)
})
it('registers resources on initialization', () => {
expect(mocks.McpServerConstructor).toHaveBeenCalled()
expect(mocks.resource).toHaveBeenCalledWith('brain-state', expect.anything(), expect.any(Function))
expect(mocks.resource).toHaveBeenCalledWith('brain-context', expect.anything(), expect.any(Function))
expect(mocks.resource).toHaveBeenCalledWith('brain-history', expect.anything(), expect.any(Function))
expect(mocks.resource).toHaveBeenCalledWith('brain-logs', expect.anything(), expect.any(Function))
})
it('registers tools on initialization', () => {
expect(mocks.tool).toHaveBeenCalledWith('execute_repl', expect.anything(), expect.any(Function))
expect(mocks.tool).toHaveBeenCalledWith('inject_chat', expect.anything(), expect.any(Function))
expect(mocks.tool).toHaveBeenCalledWith('get_state', expect.anything(), expect.any(Function))
expect(mocks.tool).toHaveBeenCalledWith('get_last_prompt', expect.anything(), expect.any(Function))
expect(mocks.tool).toHaveBeenCalledWith('get_logs', expect.anything(), expect.any(Function))
expect(mocks.tool).toHaveBeenCalledWith('get_llm_trace', expect.anything(), expect.any(Function))
})
it('executes repl via tool handler', async () => {
const executeReplCall = mocks.tool.mock.calls.find(call => call[0] === 'execute_repl')
const handler = executeReplCall[2]
const result = await handler({ code: 'test code' })
expect(brain.executeDebugRepl).toHaveBeenCalledWith('test code')
expect(result.content[0].text).toContain('success')
})
it('injects chat via tool handler', async () => {
const injectChatCall = mocks.tool.mock.calls.find(call => call[0] === 'inject_chat')
const handler = injectChatCall[2]
await handler({ username: 'steve', message: 'hi' })
expect(brain.injectDebugEvent).toHaveBeenCalledWith(expect.objectContaining({
type: 'perception',
payload: expect.objectContaining({
type: 'chat_message',
metadata: { username: 'steve', message: 'hi' },
}),
}))
})
it('gets repl state via tool handler (skips builtins by default)', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_state')
const handler = toolCall[2]
await handler({})
expect(brain.getReplState).toHaveBeenCalledWith({ includeBuiltins: false })
})
it('gets repl state via tool handler (can include builtins)', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_state')
const handler = toolCall[2]
await handler({ includeBuiltins: true })
expect(brain.getReplState).toHaveBeenCalledWith({ includeBuiltins: true })
})
it('reads brain state via resource handler', async () => {
const resourceCall = mocks.resource.mock.calls.find(call => call[0] === 'brain-state')
const handler = resourceCall[2]
const result = await handler({ href: 'brain://state' })
expect(brain.getDebugSnapshot).toHaveBeenCalled()
expect(result.contents[0].text).toContain('"processing": false')
})
it('gets last prompt via tool handler', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_last_prompt')
const handler = toolCall[2]
const result = await handler({})
const text = result.content[0].text as string
expect(brain.getLastLlmInput).toHaveBeenCalled()
expect(text).toContain('user')
expect(text).not.toContain('systemPrompt')
expect(text).not.toContain('"role":"system"')
})
it('gets logs via tool handler', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_logs')
const handler = toolCall[2]
const result = await handler({ limit: 10 })
expect(brain.getLlmLogs).toHaveBeenCalledWith(10)
expect(result.content[0].text).toContain('log')
})
it('gets llm trace via tool handler', async () => {
const toolCall = mocks.tool.mock.calls.find(call => call[0] === 'get_llm_trace')
const handler = toolCall[2]
const result = await handler({ limit: 5, turnId: 3 })
const text = result.content[0].text as string
expect(brain.getLlmTrace).toHaveBeenCalledWith(5, 3)
expect(text).toContain('await skip()')
expect(text).toContain('"messageCount":2')
expect(text).toContain('"estimatedTokens":10')
})
})