From 75e9c414bad1e9b2ef9915f269000e4e5e317960 Mon Sep 17 00:00:00 2001 From: "haozhe.yang" Date: Tue, 18 Aug 2026 16:21:34 +0800 Subject: [PATCH] test: prune low-value tests and exclude agent-core from the root suite - delete dead MicroCompaction/flag tests, an accepted-limitation it.fails, unreachable CLI input cases, duplicated TUI aggregation matrices, and strictly redundant search/minidb cases identified in the test-suite audit - exclude packages/agent-core from the root vitest projects so local pnpm test and CI shards collect the same reduced set --- apps/kimi-code/test/cli/run-prompt.test.ts | 22 - .../test/tui/kimi-tui-message-flow.test.ts | 380 ------- packages/agent-core-v2/test/index.test.ts | 3 - .../compaction/compaction-scenarios.test.ts | 92 +- .../test/agent/compaction/full.test.ts | 49 - .../test/agent/compaction/micro.test.ts | 990 ------------------ .../agent-core/test/harness/runtime.test.ts | 160 +-- .../test/session/cron-stop-on-close.test.ts | 27 +- .../test/search/searchService.test.ts | 24 - .../minidb/test/e2e/compaction-race.test.ts | 143 --- packages/minidb/test/review-fixes.test.ts | 55 - packages/minidb/test/review-round2.test.ts | 54 - vitest.config.ts | 2 +- 13 files changed, 7 insertions(+), 1994 deletions(-) delete mode 100644 packages/agent-core/test/agent/compaction/micro.test.ts diff --git a/apps/kimi-code/test/cli/run-prompt.test.ts b/apps/kimi-code/test/cli/run-prompt.test.ts index 726a83e607..afd93642d8 100644 --- a/apps/kimi-code/test/cli/run-prompt.test.ts +++ b/apps/kimi-code/test/cli/run-prompt.test.ts @@ -665,17 +665,6 @@ describe('runPrompt', () => { expect(mocks.harnessCreateSession).not.toHaveBeenCalled(); }); - it('does not forward an agent profile when resuming a concrete v1 session', async () => { - // validateOptions rejects --agent with --session; runPrompt must not - // forward a profile to resume even if a caller hands one over. - await runPrompt(opts({ session: 'ses_existing', agent: 'reviewer' }), '1.2.3-test', { - stdout: writer(), - stderr: writer(), - }); - - expect(mocks.harnessResumeSession).toHaveBeenCalledWith({ id: 'ses_existing' }); - }); - it('allows resuming a concrete session when Windows workdir uses backslashes', async () => { const cwd = vi.spyOn(process, 'cwd').mockReturnValue(String.raw`C:\Users\kimi\project`); mocks.harnessListSessions.mockResolvedValueOnce([ @@ -948,17 +937,6 @@ describe('runPrompt', () => { expect(mocks.harnessCreateSession).not.toHaveBeenCalled(); }); - it('does not forward an agent profile when continuing a previous v1 session', async () => { - // validateOptions rejects --agent with --continue; runPrompt must not - // forward a profile to resume even if a caller hands one over. - await runPrompt(opts({ continue: true, agent: 'reviewer' }), '1.2.3-test', { - stdout: writer(), - stderr: writer(), - }); - - expect(mocks.harnessResumeSession).toHaveBeenCalledWith({ id: 'ses_previous' }); - }); - it('continues a previous session without a configured default model', async () => { mocks.harnessGetConfig.mockResolvedValueOnce({ providers: {}, telemetry: true }); mocks.session.getStatus.mockResolvedValueOnce({ permission: 'manual', model: 'saved-model' }); diff --git a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts index 1634c26bb6..7a39f09b60 100644 --- a/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts +++ b/apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts @@ -676,70 +676,6 @@ describe('KimiTUI message flow', () => { expect(session.activateSkill).not.toHaveBeenCalled(); }); - it('combines a leading skill command with later inline skills into one submission (v2 engine)', async () => { - const session = makeSession({ id: 'ses-lazy' }); - const startupInput: KimiTUIStartupInput = { - ...makeStartupInput(), - engineV2: true, - cliOptions: { ...makeStartupInput().cliOptions, model: 'k2' }, - }; - const { driver } = await makeDriver( - session, - { - listWorkspaceSkills: vi.fn(async () => [ - { name: 'review', description: 'Review skill', path: '/tmp/review', source: 'user' }, - { name: 'security', description: 'Security skill', path: '/tmp/security', source: 'user' }, - ]), - listPluginCommands: vi.fn(async () => []), - }, - startupInput, - ); - await ( - driver as unknown as { refreshSkillCommands(): Promise } - ).refreshSkillCommands(); - - driver.handleUserInput('/skill:review check this /skill:security'); - - await vi.waitFor(() => { - expect(session.promptWithSkills).toHaveBeenCalledWith( - '/skill:review check this /skill:security', - [{ name: 'review' }, { name: 'security' }], - ); - }); - expect(session.activateSkill).not.toHaveBeenCalled(); - }); - - it('bundles a repeated leading skill as one bundled submission (v2 engine)', async () => { - const session = makeSession({ id: 'ses-lazy' }); - const startupInput: KimiTUIStartupInput = { - ...makeStartupInput(), - engineV2: true, - cliOptions: { ...makeStartupInput().cliOptions, model: 'k2' }, - }; - const { driver } = await makeDriver( - session, - { - listWorkspaceSkills: vi.fn(async () => [ - { name: 'review', description: 'Review skill', path: '/tmp/review', source: 'user' }, - ]), - listPluginCommands: vi.fn(async () => []), - }, - startupInput, - ); - await ( - driver as unknown as { refreshSkillCommands(): Promise } - ).refreshSkillCommands(); - - driver.handleUserInput('/skill:review check /skill:review'); - - await vi.waitFor(() => { - expect(session.promptWithSkills).toHaveBeenCalledWith('/skill:review check /skill:review', [ - { name: 'review' }, - ]); - }); - expect(session.activateSkill).not.toHaveBeenCalled(); - }); - it('passes no args in a bundle while media rides the prompt parts (v2 engine)', async () => { const session = makeSession({ id: 'ses-lazy' }); const startupInput: KimiTUIStartupInput = { @@ -778,71 +714,6 @@ describe('KimiTUI message flow', () => { }); }); - it('bundles newline-separated skills with the leading one included (v2 engine)', async () => { - const session = makeSession({ id: 'ses-lazy' }); - const startupInput: KimiTUIStartupInput = { - ...makeStartupInput(), - engineV2: true, - cliOptions: { ...makeStartupInput().cliOptions, model: 'k2' }, - }; - const { driver } = await makeDriver( - session, - { - listWorkspaceSkills: vi.fn(async () => [ - { name: 'review', description: 'Review skill', path: '/tmp/review', source: 'user' }, - { name: 'security', description: 'Security skill', path: '/tmp/security', source: 'user' }, - ]), - listPluginCommands: vi.fn(async () => []), - }, - startupInput, - ); - await ( - driver as unknown as { refreshSkillCommands(): Promise } - ).refreshSkillCommands(); - - driver.handleUserInput('/skill:review\ncheck this /skill:security'); - - await vi.waitFor(() => { - expect(session.promptWithSkills).toHaveBeenCalledWith( - '/skill:review\ncheck this /skill:security', - [{ name: 'review' }, { name: 'security' }], - ); - }); - expect(session.prompt).not.toHaveBeenCalled(); - }); - - it('scans inline skills in messages that start with an unknown slash token (v2 engine)', async () => { - const session = makeSession({ id: 'ses-lazy' }); - const startupInput: KimiTUIStartupInput = { - ...makeStartupInput(), - engineV2: true, - cliOptions: { ...makeStartupInput().cliOptions, model: 'k2' }, - }; - const { driver } = await makeDriver( - session, - { - listWorkspaceSkills: vi.fn(async () => [ - { name: 'review', description: 'Review skill', path: '/tmp/review', source: 'user' }, - ]), - listPluginCommands: vi.fn(async () => []), - }, - startupInput, - ); - await ( - driver as unknown as { refreshSkillCommands(): Promise } - ).refreshSkillCommands(); - - driver.handleUserInput('/dance please use /skill:review'); - - await vi.waitFor(() => { - expect(session.promptWithSkills).toHaveBeenCalledWith( - '/dance please use /skill:review', - [{ name: 'review' }], - ); - }); - expect(session.prompt).not.toHaveBeenCalled(); - }); - it('keeps inline skill tokens as plain text on the legacy engine', async () => { const session = makeSession({ id: 'ses-1' }); const { driver } = await makeDriver(session, { @@ -3470,59 +3341,6 @@ command = "vim" expect(attachment.fileId).toBeUndefined(); }); - it('releases every queued use of shared media when the queue is discarded', async () => { - process.env['KIMI_CODE_HOME'] = await makeTempHome(); - const { driver, harness } = await makeDriver(); - const imageStore = (driver as unknown as { imageStore: ImageAttachmentStore }).imageStore; - const attachment = stagedImage(imageStore, 'file-queued'); - driver.state.appState.streamingPhase = 'waiting'; - - driver.handleUserInput(`first ${attachment.placeholder}`); - driver.handleUserInput(`second ${attachment.placeholder}`); - const stagingPaths = driver.state.queuedMessages.flatMap((item) => item.stagingPaths ?? []); - expect(driver.state.queuedMessages).toHaveLength(2); - // An uploaded image stages no local cache copy — the engine's intake - // materializes the session copy — so only the daemon upload lease rides - // with each queued message. - expect(stagingPaths).toHaveLength(0); - - driver.clearQueuedMessages(); - - await vi.waitFor(() => { - expect(harness.deleteFile).toHaveBeenCalledWith('file-queued'); - }); - expect(harness.deleteFile).toHaveBeenCalledTimes(1); - expect(attachment.fileId).toBeUndefined(); - }); - - it('does not delete shared daemon media while another turn still uses it', async () => { - const session = makeSession(); - const { driver, harness } = await makeDriver(session); - driver.state.appState.model = 'k2'; - const imageStore = (driver as unknown as { imageStore: ImageAttachmentStore }).imageStore; - const attachment = stagedImage(imageStore, 'file-shared-turn'); - - driver.handleUserInput(`first ${attachment.placeholder}`); - driver.sessionEventHandler.handleEvent( - { type: 'turn.started', agentId: 'main', turnId: 1, origin: { kind: 'user' } } as Event, - () => {}, - ); - driver.state.appState.streamingPhase = 'waiting'; - driver.handleUserInput(`second ${attachment.placeholder}`); - driver.clearQueuedMessages(); - - await Promise.resolve(); - expect(harness.deleteFile).not.toHaveBeenCalled(); - - driver.sessionEventHandler.handleEvent( - { type: 'turn.ended', agentId: 'main', turnId: 1, reason: 'completed' } as Event, - () => {}, - ); - await vi.waitFor(() => { - expect(harness.deleteFile).toHaveBeenCalledWith('file-shared-turn'); - }); - }); - it('queues editor input instead of prompting while a turn is already streaming', async () => { const { driver, session, harness } = await makeDriver(); driver.state.appState.streamingPhase = 'waiting'; @@ -3968,35 +3786,6 @@ command = "vim" expect(driver.state.queuedMessages).toEqual([]); }); - it('keeps a shared staged upload alive while another submission still holds it', async () => { - const session = makeSession(); - const { driver, harness } = await makeDriver(session); - driver.state.appState.model = 'k2'; - driver.state.appState.streamingPhase = 'waiting'; - const imageStore = (driver as unknown as { imageStore: ImageAttachmentStore }).imageStore; - const attachment = stagedImage(imageStore, 'file-shared'); - - // One message referencing the same image twice retains it once; a second - // queued message retains it again — two retains total. - driver.handleUserInput(`compare ${attachment.placeholder} with ${attachment.placeholder}`); - driver.handleUserInput(`and ${attachment.placeholder}`); - const [first, second] = driver.state.queuedMessages; - - driver.sendQueuedMessage(session, first!); - emitTurn(driver, 1); - await new Promise((resolve) => setTimeout(resolve, 0)); - // The first turn consumed the only retain its submission held; the second - // queued message's retain keeps the upload alive. - expect(harness.deleteFile).not.toHaveBeenCalled(); - - driver.sendQueuedMessage(session, second!); - emitTurn(driver, 2); - await vi.waitFor(() => { - expect(harness.deleteFile).toHaveBeenCalledWith('file-shared'); - }); - expect(harness.deleteFile).toHaveBeenCalledTimes(1); - }); - it('keeps staged media when a queued message is recalled into the editor', async () => { const session = makeSession(); const { driver, harness } = await makeDriver(session); @@ -6104,49 +5893,6 @@ command = "vim" expect(stripSgr(renderTranscript(driver))).toContain('k2-cheap'); }); - it('shows the spawned model in the swarm panel header at spawn', async () => { - const { driver } = await makeDriver(); - const sendQueued = vi.fn(); - - driver.sessionEventHandler.handleEvent( - { - type: 'tool.call.started', - agentId: 'main', - sessionId: 'ses-1', - turnId: 1, - toolCallId: 'call_swarm', - name: 'AgentSwarm', - args: { - description: 'Review changed files', - prompt_template: 'Review {{item}}', - items: ['src/a.ts'], - }, - } as Event, - sendQueued, - ); - driver.sessionEventHandler.handleEvent( - { - type: 'subagent.spawned', - agentId: 'main', - sessionId: 'ses-1', - parentToolCallId: 'call_swarm', - subagentId: 'agent-1', - subagentName: 'coder', - description: 'Review changed files #1 (coder)', - swarmIndex: 1, - runInBackground: false, - model: 'k2-cheap', - } as Event, - sendQueued, - ); - - const progress = driver.state.transcriptContainer.children.find( - (child): child is AgentSwarmProgressComponent => child instanceof AgentSwarmProgressComponent, - ); - if (progress === undefined) throw new Error('expected AgentSwarm progress'); - expect(stripSgr(progress.render(118).join('\n'))).toContain('k2-cheap'); - }); - it('includes the spawned model in the background-agent transcript entry', async () => { const { driver } = await makeDriver(); const sendQueued = vi.fn(); @@ -6241,53 +5987,6 @@ command = "vim" expect(transcript).toContain('Using Read (src/after.ts)'); }); - it('shows AgentSwarm as completed when only some subagents fail', async () => { - const { driver } = await makeDriver(); - const sendQueued = vi.fn(); - - driver.sessionEventHandler.handleEvent( - { - type: 'tool.call.started', - agentId: 'main', - sessionId: 'ses-1', - turnId: 1, - toolCallId: 'call_swarm', - name: 'AgentSwarm', - args: { - description: 'Review changed files', - prompt_template: 'Review {{item}}', - items: ['src/a.ts', 'src/b.ts'], - }, - } as Event, - sendQueued, - ); - driver.sessionEventHandler.handleEvent( - { - type: 'tool.result', - agentId: 'main', - sessionId: 'ses-1', - turnId: 1, - toolCallId: 'call_swarm', - output: [ - '', - 'completed: 1, failed: 1', - 'Imports are stable.', - 'Agent timed out after 30s.', - '', - ].join('\n'), - isError: undefined, - } as Event, - sendQueued, - ); - - const transcript = stripSgr(renderTranscript(driver)); - const totalStatusLine = transcript.split('\n').find((line) => line.includes('Completed.')); - expect(totalStatusLine).toBeDefined(); - expect(totalStatusLine).not.toContain('Failed.'); - expect(transcript).toContain('✓ Imports are stable.'); - expect(transcript).toContain('✗ Agent timed out after 30s.'); - }); - it('renders AgentSwarm progress while tool args are still streaming', async () => { const { driver } = await makeDriver(); const sendQueued = vi.fn(); @@ -8164,85 +7863,6 @@ describe('/effort support_efforts override', () => { expect(picker.render(80).join('\n')).toContain('Max'); }); - it('offers no fallback efforts for a clearly non-Claude Anthropic-compatible model', async () => { - const { driver } = await makeDriver(makeSession(), { - getConfig: vi.fn(async () => ({ - providers: { - compatible: { type: 'anthropic', apiKey: 'test-key' }, - }, - models: { - k2: { - provider: 'compatible', - model: 'compatible-model', - maxContextSize: 100, - }, - }, - defaultModel: 'k2', - })), - }); - - driver.handleUserInput('/effort'); - - await vi.waitFor(() => { - expect(driver.state.editorContainer.children[0]).toBeInstanceOf(EffortSelectorComponent); - }); - const picker = driver.state.editorContainer.children[0] as EffortSelectorComponent; - expect(picker.render(80).join('\n')).not.toContain('Max'); - }); - - it('offers no fallback efforts for an unknown model on a Kimi provider using the Anthropic protocol', async () => { - const { driver } = await makeDriver(makeSession(), { - getConfig: vi.fn(async () => ({ - providers: { - compatible: { type: 'kimi', apiKey: 'test-key' }, - }, - models: { - k2: { - provider: 'compatible', - model: 'compatible-model', - protocol: 'anthropic', - maxContextSize: 100, - }, - }, - defaultModel: 'k2', - })), - }); - - driver.handleUserInput('/effort'); - - await vi.waitFor(() => { - expect(driver.state.editorContainer.children[0]).toBeInstanceOf(EffortSelectorComponent); - }); - const picker = driver.state.editorContainer.children[0] as EffortSelectorComponent; - expect(picker.render(80).join('\n')).not.toContain('Max'); - }); - - it('offers the latest Opus efforts for a flat providerless Claude-marked Anthropic model', async () => { - const { driver } = await makeDriver(makeSession(), { - getConfig: vi.fn(async () => ({ - providers: {}, - models: { - // v2 flat model shape: no named provider, inline endpoint + protocol. - k2: { - model: 'compatible-claude-model', - baseUrl: 'https://anthropic.example.test', - protocol: 'anthropic', - maxContextSize: 100, - }, - }, - defaultModel: 'k2', - })), - }); - - driver.handleUserInput('/effort'); - - await vi.waitFor(() => { - expect(driver.state.editorContainer.children[0]).toBeInstanceOf(EffortSelectorComponent); - }); - const picker = driver.state.editorContainer.children[0] as EffortSelectorComponent; - expect(picker.render(80).join('\n')).toContain('Max'); - }); - it('keeps rejecting efforts hidden by a Kimi support_efforts override', async () => { const session = makeSession(); const { driver } = await makeDriver(session, { diff --git a/packages/agent-core-v2/test/index.test.ts b/packages/agent-core-v2/test/index.test.ts index 4f838121ca..af3eac6cff 100644 --- a/packages/agent-core-v2/test/index.test.ts +++ b/packages/agent-core-v2/test/index.test.ts @@ -494,9 +494,6 @@ describe('AgentRecords persistence metadata', () => { }); }); -describe.skip('agent replay range build', () => { -}); - class RecordingInMemoryWireRecordPersistence extends InMemoryWireRecordPersistence { readonly rewrites: WireRecord[][] = []; diff --git a/packages/agent-core/test/agent/compaction/compaction-scenarios.test.ts b/packages/agent-core/test/agent/compaction/compaction-scenarios.test.ts index dfa47a3829..9dc6458609 100644 --- a/packages/agent-core/test/agent/compaction/compaction-scenarios.test.ts +++ b/packages/agent-core/test/agent/compaction/compaction-scenarios.test.ts @@ -12,7 +12,7 @@ // Compaction is a hot path, so these intentionally drive the real // Agent/ContextMemory/FullCompaction machinery through the test harness rather // than mocking it. -import type { ContentPart, Message } from '@moonshot-ai/kosong'; +import type { Message } from '@moonshot-ai/kosong'; import { describe, expect, it } from 'vitest'; import type { AgentOptions, AgentRecord } from '../../../src/agent'; @@ -21,8 +21,6 @@ import { AGENT_WIRE_PROTOCOL_VERSION, InMemoryAgentRecordPersistence, } from '../../../src/agent/records'; -import type { ContextMessage } from '../../../src/agent/context'; -import { FLAG_DEFINITIONS, FlagResolver } from '../../../src/flags'; import { testAgent, type TestAgentContext } from '../harness/agent'; type GenerateFn = NonNullable; @@ -348,94 +346,6 @@ describe('compaction — probe tests (high-risk scenarios)', () => { expect(historyTexts(ctx).join('\n')).toContain('TAIL-ASSISTANT'); }); - - // PROBE #6 — when the summarizer request overflows, historyForModel is shrunk - // to a recent suffix but still projected through MicroCompaction.compact() - // with the cutoff computed for the FULL history. The absolute cutoff applied - // to the shifted suffix can clear recent tool results the summary needs. - // SKIPPED: micro-compaction has been disabled and its flag removed, so this - // defect no longer exists. - it.skip('does not clear recent tool results when projecting a shrunk suffix under an active micro-compaction cutoff', () => { - // This defect only exists when micro-compaction is active, so enable the - // flag explicitly rather than inheriting the ambient KIMI_CODE_EXPERIMENTAL - // master switch — otherwise the probe's pass/fail flips with the runner's - // environment (on locally with the master switch, off in CI by default). - const ctx = testAgent({ - experimentalFlags: new FlagResolver( - { KIMI_CODE_EXPERIMENTAL_MICRO_COMPACTION: '1' }, - FLAG_DEFINITIONS, - ), - }); - ctx.configure({ provider: PROVIDER, modelCapabilities: CAPS }); - - const bigToolOutput = 'TOOL-OUTPUT-CONTENT '.repeat(60); // > minContentTokens(100) - const full: ContextMessage[] = []; - for (let i = 0; i < 20; i++) { - if (i === 15) { - full.push({ - role: 'tool', - content: [{ type: 'text', text: bigToolOutput } satisfies ContentPart], - toolCalls: [], - toolCallId: `tool-${String(i)}`, - }); - } else { - full.push({ - role: i % 2 === 0 ? 'user' : 'assistant', - content: [{ type: 'text', text: `m${String(i)}` }], - toolCalls: [], - origin: i % 2 === 0 ? { kind: 'user' } : undefined, - }); - } - } - - // Cutoff computed for the full history: keep the recent 10 (indices >= 10). - ctx.agent.microCompaction.apply(10); - - // In the full history the tool result is at index 15 (>= cutoff) -> kept. - const projectedFull = ctx.agent.context.project(full); - const fullToolText = projectedFull - .map((m) => m.content.map((p) => (p.type === 'text' ? p.text : '')).join('')) - .join('\n'); - expect(fullToolText).toContain('TOOL-OUTPUT-CONTENT'); - - // After an overflow shrink drops the oldest 10, the SAME tool result sits at - // suffix index 5; the unchanged cutoff(10) now covers it. It must still be - // preserved (it is a recent result the summary depends on). - const shrunkSuffix = full.slice(10); - const projectedSuffix = ctx.agent.context.project(shrunkSuffix); - const suffixToolText = projectedSuffix - .map((m) => m.content.map((p) => (p.type === 'text' ? p.text : '')).join('')) - .join('\n'); - expect(suffixToolText).toContain('TOOL-OUTPUT-CONTENT'); - }); - - // PROBE #7 / CMP-07 — when the oldest kept user message overflows the budget it - // is truncated to text only, dropping any image/audio/video it carried: media - // can't be partially truncated, and keeping it whole would overshoot the - // budget. Recent messages that fit keep their media; only this boundary message - // loses its attachments. Documented as an accepted limitation rather than fixed. - it.fails('keeps media on the oldest kept user message instead of dropping it on truncation', () => { - const ctx = testAgent(); - ctx.configure({ provider: PROVIDER, modelCapabilities: CAPS }); - // Oldest user message: an image + long text that will overflow the budget. - ctx.agent.context.appendUserMessage( - [ - { type: 'image_url', imageUrl: { url: 'data:image/png;base64,AAAA' } }, - { type: 'text', text: 'x'.repeat(120_000) }, // ~30k tokens of text - ], - { kind: 'user' }, - ); - ctx.agent.context.appendUserMessage([{ type: 'text', text: 'recent user' }], { kind: 'user' }); - - ctx.agent.context.applyCompaction({ - summary: 'Summary.', - compactedCount: 2, - tokensBefore: 100, - }); - - const keptParts = ctx.agent.context.history.flatMap((message) => message.content); - expect(keptParts.some((part) => part.type === 'image_url')).toBe(true); - }); }); describe('compaction — summarizer request media handling', () => { diff --git a/packages/agent-core/test/agent/compaction/full.test.ts b/packages/agent-core/test/agent/compaction/full.test.ts index abc4138dfa..9e56848624 100644 --- a/packages/agent-core/test/agent/compaction/full.test.ts +++ b/packages/agent-core/test/agent/compaction/full.test.ts @@ -24,7 +24,6 @@ import { DefaultCompactionStrategy, type CompactionStrategy, } from '../../../src/agent/compaction'; -import { FLAG_DEFINITIONS, MASTER_ENV } from '../../../src/flags'; import { HookEngine, type HookEngineTriggerArgs } from '../../../src/session/hooks'; import { estimateTokens, estimateTokensForMessages } from '../../../src/utils/tokens'; import { recordingTelemetry, type TelemetryRecord } from '../../fixtures/telemetry'; @@ -46,7 +45,6 @@ const CATALOGUED_MODEL_CAPABILITIES = { tool_use: true, max_context_tokens: 256_000, } as const; -const MICRO_COMPACTION_FLAG_ENV = getMicroCompactionFlagEnv(); describe('FullCompaction', () => { it('runs manual compaction and applies the compacted context', async () => { @@ -374,42 +372,6 @@ describe('FullCompaction', () => { ).toBe(false); }); - // Micro compaction is disabled; this scenario is skipped because the feature - // can no longer be enabled. - it.skip('micro-compacts old tool results before sending the summary request', async () => { - vi.useFakeTimers(); - enableMicroCompactionFlag(); - const ctx = testAgent({ - compactionStrategy: alwaysCompactOnce, - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - minContextUsageRatio: 0, - }, - }); - ctx.configure({ - provider: CATALOGUED_PROVIDER, - modelCapabilities: CATALOGUED_MODEL_CAPABILITIES, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(61 * 60 * 1000); - - ctx.agent.microCompaction.detect(); - const compacted = ctx.once('context.apply_compaction'); - ctx.mockNextResponse({ type: 'text', text: 'Compacted summary.' }); - await ctx.rpc.beginCompaction({ instruction: 'Summarize tool exchanges.' }); - await compacted; - - const [compactionCall] = ctx.llmCalls; - expect(messageText(compactionCall?.history[2])).toBe('[Old tool result content cleared]'); - expect(messageText(compactionCall?.history[5])).toBe('lookup result'); - }); - it('force-refreshes OAuth credentials on compaction 401 and treats replay 401 as provider auth error', async () => { const records: TelemetryRecord[] = []; const tokenCalls: Array = []; @@ -2520,17 +2482,6 @@ afterEach(() => { vi.unstubAllEnvs(); }); -function enableMicroCompactionFlag(): void { - vi.stubEnv(MASTER_ENV, '0'); - vi.stubEnv(MICRO_COMPACTION_FLAG_ENV, '1'); -} - -function getMicroCompactionFlagEnv(): string { - // Micro compaction is disabled and its flag has been removed from the registry; - // the env var name is kept so the (skipped) test still type-checks. - return 'KIMI_CODE_EXPERIMENTAL_MICRO_COMPACTION'; -} - function deferred() { let resolve!: (value: T | PromiseLike) => void; let reject!: (reason?: unknown) => void; diff --git a/packages/agent-core/test/agent/compaction/micro.test.ts b/packages/agent-core/test/agent/compaction/micro.test.ts deleted file mode 100644 index 2193bb79ef..0000000000 --- a/packages/agent-core/test/agent/compaction/micro.test.ts +++ /dev/null @@ -1,990 +0,0 @@ -import type { ContentPart, Message } from '@moonshot-ai/kosong'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; - -import type { AgentRecord } from '../../../src/agent'; -import { - AGENT_WIRE_PROTOCOL_VERSION, - InMemoryAgentRecordPersistence, -} from '../../../src/agent/records'; -import { FLAG_DEFINITIONS, FlagResolver, MASTER_ENV } from '../../../src/flags'; -import { estimateTokensForMessages } from '../../../src/utils/tokens'; -import { recordingTelemetry, type TelemetryRecord } from '../../fixtures/telemetry'; -import { testAgent, type TestAgentContext } from '../harness/agent'; - -const CATALOGUED_PROVIDER = { - type: 'kimi', - apiKey: 'test-key', - model: 'kimi-code', -} as const; -const CATALOGUED_MODEL_CAPABILITIES = { - image_in: true, - video_in: true, - audio_in: false, - thinking: true, - tool_use: true, - max_context_tokens: 256_000, -} as const; - -const MINUTE = 60 * 1000; -const DEFAULT_MARKER = '[Old tool result content cleared]'; -const MICRO_COMPACTION_FLAG_ENV = getMicroCompactionFlagEnv(); - -// Micro compaction is disabled and its flag has been removed; the suite is -// skipped because the feature can no longer be enabled. -describe.skip('MicroCompaction', () => { - beforeEach(() => { - vi.stubEnv(MASTER_ENV, '0'); - vi.stubEnv(MICRO_COMPACTION_FLAG_ENV, '1'); - }); - - // The micro_compaction flag no longer exists, so there is no default to assert. - // it('defaults the micro_compaction flag off', () => { - // expect(new FlagResolver({}, FLAG_DEFINITIONS).enabled('micro_compaction')).toBe(false); - // }); - - it('truncates old tool results after cache miss', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 4, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - minContextUsageRatio: 0, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - expect(ctx.agent.context.messages).toHaveLength(9); - - vi.setSystemTime(61 * 60 * 1000); - - ctx.agent.microCompaction.detect(); - const messages = ctx.agent.context.messages; - expect(messages[2]).toMatchObject({ - role: 'tool', - content: [{ type: 'text', text: DEFAULT_MARKER }], - }); - expect(messages[5]).toMatchObject({ - role: 'tool', - content: [{ type: 'text', text: 'lookup result' }], - }); - expect(messages[8]).toMatchObject({ - role: 'tool', - content: [{ type: 'text', text: 'lookup result' }], - }); - }); - - it('does nothing before cache miss threshold', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 4, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(30 * 60 * 1000); - - const messages = ctx.agent.context.messages; - expect(hasMarker(messages)).toBe(false); - }); - - it('persists cutoff across calls until cache miss resets it', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - minContextUsageRatio: 0, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(61 * 60 * 1000); - - ctx.agent.microCompaction.detect(); - const first = ctx.agent.context.messages; - expect(first[2]).toMatchObject({ - role: 'tool', - content: [{ type: 'text', text: DEFAULT_MARKER }], - }); - - vi.setSystemTime(62 * 60 * 1000); - - ctx.agent.microCompaction.detect(); - const second = ctx.agent.context.messages; - expect(second[2]).toMatchObject({ - role: 'tool', - content: [{ type: 'text', text: DEFAULT_MARKER }], - }); - }); - - it('clears cutoff on reset', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 4, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(61 * 60 * 1000); - - ctx.agent.microCompaction.reset(); - - const messages = ctx.agent.context.messages; - expect(hasMarker(messages)).toBe(false); - }); - - it('skips tool results below minContentTokens', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 100, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(61 * 60 * 1000); - - const messages = ctx.agent.context.messages; - expect(hasMarker(messages)).toBe(false); - }); - - it('skips non-tool messages', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - - vi.setSystemTime(0); - ctx.appendExchange(1, 'user one', 'assistant one', 10); - ctx.appendExchange(2, 'user two', 'assistant two', 10); - ctx.appendExchange(3, 'user three', 'assistant three', 10); - - vi.setSystemTime(61 * 60 * 1000); - - const messages = ctx.agent.context.messages; - expect(messages.every((m) => m.role === 'user' || m.role === 'assistant')).toBe(true); - expect(hasMarker(messages)).toBe(false); - }); - - it('clears cutoff on context clear', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(61 * 60 * 1000); - - ctx.agent.context.clear(); - - expect(ctx.agent.context.messages).toHaveLength(0); - expect(ctx.agent.context.lastAssistantAt).toBeNull(); - }); - - it('sends truncated old tool results to the next model request without mutating history', async () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 4, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - }); - ctx.configure({ - provider: CATALOGUED_PROVIDER, - modelCapabilities: CATALOGUED_MODEL_CAPABILITIES, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'old result one' }); - appendMicroToolExchange(ctx, 2, { output: 'middle result two' }); - appendMicroToolExchange(ctx, 3, { output: 'recent result three' }); - - vi.setSystemTime(61 * MINUTE); - - ctx.mockNextResponse({ type: 'text', text: 'done after micro compaction' }); - await ctx.rpc.prompt({ input: [{ type: 'text', text: 'continue' }] }); - await ctx.untilTurnEnd(); - - const call = ctx.llmCalls.at(-1); - expect(textOf(call?.history[2])).toBe(DEFAULT_MARKER); - expect(textOf(call?.history[5])).toBe(DEFAULT_MARKER); - expect(textOf(call?.history[8])).toBe('recent result three'); - - expect(textOf(ctx.agent.context.history[2])).toBe('old result one'); - expect(textOf(ctx.agent.context.history[5])).toBe('middle result two'); - expect(textOf(ctx.agent.context.history[8])).toBe('recent result three'); - await ctx.expectResumeMatches(); - }); - - it('restores lastAssistantAt from record time before applying cache-miss rules', async () => { - vi.useFakeTimers(); - const assistantRecordTime = 2_000; - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - persistence: new InMemoryAgentRecordPersistence( - resumeToolExchangeRecords(assistantRecordTime), - ), - }); - - vi.setSystemTime(999_999); - await ctx.agent.resume(); - - expect(ctx.agent.context.lastAssistantAt).toBe(assistantRecordTime); - - vi.setSystemTime(assistantRecordTime + 30 * MINUTE); - expect(hasMarker(ctx.agent.context.messages)).toBe(false); - - vi.setSystemTime(assistantRecordTime + 61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([DEFAULT_MARKER]); - }); - - it('preserves the restored cutoff when resuming before the next cache miss', async () => { - vi.useFakeTimers(); - const persistence = new InMemoryAgentRecordPersistence(); - const config = { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }; - const ctx = testAgent({ - microCompaction: config, - persistence, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one' }); - appendMicroToolExchange(ctx, 2, { output: 'result two' }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([DEFAULT_MARKER, 'result two']); - expect(lastMicroCompactionCutoff(persistence.records)).toBe(4); - - vi.setSystemTime(62 * MINUTE); - appendMicroToolExchange(ctx, 3, { output: 'result three' }); - - const resumed = testAgent({ - microCompaction: config, - persistence: new InMemoryAgentRecordPersistence(cloneRecords(persistence.records)), - }); - - vi.setSystemTime(63 * MINUTE); - await resumed.agent.resume(); - - expect(resumed.agent.context.lastAssistantAt).toBe(62 * MINUTE); - expect(toolTexts(resumed.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - 'result two', - 'result three', - ]); - }); - - it('recomputes the restored cutoff when resuming after the cache-miss threshold', async () => { - vi.useFakeTimers(); - const persistence = new InMemoryAgentRecordPersistence(); - const config = { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }; - const ctx = testAgent({ - microCompaction: config, - persistence, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one' }); - appendMicroToolExchange(ctx, 2, { output: 'result two' }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([DEFAULT_MARKER, 'result two']); - expect(lastMicroCompactionCutoff(persistence.records)).toBe(4); - - vi.setSystemTime(62 * MINUTE); - appendMicroToolExchange(ctx, 3, { output: 'result three' }); - - const resumedPersistence = new InMemoryAgentRecordPersistence( - cloneRecords(persistence.records), - ); - const resumed = testAgent({ - microCompaction: config, - persistence: resumedPersistence, - }); - - vi.setSystemTime(123 * MINUTE); - await resumed.agent.resume(); - - expect(resumed.agent.context.lastAssistantAt).toBe(62 * MINUTE); - resumed.agent.microCompaction.detect(); - expect(toolTexts(resumed.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - DEFAULT_MARKER, - 'result three', - ]); - expect(lastMicroCompactionCutoff(resumedPersistence.records)).toBe(7); - }); - - it('keeps an old cutoff while cache is warm and advances it on the next miss', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one' }); - appendMicroToolExchange(ctx, 2, { output: 'result two' }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([DEFAULT_MARKER, 'result two']); - - vi.setSystemTime(62 * MINUTE); - appendMicroToolExchange(ctx, 3, { output: 'result three' }); - - vi.setSystemTime(63 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - 'result two', - 'result three', - ]); - - vi.setSystemTime(123 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - DEFAULT_MARKER, - 'result three', - ]); - }); - - it('clamps cutoff when undo shortens the context', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one' }); - appendMicroToolExchange(ctx, 2, { output: 'result two' }); - appendMicroToolExchange(ctx, 3, { output: 'result three' }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - DEFAULT_MARKER, - 'result three', - ]); - - ctx.agent.context.undo(2); - appendMicroToolExchange(ctx, 4, { output: 'result four' }); - - expect(toolTexts(ctx.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - 'result four', - ]); - }); - - it('tracks telemetry when a cache miss advances the micro_compaction cutoff', () => { - vi.useFakeTimers(); - const records: TelemetryRecord[] = []; - const microCompaction = { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }; - const ctx = testAgent({ - telemetry: recordingTelemetry(records), - microCompaction, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one '.repeat(20) }); - appendMicroToolExchange(ctx, 2, { output: 'result two '.repeat(20) }); - appendMicroToolExchange(ctx, 3, { output: 'result three' }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - DEFAULT_MARKER, - 'result three', - ]); - - const event = singleTelemetryEvent(records, 'micro_compaction_finished'); - expect(event.properties).toMatchObject({ - keep_recent_messages: microCompaction.keepRecentMessages, - min_content_tokens: microCompaction.minContentTokens, - cache_missed_threshold_ms: microCompaction.cacheMissedThresholdMs, - truncated_marker: DEFAULT_MARKER, - min_context_usage_ratio: microCompaction.minContextUsageRatio, - previous_cutoff: 0, - cutoff: 7, - message_count: 9, - cache_age_ms: 61 * MINUTE, - truncated_tool_result_count: 2, - truncated_tool_result_tokens_before: expect.any(Number), - truncated_tool_result_tokens_after: expect.any(Number), - tokens_before: expect.any(Number), - tokens_after: expect.any(Number), - thinking_effort: 'off', - }); - expect(numberProperty(event, 'truncated_tool_result_tokens_before')).toBeGreaterThan( - numberProperty(event, 'truncated_tool_result_tokens_after'), - ); - expect(numberProperty(event, 'tokens_before')).toBeGreaterThan( - numberProperty(event, 'tokens_after'), - ); - - expect(ctx.agent.context.messages).toHaveLength(9); - expect(records.filter((record) => record.event === 'micro_compaction_finished')).toHaveLength(1); - }); - - it('reports context token deltas from the previously compacted projection', () => { - vi.useFakeTimers(); - const records: TelemetryRecord[] = []; - const microCompaction = { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }; - const ctx = testAgent({ - telemetry: recordingTelemetry(records), - microCompaction, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one '.repeat(20) }); - appendMicroToolExchange(ctx, 2, { output: 'result two '.repeat(20) }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([ - DEFAULT_MARKER, - 'result two '.repeat(20), - ]); - - vi.setSystemTime(62 * MINUTE); - appendMicroToolExchange(ctx, 3, { output: 'result three' }); - const expectedContextTokensBefore = estimateTokensForMessages(ctx.agent.context.messages); - - vi.setSystemTime(123 * MINUTE); - ctx.agent.microCompaction.detect(); - - const events = records.filter((record) => record.event === 'micro_compaction_finished'); - expect(events).toHaveLength(2); - const secondEvent = events[1]!; - expect(secondEvent.properties).toMatchObject({ - previous_cutoff: 4, - cutoff: 7, - truncated_tool_result_count: 2, - tokens_before: expectedContextTokensBefore, - tokens_after: estimateTokensForMessages(ctx.agent.context.messages), - }); - }); - - it('leaves context unchanged when the micro_compaction flag is disabled', () => { - vi.stubEnv(MICRO_COMPACTION_FLAG_ENV, '0'); - vi.useFakeTimers(); - const persistence = new InMemoryAgentRecordPersistence(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - persistence, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one' }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.microCompaction.detect(); - - expect(toolTexts(ctx.agent.context.messages)).toEqual(['result one']); - expect(lastMicroCompactionCutoff(persistence.records)).toBeUndefined(); - }); - - it('uses the custom marker at the minContentTokens boundary', () => { - vi.useFakeTimers(); - const marker = '[tool output removed for test]'; - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - truncatedMarker: marker, - minContextUsageRatio: 0, - }, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'abcd' }); - - vi.setSystemTime(61 * MINUTE); - - ctx.agent.microCompaction.detect(); - expect(toolTexts(ctx.agent.context.messages)).toEqual([marker]); - expect(textOf(ctx.agent.context.history[2])).toBe('abcd'); - }); - - it('keeps raw pending token accounting even when projection truncates tool output', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - }); - ctx.configure(); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { - output: 'x'.repeat(400), - usageTokens: 50, - }); - - vi.setSystemTime(61 * MINUTE); - - ctx.agent.microCompaction.detect(); - const rawPending = ctx.agent.context.history.slice(-1); - const projectedPending = ctx.agent.context.project(rawPending); - expect(textOf(projectedPending[0])).toBe(DEFAULT_MARKER); - expect(ctx.agent.context.tokenCountWithPending).toBe( - ctx.agent.context.tokenCount + estimateTokensForMessages(rawPending), - ); - expect(ctx.agent.context.tokenCountWithPending).toBeGreaterThan( - ctx.agent.context.tokenCount + estimateTokensForMessages(projectedPending), - ); - }); - - it('replaces rich error tool content while preserving context metadata before projection', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0, - }, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { - output: [ - { type: 'text', text: 'large rich output' }, - { type: 'video_url', videoUrl: { url: 'ms://video-1', id: 'video-1' } }, - ], - isError: true, - }); - - vi.setSystemTime(61 * MINUTE); - - ctx.agent.microCompaction.detect(); - const compacted = ctx.agent.microCompaction.compact(ctx.agent.context.history); - const tool = compacted.find((message) => message.role === 'tool'); - expect(tool).toMatchObject({ - role: 'tool', - toolCallId: 'call_micro_1', - isError: true, - content: [{ type: 'text', text: DEFAULT_MARKER }], - }); - expect(tool?.content).toHaveLength(1); - }); - - it('does not truncate tool-shaped messages without a toolCallId', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - }, - }); - - vi.setSystemTime(61 * MINUTE); - ctx.agent.context.appendMessage({ - role: 'tool', - content: [{ type: 'text', text: 'orphan tool-like output' }], - toolCalls: [], - }); - - expect(toolTexts(ctx.agent.context.messages)).toEqual(['orphan tool-like output']); - }); - - it('clears cutoff on full compaction', async () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 2, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - ctx.configure({ - provider: CATALOGUED_PROVIDER, - modelCapabilities: CATALOGUED_MODEL_CAPABILITIES, - }); - - vi.setSystemTime(0); - ctx.appendExchange(1, 'old user', 'old assistant', 20); - ctx.appendExchange(2, 'recent user', 'recent assistant', 80); - - vi.setSystemTime(61 * 60 * 1000); - - const compacted = ctx.once('context.apply_compaction'); - ctx.mockNextResponse({ type: 'text', text: 'Summary.' }); - await ctx.rpc.beginCompaction({}); - await compacted; - - expect(ctx.agent.context.messages).toHaveLength(2); - expect(ctx.agent.context.messages[1]).toMatchObject({ - role: 'user', - content: [{ type: 'text', text: expect.stringContaining('Summary.') }], - }); - }); - - it('does not apply when context usage is below minContextUsageRatio', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0.9, - }, - }); - ctx.configure({ - provider: CATALOGUED_PROVIDER, - modelCapabilities: CATALOGUED_MODEL_CAPABILITIES, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'result one' }); - - vi.setSystemTime(61 * MINUTE); - - const messages = ctx.agent.context.messages; - expect(hasMarker(messages)).toBe(false); - }); - - it('applies when context usage is above minContextUsageRatio', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 0, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * MINUTE, - minContextUsageRatio: 0.5, - }, - }); - ctx.configure({ - provider: CATALOGUED_PROVIDER, - modelCapabilities: { - image_in: true, - video_in: true, - audio_in: false, - thinking: true, - tool_use: true, - max_context_tokens: 100, - }, - }); - - vi.setSystemTime(0); - appendMicroToolExchange(ctx, 1, { output: 'x'.repeat(300) }); - - vi.setSystemTime(61 * MINUTE); - - ctx.agent.microCompaction.detect(); - const messages = ctx.agent.context.messages; - expect(hasMarker(messages)).toBe(true); - }); - - it('does not truncate when messages are fewer than keepRecentMessages', () => { - vi.useFakeTimers(); - const ctx = testAgent({ - microCompaction: { - keepRecentMessages: 20, - minContentTokens: 1, - cacheMissedThresholdMs: 60 * 60 * 1000, - }, - }); - - vi.setSystemTime(0); - ctx.appendToolExchange(); - ctx.appendToolExchange(); - - vi.setSystemTime(61 * 60 * 1000); - - const messages = ctx.agent.context.messages; - expect(hasMarker(messages)).toBe(false); - }); -}); - -afterEach(() => { - vi.useRealTimers(); - vi.unstubAllEnvs(); -}); - -interface MicroToolExchangeOptions { - readonly output?: string | ContentPart[] | undefined; - readonly isError?: boolean | undefined; - readonly usageTokens?: number | undefined; -} - -function appendMicroToolExchange( - ctx: TestAgentContext, - index: number, - options: MicroToolExchangeOptions = {}, -): void { - const stepUuid = `micro-tool-step-${String(index)}`; - const toolCallId = `call_micro_${String(index)}`; - const output = options.output ?? `lookup result ${String(index)}`; - const usage = - options.usageTokens === undefined - ? undefined - : { - inputOther: options.usageTokens - 1, - output: 1, - inputCacheRead: 0, - inputCacheCreation: 0, - }; - - ctx.agent.context.appendUserMessage([{ type: 'text', text: `lookup ${String(index)}` }]); - ctx.dispatch({ - type: 'context.append_loop_event', - event: { type: 'step.begin', uuid: stepUuid, turnId: '', step: index }, - }); - ctx.dispatch({ - type: 'context.append_loop_event', - event: { - type: 'content.part', - uuid: `micro-tool-part-${String(index)}`, - turnId: '', - step: index, - stepUuid, - part: { type: 'text', text: `calling Lookup ${String(index)}` }, - }, - }); - ctx.dispatch({ - type: 'context.append_loop_event', - event: { - type: 'tool.call', - uuid: toolCallId, - turnId: '', - step: index, - stepUuid, - toolCallId, - name: 'Lookup', - args: { query: `item-${String(index)}` }, - }, - }); - ctx.dispatch({ - type: 'context.append_loop_event', - event: { - type: 'step.end', - uuid: stepUuid, - turnId: '', - step: index, - usage, - finishReason: 'tool_use', - }, - }); - ctx.dispatch({ - type: 'context.append_loop_event', - event: { - type: 'tool.result', - parentUuid: toolCallId, - toolCallId, - result: { output, isError: options.isError }, - }, - }); -} - -function resumeToolExchangeRecords(assistantRecordTime: number): AgentRecord[] { - return [ - { - type: 'metadata', - protocol_version: AGENT_WIRE_PROTOCOL_VERSION, - created_at: 1, - }, - { - type: 'context.append_message', - time: 1_000, - message: { - role: 'user', - content: [{ type: 'text', text: 'lookup from restored session' }], - toolCalls: [], - origin: { kind: 'user' }, - }, - }, - { - type: 'context.append_loop_event', - time: assistantRecordTime, - event: { type: 'step.begin', uuid: 'resume-micro-step', turnId: '0', step: 1 }, - }, - { - type: 'context.append_loop_event', - time: assistantRecordTime + 1, - event: { - type: 'content.part', - uuid: 'resume-micro-part', - turnId: '0', - step: 1, - stepUuid: 'resume-micro-step', - part: { type: 'text', text: 'calling restored Lookup' }, - }, - }, - { - type: 'context.append_loop_event', - time: assistantRecordTime + 2, - event: { - type: 'tool.call', - uuid: 'resume-micro-call', - turnId: '0', - step: 1, - stepUuid: 'resume-micro-step', - toolCallId: 'resume_micro_call', - name: 'Lookup', - args: { query: 'restored' }, - }, - }, - { - type: 'context.append_loop_event', - time: assistantRecordTime + 3, - event: { - type: 'step.end', - uuid: 'resume-micro-step', - turnId: '0', - step: 1, - finishReason: 'tool_use', - }, - }, - { - type: 'context.append_loop_event', - time: assistantRecordTime + 4, - event: { - type: 'tool.result', - parentUuid: 'resume-micro-call', - toolCallId: 'resume_micro_call', - result: { output: 'restored lookup result' }, - }, - }, - ]; -} - -function cloneRecords(records: readonly AgentRecord[]): AgentRecord[] { - return records.map((record) => structuredClone(record)); -} - -function lastMicroCompactionCutoff(records: readonly AgentRecord[]): number | undefined { - return records.findLast((record) => record.type === 'micro_compaction.apply')?.cutoff; -} - -function toolTexts(messages: readonly Message[]): string[] { - return messages - .filter((message) => message.role === 'tool') - .map((message) => textOf(message)); -} - -function textOf(message: Message | undefined): string { - return ( - message?.content - .map((part) => { - if (part.type === 'text') return part.text; - return ''; - }) - .join('') ?? '' - ); -} - -function hasMarker(messages: readonly Message[]): boolean { - return toolTexts(messages).includes(DEFAULT_MARKER); -} - -function getMicroCompactionFlagEnv(): string { - // Micro compaction is disabled and its flag has been removed from the registry; - // the env var name is kept so the (skipped) suite still type-checks. - return 'KIMI_CODE_EXPERIMENTAL_MICRO_COMPACTION'; -} - -function singleTelemetryEvent( - records: readonly TelemetryRecord[], - event: string, -): TelemetryRecord { - const matches = records.filter((record) => record.event === event); - expect(matches).toHaveLength(1); - return matches[0]!; -} - -function numberProperty(record: TelemetryRecord, key: string): number { - const value = record.properties?.[key]; - expect(typeof value).toBe('number'); - return value as number; -} diff --git a/packages/agent-core/test/harness/runtime.test.ts b/packages/agent-core/test/harness/runtime.test.ts index 912a6b5df2..7318d3c29e 100644 --- a/packages/agent-core/test/harness/runtime.test.ts +++ b/packages/agent-core/test/harness/runtime.test.ts @@ -6,8 +6,6 @@ import type { Kaos } from '@moonshot-ai/kaos'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { - FLAG_DEFINITIONS, - MASTER_ENV, createRPC, ErrorCodes, KimiCore, @@ -16,31 +14,10 @@ import { type CoreAPI, type SDKAPI, } from '../../src'; -import { - __resetRootLoggerForTest, - getRootLogger, - resolveGlobalLogPath, -} from '../../src/logging/logger'; -import { resolveLoggingConfig } from '../../src/logging/resolve-config'; +import { __resetRootLoggerForTest } from '../../src/logging/logger'; import type { OAuthTokenProviderResolver } from '../../src/session/provider-manager'; import { testKaos } from '../fixtures/test-kaos'; -function requiredFlagEnv(id: string): string { - // Micro compaction was the only registered flag and has been removed, so the - // env var name is derived directly; the (skipped) tests still type-check. - return `KIMI_CODE_EXPERIMENTAL_${id.toUpperCase()}`; -} - -function clearExperimentalEnv(): void { - vi.stubEnv(MASTER_ENV, '0'); - // No experimental flags are currently registered, so there are no per-flag - // env vars to clear. -} - -function experimentalFeatureEnabled(core: KimiCore, id: string): boolean | undefined { - return core.getExperimentalFeatures().find((feature) => feature.id === id)?.enabled; -} - function setCoreKaos(core: KimiCore, kaos: Promise): void { (core as unknown as { kaos?: Promise }).kaos = kaos; } @@ -93,141 +70,6 @@ describe('KimiCore runtime config', () => { vi.unstubAllGlobals(); }); - // Micro compaction was the only experimental flag and has been removed; this - // test is skipped because there is no flag to enable. - it.skip('logs all enabled experimental flags once on core startup', async () => { - tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); - const homeDir = join(tmp, 'home'); - await mkdir(homeDir, { recursive: true }); - await getRootLogger().configure(resolveLoggingConfig({ homeDir })); - - vi.stubEnv(MASTER_ENV, '0'); - // No experimental flags are currently registered, so there is nothing to clear. - // for (const def of FLAG_DEFINITIONS) { - // vi.stubEnv(def.env, '0'); - // } - vi.stubEnv(requiredFlagEnv('micro_compaction'), '1'); - - void new KimiCore(async () => ({}) as never, { homeDir }); - await getRootLogger().flushGlobal(); - - const text = await readFile(resolveGlobalLogPath(homeDir), 'utf-8'); - expect(text).toContain('experimental flags enabled'); - expect(text).toContain('micro_compaction'); - expect(text.match(/experimental flags enabled/g)).toHaveLength(1); - }); - - // Micro compaction was the only experimental flag and has been removed; this - // test is skipped because there is no flag to resolve. - it.skip('resolves experimental flags from each core config independently', async () => { - tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); - const firstHome = join(tmp, 'first-home'); - const secondHome = join(tmp, 'second-home'); - await mkdir(firstHome, { recursive: true }); - await mkdir(secondHome, { recursive: true }); - await writeFile( - join(firstHome, 'config.toml'), - ` -[experimental] -micro_compaction = true -`, - ); - await writeFile( - join(secondHome, 'config.toml'), - ` -[experimental] -micro_compaction = false -`, - ); - clearExperimentalEnv(); - - const first = new KimiCore(async () => ({}) as never, { homeDir: firstHome }); - const second = new KimiCore(async () => ({}) as never, { homeDir: secondHome }); - - expect(experimentalFeatureEnabled(first, 'micro_compaction')).toBe(true); - expect(experimentalFeatureEnabled(second, 'micro_compaction')).toBe(false); - }); - - // Micro compaction was the only experimental flag and has been removed; this - // test is skipped because there is no flag to update. - it.skip('updates the scoped experimental resolver after setKimiConfig', async () => { - tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); - const homeDir = join(tmp, 'home'); - await mkdir(homeDir, { recursive: true }); - await writeFile( - join(homeDir, 'config.toml'), - ` -[experimental] -micro_compaction = false -`, - ); - clearExperimentalEnv(); - - const core = new KimiCore(async () => ({}) as never, { homeDir }); - expect(experimentalFeatureEnabled(core, 'micro_compaction')).toBe(false); - - await core.setKimiConfig({ - experimental: { - 'micro_compaction': true, - }, - }); - - expect(experimentalFeatureEnabled(core, 'micro_compaction')).toBe(true); - }); - - // Micro compaction was the only experimental flag and has been removed; this - // test is skipped because there is no flag to update. - it.skip('updates the shared experimental resolver while goal tools stay available', async () => { - tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); - const homeDir = join(tmp, 'home'); - const workDir = join(tmp, 'work'); - await mkdir(homeDir, { recursive: true }); - await mkdir(workDir, { recursive: true }); - await writeFile( - join(homeDir, 'config.toml'), - `${baseModelConfig()} -[experimental] -micro_compaction = false -`, - ); - clearExperimentalEnv(); - - const [coreRpc, sdkRpc] = createRPC(); - const core = new KimiCore(coreRpc, { homeDir }); - const rpc = await sdkRpc({ - emitEvent: vi.fn(), - requestApproval: vi.fn(async (): Promise => ({ decision: 'rejected' })), - requestQuestion: vi.fn(async () => null), - toolCall: vi.fn(async () => ({ output: '' })), - }); - - const created = await rpc.createSession({ - id: 'ses_runtime_experimental_refresh', - workDir, - model: 'default-mock', - }); - const session = core.sessions.get(created.id); - const mainAgent = session?.getReadyAgent('main'); - - // expect(session?.experimentalFlags.enabled('micro_compaction')).toBe(false); - // expect(mainAgent?.experimentalFlags.enabled('micro_compaction')).toBe(false); - expect(mainAgent?.tools.data().some((tool) => tool.name === 'CreateGoal')).toBe(true); - - await core.setKimiConfig({ - experimental: { - 'micro_compaction': true, - }, - }); - - // expect(session?.experimentalFlags.enabled('micro_compaction')).toBe(true); - // expect(mainAgent?.experimentalFlags.enabled('micro_compaction')).toBe(true); - expect(mainAgent?.tools.data().some((tool) => tool.name === 'CreateGoal')).toBe(true); - - await rpc.reloadSession({ sessionId: created.id }); - const reloadedMainAgent = core.sessions.get(created.id)?.getReadyAgent('main'); - expect(reloadedMainAgent?.tools.data().some((tool) => tool.name === 'CreateGoal')).toBe(true); - }); - it('live-applies the complete persisted secondary recipe', async () => { tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); const homeDir = join(tmp, 'home'); diff --git a/packages/agent-core/test/session/cron-stop-on-close.test.ts b/packages/agent-core/test/session/cron-stop-on-close.test.ts index 9947a86ec1..beaa96fa78 100644 --- a/packages/agent-core/test/session/cron-stop-on-close.test.ts +++ b/packages/agent-core/test/session/cron-stop-on-close.test.ts @@ -26,30 +26,11 @@ afterEach(async () => { }); describe('Session.close stops cron', () => { - it('stops each agent cron scheduler on close', async () => { - const { sessionDir, workDir } = await sessionFixture(); - const session = new Session({ - kaos: testKaos.withCwd(workDir), - id: 'session-cron-stop', - homedir: sessionDir, - rpc: createSessionRpc(), - skills: { explicitDirs: [join(workDir, 'missing-skills')] }, - }); - const main = await session.createMain(); - const stopSpy = vi.spyOn(main.cron!, 'stop'); - - await session.close(); - - expect(stopSpy).toHaveBeenCalledTimes(1); - }); - it('observably tears down cron side effects (SIGUSR1 listener cleared)', async () => { - // The spy-only test above proves `stop()` was called but would - // still pass if `stop()` no-op'd. Gate manual-tick mode so the - // CronManager binds a real SIGUSR1 listener, then assert the - // listener count returns to its pre-construction baseline after - // `session.close()`. Anything short of `unbindSigusr1` running - // would leak a listener. + // Gate manual-tick mode so the CronManager binds a real SIGUSR1 + // listener, then assert the listener count returns to its + // pre-construction baseline after `session.close()`. Anything short + // of `unbindSigusr1` running would leak a listener. if (process.platform === 'win32') return; vi.stubEnv('KIMI_CRON_MANUAL_TICK', '1'); diff --git a/packages/kap-server/test/search/searchService.test.ts b/packages/kap-server/test/search/searchService.test.ts index dc9eb305ed..9e007e228a 100644 --- a/packages/kap-server/test/search/searchService.test.ts +++ b/packages/kap-server/test/search/searchService.test.ts @@ -2796,30 +2796,6 @@ describe('search lifecycle diagnostics (stage 5)', () => { expect(status.degraded).toContain('metadata store down'); }); - it('a restart attaches the published generation instead of rebuilding (inline)', async () => { - const s1 = summary('s1', '代际复用', T1); - await writeWire(home!, 's1', 'main', [userLine('苹果 generation-reuse', T1)]); - const first = track(makeInlineService(home!, staticIndex([s1]))); - await first.reindex(); - const firstCore = coreOf(first) as unknown as { - db: { buildGeneration(trigger: 'manual'): Promise } | null; - }; - await firstCore.db!.buildGeneration('manual'); - first.dispose(); - await drainGlobalSearchDisposals(); - - const second = track(makeInlineService(home!, staticIndex([s1]))); - await settleSync(second); - const page = await second.search({ query: '苹果' }); - expect(page.items.length).toBe(1); - const core = coreOf(second) as unknown as { - db: { lifecycleStatus(): { path: string[] } } | null; - }; - const path = core.db!.lifecycleStatus().path; - expect(path).toContain('generation-load'); - expect(path).not.toContain('full-rebuild'); - }); - it('concurrent cold calls open the index exactly once (inline)', async () => { const s1 = summary('s1', '单次打开', T1); await writeWire(home!, 's1', 'main', [userLine('苹果 single-open', T1)]); diff --git a/packages/minidb/test/e2e/compaction-race.test.ts b/packages/minidb/test/e2e/compaction-race.test.ts index 2bcc68aeeb..3fae8b52ec 100644 --- a/packages/minidb/test/e2e/compaction-race.test.ts +++ b/packages/minidb/test/e2e/compaction-race.test.ts @@ -9,67 +9,6 @@ import assert from 'node:assert/strict'; import { MiniDb } from '../../src/index.js'; import { tmpDir, rmrf } from './helpers/tmp.js'; -test('compaction-race: concurrent writes + frequent compaction lose nothing', { timeout: 30_000 }, async () => { - const dir = await tmpDir(); - let db = await MiniDb.open({ - dir, - valueCodec: 'json', - fsyncPolicy: 'no', - compactThresholdBytes: 2048, // tiny -> compaction triggers a lot - }); - const N = 1000; - const written = new Map(); - try { - const ops = []; - for (let i = 0; i < N; i++) { - const k = 'k' + i; - const v = { i, pad: 'x'.repeat(30) }; - written.set(k, v); - ops.push(db.set(k, v)); - if (i % 100 === 0) ops.push(db.compact().then(() => {})); // manual compaction, concurrent - } - await Promise.all(ops); - if (db.compacting) await db._compactDone; - - for (const [k, v] of written) assert.deepEqual(db.get(k), v, `get(${k})`); - assert.equal(db.size, N); - const compactionsRan = db.stats.compactions; - - // recoverable + still correct - await db.close(); - db = await MiniDb.open({ dir, valueCodec: 'json' }); - for (const [k, v] of written) assert.deepEqual(db.get(k), v, `reopen get(${k})`); - assert.equal(db.size, N); - assert.ok(compactionsRan >= 1, 'at least one compaction ran'); - } finally { - await db.close().catch(() => {}); - await rmrf(dir); - } -}); - -test('compaction-race: reads remain available during compaction', { timeout: 30_000 }, async () => { - const dir = await tmpDir(); - const db = await MiniDb.open({ - dir, - valueCodec: 'json', - fsyncPolicy: 'no', - compactThresholdBytes: 4096, - }); - try { - for (let i = 0; i < 200; i++) await db.set('k' + i, { i }); - // trigger compaction but don't await; reads should still work immediately - const cp = db.compact(); - for (let i = 0; i < 200; i++) { - const v = db.get('k' + i); - assert.deepEqual(v, { i }, `read during compaction k${i}`); - } - await cp; - } finally { - await db.close().catch(() => {}); - await rmrf(dir); - } -}); - test('compaction-race: snapshot phase does not block writes', { timeout: 30_000 }, async () => { // A write issued while a large snapshot is being written must complete // BEFORE the whole compaction finishes — i.e. the snapshot phase is @@ -132,88 +71,6 @@ test('compaction-race: snapshot phase does not block writes', { timeout: 30_000 } }); -test('compaction-race: heavy writes during compaction grow a WAL tail that survives recovery', { timeout: 30_000 }, async () => { - // Sustained writes during compaction force the pre-copy loop to drain a real - // WAL tail; the tail must be replayed on top of the snapshot after a reopen. - const dir = await tmpDir(); - let db = await MiniDb.open({ - dir, - valueCodec: 'json', - fsyncPolicy: 'no', - compactThresholdBytes: 1 << 30, - }); - try { - // 10k keys span 5 writeSnapshot yield windows (yieldEvery=2000, src/snapshot.ts), - // so compaction is still in progress while the writes below land. - const N = 10_000; - for (let base = 0; base < N; base += 500) { - await db.batch( - Array.from({ length: Math.min(500, N - base) }, (_, j) => ({ - op: 'set' as const, - key: 'k' + (base + j), - value: { i: base + j }, - })), - ); - } - - const cp = db.compact(); - // ~55 B/frame × 2000 ≈ 110 KB post-fence tail > SMALL_DELTA (64 KiB, - // src/compaction.ts), so the pre-copy loop still drains a real WAL tail. - const M = 2000; - const writes: Promise[] = []; - for (let i = 0; i < M; i++) writes.push(db.set('k' + i, { i, bumped: true })); - await Promise.all(writes); - await cp; - await db.close(); - - db = await MiniDb.open({ dir, valueCodec: 'json' }); - for (let i = 0; i < M; i++) assert.deepEqual(db.get('k' + i), { i, bumped: true }, `bumped k${i}`); - for (let i = M; i < N; i++) assert.deepEqual(db.get('k' + i), { i }, `untouched k${i}`); - assert.equal(db.size, N); - } finally { - await db.close().catch(() => {}); - await rmrf(dir); - } -}); - -test('compaction-race: valueMode disk preserves concurrent writes and remaps pointers', { timeout: 30_000 }, async () => { - const dir = await tmpDir(); - let db = await MiniDb.open({ - dir, - valueCodec: 'json', - valueMode: 'disk', - fsyncPolicy: 'no', - compactThresholdBytes: 2048, - }); - const N = 300; - const written = new Map(); - try { - const ops = []; - for (let i = 0; i < N; i++) { - const k = 'k' + i; - const v = { i, pad: 'x'.repeat(100) }; - written.set(k, v); - ops.push(db.set(k, v)); - if (i % 50 === 0) ops.push(db.compact().then(() => {})); - } - await Promise.all(ops); - if (db.compacting) await db._compactDone; - - for (const [k, v] of written) assert.deepEqual(db.get(k), v, `get(${k})`); - assert.equal(db.size, N); - const sawDiskRef = [...db.store.map.values()].some((r) => r.ref.kind === 'disk'); - assert.ok(sawDiskRef, 'expected disk-backed value refs after compaction'); - - await db.close(); - db = await MiniDb.open({ dir, valueCodec: 'json', valueMode: 'disk' }); - for (const [k, v] of written) assert.deepEqual(db.get(k), v, `reopen get(${k})`); - assert.equal(db.size, N); - } finally { - await db.close().catch(() => {}); - await rmrf(dir); - } -}); - // Regression: under sustained writes whose append rate approaches the pre-copy // rate, auto-compactions previously never converged (stats.compactions stayed 0 // until the storm stopped; the WAL grew unboundedly). Compaction must now give diff --git a/packages/minidb/test/review-fixes.test.ts b/packages/minidb/test/review-fixes.test.ts index 73e20602a6..fafc0df33b 100644 --- a/packages/minidb/test/review-fixes.test.ts +++ b/packages/minidb/test/review-fixes.test.ts @@ -41,39 +41,6 @@ test('WAL.flush() drains frames queued behind an in-flight batch', async () => { } }); -// --- P0: compaction must not lose concurrent writes (clean restart) --------- - -for (const policy of ['always', 'everysec', 'no'] as const) { - test(`compact + concurrent writes survive clean close+reopen (fsync=${policy})`, async () => { - const dir = await tmpDir(); - try { - const db = await MiniDb.open({ - dir, - valueCodec: 'string', - fsyncPolicy: policy, - compactThresholdBytes: 1, - autoCompact: false, - }); - for (let i = 0; i < 50; i++) await db.set(`seed${i}`, 'x'.repeat(64)); - - const N = 500; - const big = 'y'.repeat(4096); - const writes: Promise[] = []; - for (let i = 0; i < N; i++) writes.push(db.set(`live${i}`, big)); - await Promise.all([db.compact(), ...writes]); - await db.close(); - - const db2 = await MiniDb.open({ dir, valueCodec: 'string' }); - const lost: string[] = []; - for (let i = 0; i < N; i++) if (db2.get(`live${i}`) !== big) lost.push(`live${i}`); - await db2.close(); - assert.deepEqual(lost, [], `lost ${lost.length}/${N} keys: ${lost.slice(0, 5).join(',')}`); - } finally { - await fs.rm(dir, { recursive: true, force: true }); - } - }); -} - // --- P0: TTL expiration must drop derived index entries --------------------- test('expired keys are removed from secondary indexes', async () => { @@ -105,28 +72,6 @@ test('expired keys are removed from the full-text index', async () => { } }); -// --- P1: batch() must enforce unique indexes within the batch --------------- - -test('batch() rejects intra-batch unique violations', async () => { - const dir = await tmpDir(); - try { - const db = await MiniDb.open({ dir, valueCodec: 'json' }); - await db.createIndex('byMail', { field: 'email', unique: true }); - await assert.rejects( - db.batch([ - { op: 'set', key: 'a', value: { email: 'duplicate@example.test' } }, - { op: 'set', key: 'b', value: { email: 'duplicate@example.test' } }, - ]), - /unique/i, - ); - assert.equal(db.get('a'), undefined, 'nothing committed on failure'); - assert.equal(db.get('b'), undefined); - await db.close(); - } finally { - await fs.rm(dir, { recursive: true, force: true }); - } -}); - // --- P1: recovery must drop records whose TTL already elapsed --------------- test('recovery drops expired records (size consistent with scan)', async () => { diff --git a/packages/minidb/test/review-round2.test.ts b/packages/minidb/test/review-round2.test.ts index 8396203ea7..3b1bd99b5e 100644 --- a/packages/minidb/test/review-round2.test.ts +++ b/packages/minidb/test/review-round2.test.ts @@ -219,17 +219,6 @@ test('batch allows del(u1) + set(u2) reusing u1 unique value', async () => { await fs.rm(dir, { recursive: true, force: true }); }); -test('batch still rejects genuine unique violations', async () => { - const dir = await tmpDir(); - const db = await MiniDb.open({ dir, valueCodec: 'json' }); - await db.createIndex('byMail', { field: 'email', unique: true }); - await db.set('u1', { email: 'duplicate@example.test' }); - await assert.rejects(db.batch([{ op: 'set', key: 'u2', value: { email: 'duplicate@example.test' } }]), /unique/i); - assert.equal(db.get('u2'), undefined); - await db.close(); - await fs.rm(dir, { recursive: true, force: true }); -}); - // --- #7 createIndex(unique) rejects existing duplicates --------------------- test('createIndex(unique) rejects existing duplicate data', async () => { @@ -267,20 +256,6 @@ test('repeated TTL updates on one key do not bloat the heap', { timeout: 30_000 await fs.rm(dir, { recursive: true, force: true }); }); -// --- #10 size excludes expired keys ----------------------------------------- - -test('size excludes expired-but-not-yet-reaped keys', async () => { - const dir = await tmpDir(); - const db = await MiniDb.open({ dir, valueCodec: 'string', activeExpireIntervalMs: 0 }); - await db.set('e', 'v', { ttl: 1 }); - await db.set('s', 'ok'); - await new Promise((r) => setTimeout(r, 10)); - assert.equal(db.size, 1); - assert.equal(db.scan().length, 1); - await db.close(); - await fs.rm(dir, { recursive: true, force: true }); -}); - // --- #11 range index indexes array fields per element ----------------------- test('range index indexes array fields per element', async () => { @@ -311,35 +286,6 @@ test('dtColumns drops columns that no record has anymore', async () => { await fs.rm(dir, { recursive: true, force: true }); }); -// --- #13 RESP MSET sets all keys (atomic via batch) ------------------------- - -test('RESP MSET sets all keys', async () => { - const dir = await tmpDir(); - const { port, close } = await startServer({ dir, port: 0 }); - try { - const raw = await new Promise((resolve, reject) => { - const sock = net.connect(port, '127.0.0.1'); - const chunks: Buffer[] = []; - sock.on('data', (c) => chunks.push(c)); - sock.on('connect', () => { - sock.write('MSET a 1 b 2 c 3\r\n'); - setTimeout(() => sock.write('GET a\r\nGET b\r\nGET c\r\n'), 40); - setTimeout(() => sock.end(), 140); - }); - sock.on('end', () => resolve(Buffer.concat(chunks))); - sock.on('error', reject); - }); - const s = raw.toString('binary'); - assert.ok(s.includes('+OK')); - assert.ok(s.includes('$1\r\n1\r\n')); - assert.ok(s.includes('$1\r\n2\r\n')); - assert.ok(s.includes('$1\r\n3\r\n')); - } finally { - await close(); - await fs.rm(dir, { recursive: true, force: true }); - } -}); - // --- #14 openOrRebuild only rebuilds on corruption -------------------------- test('openOrRebuild rebuilds on corrupt index-definition JSON', async () => { diff --git a/vitest.config.ts b/vitest.config.ts index 841d39ee3d..af5e56f3aa 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - projects: ['packages/*', 'apps/kimi-code', 'apps/vscode'], + projects: ['packages/*', '!packages/agent-core', 'apps/kimi-code', 'apps/vscode'], coverage: { provider: 'v8', include: ['packages/*/src/**/*.ts', 'apps/*/src/**/*.ts'],