mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-21 06:35:07 +00:00
* feat(core): add user prompt expansion hooks * fix(cli): handle missing prompt expansion hooks * fix(cli): preserve prompt expansion hook context * fix(cli): gate user prompt expansion hooks * fix(cli): bound prompt expansion context * fix(cli): surface blocked prompt expansion to skills * fix(core): fail closed on prompt expansion hook errors * fix(core): fail open on prompt expansion setup errors * fix(cli): block model-invocable prompt expansion * fix(cli): surface prompt expansion block reason * fix(core): fail closed prompt expansion setup errors * fix(cli): keep blocked prompt expansion out of skill output * fix(cli): surface blocked prompt expansion reason * fix(core): sanitize prompt expansion context chaining * fix(core): preserve prompt submit hook context * fix(cli): address user prompt expansion review * fix(core): harden prompt expansion hook output * test(cli): cover prompt expansion ampersand escaping * fix(core): preserve prompt submit hook context * test(cli): cover MCP prompt expansion hook * fix(core): route prompt submit hook output through wrapper * fix(core): return model command hook blocks as errors * fix(core): sanitize user prompt hook context * fix(core): harden prompt expansion context sanitizing * fix(core): forward skill command args * refactor(core): remove redundant prompt submit hook output * fix(core): fail open prompt expansion hook setup * fix(core): include skill args in approval checks * fix(cli): stop aborted prompt expansion hooks * fix(core): sanitize skill arg descriptions * fix(cli): report cancelled skill expansion accurately
116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
appendUserPromptExpansionAdditionalContext,
|
|
formatUserPromptExpansionBlockedMessage,
|
|
serializeUserPromptExpansionPrompt,
|
|
} from './userPromptExpansionHook.js';
|
|
|
|
describe('appendUserPromptExpansionAdditionalContext', () => {
|
|
it('returns content unchanged when additionalContext is undefined', () => {
|
|
expect(
|
|
appendUserPromptExpansionAdditionalContext('base prompt', undefined),
|
|
).toBe('base prompt');
|
|
});
|
|
|
|
it('appends additional context to string prompts', () => {
|
|
const result = appendUserPromptExpansionAdditionalContext(
|
|
'base prompt',
|
|
'hook context',
|
|
);
|
|
|
|
expect(result).toBe('base prompt\n\nhook context');
|
|
});
|
|
|
|
it('appends additional context to part arrays', () => {
|
|
const result = appendUserPromptExpansionAdditionalContext(
|
|
[{ text: 'base prompt' }],
|
|
'hook context',
|
|
);
|
|
|
|
expect(result).toEqual([
|
|
{ text: 'base prompt' },
|
|
{ text: '\n\nhook context' },
|
|
]);
|
|
});
|
|
|
|
it('appends additional context to a single part', () => {
|
|
const result = appendUserPromptExpansionAdditionalContext(
|
|
{ text: 'base prompt' },
|
|
'hook context',
|
|
);
|
|
|
|
expect(result).toEqual([
|
|
{ text: 'base prompt' },
|
|
{ text: '\n\nhook context' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('formatUserPromptExpansionBlockedMessage', () => {
|
|
it('escapes ampersands before angle brackets', () => {
|
|
const result = formatUserPromptExpansionBlockedMessage('a&b<c>');
|
|
|
|
expect(result).toBe('UserPromptExpansion blocked: a&b<c>');
|
|
});
|
|
|
|
it('sanitizes and truncates block reasons', () => {
|
|
const longReason = `<policy>${'x'.repeat(10_000)}</policy>`;
|
|
|
|
const result = formatUserPromptExpansionBlockedMessage(longReason);
|
|
|
|
expect(result).toBe(
|
|
`UserPromptExpansion blocked: <policy>${'x'.repeat(9_986)}`,
|
|
);
|
|
expect(result.length).toBe('UserPromptExpansion blocked: '.length + 10_000);
|
|
});
|
|
|
|
it('does not leave a partial entity after truncation', () => {
|
|
const result = formatUserPromptExpansionBlockedMessage(
|
|
'x'.repeat(9_999) + '<',
|
|
);
|
|
|
|
expect(result).toBe(`UserPromptExpansion blocked: ${'x'.repeat(9_999)}`);
|
|
});
|
|
|
|
it('does not leave a partial ampersand entity after truncation', () => {
|
|
const result = formatUserPromptExpansionBlockedMessage(
|
|
'x'.repeat(9_998) + '&',
|
|
);
|
|
|
|
expect(result).toBe(`UserPromptExpansion blocked: ${'x'.repeat(9_998)}`);
|
|
});
|
|
});
|
|
|
|
describe('serializeUserPromptExpansionPrompt', () => {
|
|
it('returns string prompts unchanged', () => {
|
|
expect(serializeUserPromptExpansionPrompt('plain prompt')).toBe(
|
|
'plain prompt',
|
|
);
|
|
});
|
|
|
|
it('serializes part arrays with verbose formatting', () => {
|
|
expect(
|
|
serializeUserPromptExpansionPrompt([
|
|
{ text: 'first' },
|
|
{ inlineData: { mimeType: 'text/plain', data: 'ZGF0YQ==' } },
|
|
{ text: 'last' },
|
|
]),
|
|
).toBe('first<text/plain>last');
|
|
});
|
|
|
|
it('serializes a single part object', () => {
|
|
expect(serializeUserPromptExpansionPrompt({ text: 'single part' })).toBe(
|
|
'single part',
|
|
);
|
|
});
|
|
|
|
it('serializes empty part arrays to an empty string', () => {
|
|
expect(serializeUserPromptExpansionPrompt([])).toBe('');
|
|
});
|
|
});
|