qwen-code/packages/core/src/services/sessionRecap.test.ts
易良 9bb68b323c
fix(core): strip system-reminder blocks from session title and recap side-query prompts (#6435)
* fix(core): strip system-reminder pollution from session title and recap prompts

The `filterToDialog` function in sessionTitle.ts and sessionRecap.ts
was including system-reminder blocks (skills list, CLAUDE.md, MCP
announcements) in the prompt sent to the title/recap model. When the
user's first message was short, `flattenToTail` would reach back into
these injected entries, causing titles like "resolve-cr-comments" instead
of reflecting the actual conversation.

- Skip startup prelude entries via `getStartupContextLength`
- Strip `<system-reminder>` blocks from text parts
- Add shared `stripSystemReminderBlocks` helper in environmentContext.ts
- Add regression tests for both sessionTitle and sessionRecap

Closes #6419

* fix(core): preserve mixed reminder prompt turns
2026-07-07 11:49:19 +00:00

75 lines
2.4 KiB
TypeScript

/**
* @license
* Copyright 2025 Qwen Code
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it, vi } from 'vitest';
import type { Content } from '@google/genai';
import type { Config } from '../config/config.js';
import {
SYSTEM_REMINDER_CLOSE,
SYSTEM_REMINDER_OPEN,
} from '../utils/environmentContext.js';
import { generateSessionRecap } from './sessionRecap.js';
const reminder = (body: string) =>
`${SYSTEM_REMINDER_OPEN}\n${body}\n${SYSTEM_REMINDER_CLOSE}`;
describe('generateSessionRecap', () => {
it('strips startup and mid-session system reminders from recap input', async () => {
const history: Content[] = [
{
role: 'user',
parts: [
{ text: reminder('STARTUP_SKILL_LIST') },
{ text: 'fix session title pollution' },
],
},
{ role: 'model', parts: [{ text: 'I found the title service.' }] },
{ role: 'user', parts: [{ text: reminder('ADDED_MCP_TOOLS') }] },
{
role: 'user',
parts: [
{ text: reminder('PLAN_MODE_REMINDER') },
{
text: `continue with recap coverage\n${reminder('IDE_CONTEXT')}`,
},
],
},
];
let captured: Content[] | null = null;
const generateText = vi.fn(async (opts: { contents: Content[] }) => {
captured = opts.contents;
return {
text: '<recap>Fixing session title pollution. Next: verify tests.</recap>',
usage: undefined,
};
});
const config = {
getFastModel: vi.fn(() => 'qwen-turbo'),
getModel: vi.fn(() => 'qwen-plus'),
getGeminiClient: vi.fn(() => ({
getHistoryShallow: () => history,
})),
getBaseLlmClient: vi.fn(() => ({ generateText })),
getOutputLanguageFilePath: vi.fn(() => undefined),
} as unknown as Config;
const result = await generateSessionRecap(
config,
new AbortController().signal,
);
expect(result).toBe('Fixing session title pollution. Next: verify tests.');
expect(captured).not.toBeNull();
const serialized = JSON.stringify(captured);
expect(serialized).not.toContain('STARTUP_SKILL_LIST');
expect(serialized).not.toContain('ADDED_MCP_TOOLS');
expect(serialized).not.toContain('PLAN_MODE_REMINDER');
expect(serialized).not.toContain('IDE_CONTEXT');
expect(serialized).toContain('fix session title pollution');
expect(serialized).toContain('continue with recap coverage');
});
});