💄 style: add the history count limit back in agents params settings (#12199)

* fix: add the history count limit back in agents params settings

* fix: fixed the test

* fix: change the default settings snap the enableHistoryCount as false

* fix: change the history process to the first into MessageEngine

* fix: fixed some count limited

* fix: fixed the enableHistoryCount check test

* fix: change the getEnableHistoryCountById logic
This commit is contained in:
LiJian
2026-03-11 15:46:56 +08:00
committed by GitHub
parent 8e60b9f620
commit 1a3c561e21
11 changed files with 544 additions and 45 deletions

View File

@@ -28,7 +28,7 @@ export const DEFAULT_AGENT_CHAT_CONFIG: LobeAgentChatConfig = {
enableAutoCreateTopic: true,
enableCompressHistory: true,
enableContextCompression: true,
enableHistoryCount: true,
enableHistoryCount: false,
enableReasoning: false,
enableStreaming: true,
historyCount: 20,

View File

@@ -9,6 +9,7 @@ import {
GroupMessageFlattenProcessor,
GroupOrchestrationFilterProcessor,
GroupRoleTransformProcessor,
HistoryTruncateProcessor,
InputTemplateProcessor,
MessageCleanupProcessor,
MessageContentProcessor,
@@ -121,6 +122,8 @@ export class MessagesEngine {
provider,
systemRole,
inputTemplate,
enableHistoryCount,
historyCount,
forceFinish,
historySummary,
formatHistorySummary,
@@ -168,6 +171,17 @@ export class MessagesEngine {
const isSystemDateEnabled = enableSystemDate !== false && !hasDateAwareTools;
return [
// =============================================
// Phase 0: History Truncation (FIRST - truncate before any processing)
// =============================================
// 0. History truncate (limit message count based on configuration)
// This MUST be first to ensure subsequent processors only work with truncated messages
new HistoryTruncateProcessor({
enableHistoryCount,
historyCount,
}),
// =============================================
// Phase 1: System Role Injection
// =============================================

View File

@@ -12,8 +12,202 @@ export interface HistoryTruncateConfig {
historyCount?: number;
}
/**
* Helper interface for message with minimal required fields
*/
interface MinimalMessage {
agentId?: string | 'supervisor';
id: string;
metadata?: {
agentCouncil?: boolean;
compare?: boolean;
[key: string]: any;
} | null;
parentId?: string;
role: string;
tool_call_id?: string;
tools?: Array<{ id: string; [key: string]: any }>;
}
/**
* Build helper maps for message relationships
*/
const buildMessageMaps = (messages: MinimalMessage[]) => {
const messageMap = new Map<string, MinimalMessage>();
const childrenMap = new Map<string, string[]>();
// Build messageMap
for (const msg of messages) {
messageMap.set(msg.id, msg);
}
// Build childrenMap
for (const msg of messages) {
if (msg.parentId) {
const parentChildren = childrenMap.get(msg.parentId) || [];
parentChildren.push(msg.id);
childrenMap.set(msg.parentId, parentChildren);
}
}
return { childrenMap, messageMap };
};
/**
* Collect all message IDs in an AssistantGroup chain
* Follows the same logic as MessageCollector.collectAssistantChain
*/
const collectAssistantGroupIds = (
assistantId: string,
messageMap: Map<string, MinimalMessage>,
childrenMap: Map<string, string[]>,
collected: Set<string>,
): void => {
const assistant = messageMap.get(assistantId);
if (!assistant || assistant.role !== 'assistant') return;
// Mark assistant as collected
collected.add(assistantId);
// Get the agentId from the first assistant in chain
const groupAgentId = assistant.agentId;
// Collect tool messages
const tools = assistant.tools || [];
for (const tool of tools) {
const toolId = tool.id;
const toolMsg = messageMap.get(toolId);
if (!toolMsg) continue;
// Mark tool as collected
collected.add(toolId);
// Check if tool has agentCouncil metadata (stop here if true)
if (toolMsg.metadata?.agentCouncil === true) continue;
// Check if tool has multiple task children (stop here if true)
const toolChildren = childrenMap.get(toolId) || [];
const taskChildren = toolChildren.filter((cid) => messageMap.get(cid)?.role === 'task');
if (taskChildren.length > 1) continue;
// Find first child of tool message
const firstChildId = toolChildren[0];
if (!firstChildId) continue;
const nextMsg = messageMap.get(firstChildId);
if (!nextMsg) continue;
// If next message is assistant with same agentId, recurse
if (nextMsg.role === 'assistant' && nextMsg.agentId === groupAgentId) {
collectAssistantGroupIds(firstChildId, messageMap, childrenMap, collected);
}
}
};
/**
* Check if a message is the start of a logical group
*/
const isGroupStart = (
msg: MinimalMessage,
messageMap: Map<string, MinimalMessage>,
childrenMap: Map<string, string[]>,
): boolean => {
// AssistantGroup: assistant with tools
if (msg.role === 'assistant' && msg.tools && msg.tools.length > 0) {
return true;
}
// AgentCouncil: tool message with agentCouncil metadata
if (msg.role === 'tool' && msg.metadata?.agentCouncil === true) {
return true;
}
// Compare: message with compare metadata
if (msg.metadata?.compare === true) {
return true;
}
// Tasks: first task message in a group of multiple tasks with same parentId
if (msg.role === 'task' && msg.parentId) {
const siblings = childrenMap.get(msg.parentId) || [];
const taskSiblings = siblings.filter((sid) => messageMap.get(sid)?.role === 'task');
if (taskSiblings.length > 1 && taskSiblings[0] === msg.id) {
return true;
}
}
return false;
};
/**
* Collect all message IDs in a logical group starting from a group-start message
*/
const collectGroupIds = (
startId: string,
messageMap: Map<string, MinimalMessage>,
childrenMap: Map<string, string[]>,
): Set<string> => {
const collected = new Set<string>();
const startMsg = messageMap.get(startId);
if (!startMsg) return collected;
// AssistantGroup
if (startMsg.role === 'assistant' && startMsg.tools && startMsg.tools.length > 0) {
collectAssistantGroupIds(startId, messageMap, childrenMap, collected);
return collected;
}
// AgentCouncil: collect tool + all its children
if (startMsg.role === 'tool' && startMsg.metadata?.agentCouncil === true) {
collected.add(startId);
const children = childrenMap.get(startId) || [];
for (const childId of children) {
collected.add(childId);
// Also collect their tool messages if they have any
const child = messageMap.get(childId);
if (child?.tools) {
for (const tool of child.tools) {
collected.add(tool.id);
}
}
}
return collected;
}
// Compare: collect message + all children
if (startMsg.metadata?.compare === true) {
collected.add(startId);
const children = childrenMap.get(startId) || [];
for (const childId of children) {
collected.add(childId);
}
return collected;
}
// Tasks: collect all task siblings
if (startMsg.role === 'task' && startMsg.parentId) {
const siblings = childrenMap.get(startMsg.parentId) || [];
const taskSiblings = siblings.filter((sid) => messageMap.get(sid)?.role === 'task');
if (taskSiblings.length > 1) {
// Also include parent tool message
collected.add(startMsg.parentId);
for (const taskId of taskSiblings) {
collected.add(taskId);
}
return collected;
}
}
// Not a group, just the message itself
collected.add(startId);
return collected;
};
/**
* Slice messages based on history count configuration
* Uses group-aware counting: messages grouped together in the frontend
* (AssistantGroup, AgentCouncil, Compare, Tasks) are counted as a single unit
*
* @param messages Original messages array
* @param options Configuration options for slicing
* @returns Sliced messages array
@@ -31,8 +225,68 @@ export const getSlicedMessages = (
// if historyCount is negative or set to 0, return empty array
if (options.historyCount <= 0) return [];
// if historyCount is positive, return last N messages
return messages.slice(-options.historyCount);
// Build message relationship maps
const { messageMap, childrenMap } = buildMessageMaps(messages as MinimalMessage[]);
// Step 1: Identify all groups by walking forward
// Build messageId → groupIndex mapping
const messageToGroup = new Map<string, number>();
const groups: Set<string>[] = [];
const processed = new Set<string>();
let groupIndex = 0;
// Walk forward to identify all groups
for (const msg of messages as MinimalMessage[]) {
if (processed.has(msg.id)) continue;
// Check if this message starts a group
if (isGroupStart(msg, messageMap, childrenMap)) {
const groupIds = collectGroupIds(msg.id, messageMap, childrenMap);
groups.push(groupIds);
// Map each message in the group to this group index
groupIds.forEach((id) => {
messageToGroup.set(id, groupIndex);
processed.add(id);
});
groupIndex++;
} else {
// Single message (not part of a group)
const singleGroup = new Set([msg.id]);
groups.push(singleGroup);
messageToGroup.set(msg.id, groupIndex);
processed.add(msg.id);
groupIndex++;
}
}
// Step 2: Walk backwards through messages to select last N groups
const selectedGroupIndices = new Set<number>();
for (let i = messages.length - 1; i >= 0 && selectedGroupIndices.size < options.historyCount; i--) {
const msg = messages[i] as MinimalMessage;
const groupIdx = messageToGroup.get(msg.id);
if (groupIdx !== undefined) {
selectedGroupIndices.add(groupIdx);
}
}
// Step 3: Collect all message IDs from selected groups
const selectedIds = new Set<string>();
for (const groupIdx of selectedGroupIndices) {
const group = groups[groupIdx];
if (group) {
group.forEach((id) => selectedIds.add(id));
}
}
// Step 4: Filter original messages array to keep only selected messages
// Preserve original order
const result = messages.filter((msg: any) => selectedIds.has(msg.id));
log(
`Group-aware truncation: ${messages.length} messages → ${groups.length} groups → kept ${selectedGroupIndices.size} groups (${result.length} messages)`,
);
return result;
};
/**
@@ -54,7 +308,7 @@ export class HistoryTruncateProcessor extends BaseProcessor {
const originalCount = clonedContext.messages.length;
// Apply history truncation
// Apply group-aware history truncation
clonedContext.messages = getSlicedMessages(clonedContext.messages, {
enableHistoryCount: this.config.enableHistoryCount,
historyCount: this.config.historyCount,
@@ -68,7 +322,7 @@ export class HistoryTruncateProcessor extends BaseProcessor {
clonedContext.metadata.finalMessageCount = finalCount;
log(
`History truncation completed, truncated ${truncatedCount} messages (${originalCount}${finalCount})`,
`History truncation completed (group-aware), truncated ${truncatedCount} messages (${originalCount}${finalCount})`,
);
return this.markAsExecuted(clonedContext);

View File

@@ -80,6 +80,181 @@ describe('HistoryTruncateProcessor', () => {
});
expect(result).toEqual([]);
});
describe('Group-aware truncation', () => {
it('should count AssistantGroup as a single unit', () => {
// Simulate: user -> assistant with tools -> tool -> assistant (same agentId)
const messagesWithGroup = [
{ id: '1', content: 'User message 1', role: 'user' },
{ id: '2', content: 'User message 2', role: 'user' },
{
agentId: 'agent-1',
content: 'First assistant',
id: '3',
parentId: '2',
role: 'assistant',
tools: [{ id: '4' }],
},
{ id: '4', content: 'Tool result', parentId: '3', role: 'tool', tool_call_id: 'call-1' },
{
agentId: 'agent-1',
content: 'Second assistant',
id: '5',
parentId: '4',
role: 'assistant',
},
];
// Groups identified:
// - Group 0: user message 1 (id: 1)
// - Group 1: user message 2 (id: 2)
// - Group 2: AssistantGroup (ids: 3, 4, 5)
// historyCount: 2 should keep last 2 groups: Group 1 and Group 2
const result = getSlicedMessages(messagesWithGroup, {
enableHistoryCount: true,
historyCount: 2,
});
expect(result).toHaveLength(4);
expect(result.map((m: any) => m.id)).toEqual(['2', '3', '4', '5']);
});
it('should stop AssistantGroup chain when agentId changes', () => {
const messagesWithGroup = [
{ id: '1', content: 'User message', role: 'user' },
{
agentId: 'agent-1',
content: 'First assistant',
id: '2',
parentId: '1',
role: 'assistant',
tools: [{ id: '3' }],
},
{ id: '3', content: 'Tool result', parentId: '2', role: 'tool', tool_call_id: 'call-1' },
{
agentId: 'agent-2', // Different agent!
content: 'Second assistant',
id: '4',
parentId: '3',
role: 'assistant',
},
];
// historyCount: 1 should only keep the last "group"
// Since agent-2 is different, it's a separate single message
const result = getSlicedMessages(messagesWithGroup, {
enableHistoryCount: true,
historyCount: 1,
});
// Should keep only the last message (different agentId breaks the group)
expect(result).toHaveLength(1);
expect(result[0].id).toBe('4');
});
it('should count AgentCouncil as a single unit', () => {
const messagesWithCouncil = [
{ id: '1', content: 'User message', role: 'user' },
{
id: '2',
content: 'Tool message',
metadata: { agentCouncil: true },
parentId: '1',
role: 'tool',
},
{
agentId: 'agent-1',
content: 'Agent 1 response',
id: '3',
parentId: '2',
role: 'assistant',
},
{
agentId: 'agent-2',
content: 'Agent 2 response',
id: '4',
parentId: '2',
role: 'assistant',
},
];
// historyCount: 1 should keep the entire council (tool + all children)
const result = getSlicedMessages(messagesWithCouncil, {
enableHistoryCount: true,
historyCount: 1,
});
expect(result).toHaveLength(3);
expect(result.map((m: any) => m.id)).toEqual(['2', '3', '4']);
});
it('should count Tasks group as a single unit', () => {
const messagesWithTasks = [
{ id: '1', content: 'User message', role: 'user' },
{ id: '2', content: 'Tool message', parentId: '1', role: 'tool' },
{ content: 'Task 1', id: '3', parentId: '2', role: 'task' },
{ content: 'Task 2', id: '4', parentId: '2', role: 'task' },
{ content: 'Task 3', id: '5', parentId: '2', role: 'task' },
];
// historyCount: 1 should keep the entire task group (tool + all tasks)
const result = getSlicedMessages(messagesWithTasks, {
enableHistoryCount: true,
historyCount: 1,
});
expect(result).toHaveLength(4);
expect(result.map((m: any) => m.id)).toEqual(['2', '3', '4', '5']);
});
it('should count Compare group as a single unit', () => {
const messagesWithCompare = [
{ id: '1', content: 'User message', role: 'user' },
{ content: 'Compare message', id: '2', metadata: { compare: true }, parentId: '1', role: 'user' },
{ content: 'Column 1', id: '3', parentId: '2', role: 'assistant' },
{ content: 'Column 2', id: '4', parentId: '2', role: 'assistant' },
];
// historyCount: 1 should keep the entire compare group
const result = getSlicedMessages(messagesWithCompare, {
enableHistoryCount: true,
historyCount: 1,
});
expect(result).toHaveLength(3);
expect(result.map((m: any) => m.id)).toEqual(['2', '3', '4']);
});
it('should handle mixed groups correctly', () => {
const mixedMessages = [
{ id: '1', content: 'User 1', role: 'user' },
{ content: 'Assistant 1', id: '2', parentId: '1', role: 'assistant' },
{ id: '3', content: 'User 2', role: 'user' },
{
agentId: 'agent-1',
content: 'Assistant with tools',
id: '4',
parentId: '3',
role: 'assistant',
tools: [{ id: '5' }],
},
{ id: '5', parentId: '4', role: 'tool' },
{ agentId: 'agent-1', content: 'Final assistant', id: '6', parentId: '5', role: 'assistant' },
{ id: '7', content: 'User 3', role: 'user' },
];
// historyCount: 2 should keep:
// - Group 1: AssistantGroup (ids 4, 5, 6)
// - Group 2: User 3 (id 7)
const result = getSlicedMessages(mixedMessages, {
enableHistoryCount: true,
historyCount: 2,
});
expect(result).toHaveLength(4);
expect(result.map((m: any) => m.id)).toEqual(['4', '5', '6', '7']);
});
});
});
describe('HistoryTruncateProcessor', () => {

View File

@@ -16,7 +16,7 @@ import {
TopP,
} from '@/features/ModelParamsControl';
import { useAgentStore } from '@/store/agent';
import { agentByIdSelectors } from '@/store/agent/selectors';
import { agentByIdSelectors, chatConfigByIdSelectors } from '@/store/agent/selectors';
import { useServerConfigStore } from '@/store/serverConfig';
import { useAgentId } from '../../hooks/useAgentId';
@@ -142,7 +142,7 @@ const PARAM_CONFIG = {
}
>;
const Controls = memo<ControlsProps>(({ setUpdating }) => {
const Controls = memo<ControlsProps>(({ setUpdating, updating }) => {
const { t } = useTranslation('setting');
const mobile = useServerConfigStore((s) => s.isMobile);
const agentId = useAgentId();
@@ -152,8 +152,17 @@ const Controls = memo<ControlsProps>(({ setUpdating }) => {
const [form] = Form.useForm();
const enableMaxTokens = AntdForm.useWatch(['chatConfig', 'enableMaxTokens'], form);
const enableHistoryCount = AntdForm.useWatch(['chatConfig', 'enableHistoryCount'], form);
const { frequency_penalty, presence_penalty, temperature, top_p } = config.params ?? {};
const historyCountFromStore = useAgentStore((s) =>
chatConfigByIdSelectors.getHistoryCountById(agentId)(s),
);
// Use raw chatConfig value, not the selector with business logic that may force false
const enableHistoryCountFromStore = useAgentStore(
(s) => chatConfigByIdSelectors.getChatConfigById(agentId)(s).enableHistoryCount,
);
const lastValuesRef = useRef<Record<ParamKey, number | undefined>>({
frequency_penalty,
presence_penalty,
@@ -174,6 +183,20 @@ const Controls = memo<ControlsProps>(({ setUpdating }) => {
}
}, [config, form, frequency_penalty, presence_penalty, temperature, top_p]);
// Sync history count values to form
useEffect(() => {
// Skip syncing when updating to avoid overwriting user's in-progress edits
if (updating) return;
form.setFieldsValue({
chatConfig: {
...form.getFieldValue('chatConfig'),
enableHistoryCount: enableHistoryCountFromStore,
historyCount: historyCountFromStore,
},
});
}, [form, enableHistoryCountFromStore, historyCountFromStore, updating]);
const temperatureValue = AntdForm.useWatch(PARAM_NAME_MAP.temperature, form);
const topPValue = AntdForm.useWatch(PARAM_NAME_MAP.top_p, form);
const presencePenaltyValue = AntdForm.useWatch(PARAM_NAME_MAP.presence_penalty, form);
@@ -336,7 +359,55 @@ const Controls = memo<ControlsProps>(({ setUpdating }) => {
},
];
const allItems = [...baseItems, ...maxTokensItems, ...contextCompressionItems];
// History Count items
const historyCountItems: FormItemProps[] = [
{
children: <Switch />,
label: (
<Flexbox horizontal align={'center'} className={styles.label} gap={8}>
{t('settingChat.enableHistoryCount.title')}
<InfoTooltip title={t('settingChat.historyCount.desc')} />
</Flexbox>
),
name: ['chatConfig', 'enableHistoryCount'],
tag: 'history',
valuePropName: 'checked',
},
...(enableHistoryCount
? [
{
children: (
<SliderWithInput
max={20}
min={0}
step={1}
unlimitedInput={true}
styles={{
input: {
maxWidth: 64,
},
}}
/>
),
label: (
<Flexbox horizontal align={'center'} className={styles.label} gap={8}>
{t('settingChat.historyCount.title')}
<InfoTooltip title={t('settingChat.historyCount.desc')} />
</Flexbox>
),
name: ['chatConfig', 'historyCount'],
tag: 'history',
} satisfies FormItemProps,
]
: []),
];
const allItems = [
...baseItems,
...maxTokensItems,
...contextCompressionItems,
...historyCountItems,
];
return (
<Form

View File

@@ -24,7 +24,6 @@ import {
agentByIdSelectors,
agentChatConfigSelectors,
agentSelectors,
chatConfigByIdSelectors,
} from '@/store/agent/selectors';
import { aiProviderSelectors, getAiInfraStoreState } from '@/store/aiInfra';
import { getChatStoreState } from '@/store/chat';
@@ -241,12 +240,12 @@ class ChatService {
const modelMessages = await contextEngineering({
agentBuilderContext,
agentId: targetAgentId,
enableHistoryCount:
chatConfigByIdSelectors.getEnableHistoryCountById(targetAgentId)(getAgentStoreState()),
// Use raw chatConfig values, not selectors with business logic that may force false
enableHistoryCount: chatConfig.enableHistoryCount,
enableUserMemories,
groupId,
historyCount:
chatConfigByIdSelectors.getHistoryCountById(targetAgentId)(getAgentStoreState()) + 2,
// historyCount + 2 accounts for: 1 user message + 1 assistant response being added
historyCount: (chatConfig.historyCount ?? 20) + 2,
// Page editor context from agent runtime
initialContext: options?.initialContext,
inputTemplate: chatConfig.inputTemplate,

View File

@@ -59,7 +59,7 @@ describe('chatConfigByIdSelectors', () => {
});
describe('getEnableHistoryCountById', () => {
it('should return false when context caching enabled and model supports it', () => {
it('should return enableHistoryCount value even when context caching is enabled', () => {
const state = createState({
agentMap: {
'agent-1': {
@@ -69,10 +69,10 @@ describe('chatConfigByIdSelectors', () => {
},
});
expect(chatConfigByIdSelectors.getEnableHistoryCountById('agent-1')(state)).toBe(false);
expect(chatConfigByIdSelectors.getEnableHistoryCountById('agent-1')(state)).toBe(true);
});
it('should return false when search enabled and model is claude-3-7-sonnet', () => {
it('should return enableHistoryCount value even when search is enabled', () => {
const state = createState({
agentMap: {
'agent-1': {
@@ -86,10 +86,10 @@ describe('chatConfigByIdSelectors', () => {
},
});
expect(chatConfigByIdSelectors.getEnableHistoryCountById('agent-1')(state)).toBe(false);
expect(chatConfigByIdSelectors.getEnableHistoryCountById('agent-1')(state)).toBe(true);
});
it('should return enableHistoryCount value when no special cases apply', () => {
it('should return enableHistoryCount value directly from config', () => {
const state = createState({
agentMap: {
'agent-1': {
@@ -114,7 +114,7 @@ describe('chatConfigByIdSelectors', () => {
model: 'gpt-4',
},
'agent-2': {
chatConfig: { disableContextCaching: false, enableHistoryCount: true },
chatConfig: { disableContextCaching: false, enableHistoryCount: false },
model: 'claude-3-5-sonnet',
},
},

View File

@@ -1,5 +1,4 @@
import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_SEARCH_FC_MODEL } from '@lobechat/const';
import { isContextCachingModel, isThinkingWithToolClaudeModel } from '@lobechat/model-runtime';
import { type LobeAgentChatConfig } from '@lobechat/types';
import { type AgentStoreState } from '@/store/agent/initialState';
@@ -16,23 +15,9 @@ const getChatConfigById =
(s: AgentStoreState): LobeAgentChatConfig =>
agentSelectors.getAgentConfigById(agentId)(s)?.chatConfig || {};
const getEnableHistoryCountById = (agentId: string) => (s: AgentStoreState) => {
const config = agentSelectors.getAgentConfigById(agentId)(s);
const chatConfig = getChatConfigById(agentId)(s);
// If context caching is enabled and the current model type matches, do not enable history count
const enableContextCaching = !chatConfig.disableContextCaching;
if (enableContextCaching && config?.model && isContextCachingModel(config.model)) return false;
// When search is enabled, do not enable history count for the claude 3.7 sonnet model
const searchMode = chatConfig.searchMode || 'auto';
const enableSearch = searchMode !== 'off';
if (enableSearch && config?.model && isThinkingWithToolClaudeModel(config.model)) return false;
return chatConfig.enableHistoryCount;
};
// Return raw chatConfig value without business logic overrides
const getEnableHistoryCountById = (agentId: string) => (s: AgentStoreState) =>
getChatConfigById(agentId)(s).enableHistoryCount;
const getHistoryCountById =
(agentId: string) =>

View File

@@ -143,7 +143,7 @@ describe('agentChatConfigSelectors', () => {
});
describe('enableHistoryCount', () => {
it('should return false when context caching enabled and model supports it', () => {
it('should return enableHistoryCount value even when context caching is enabled', () => {
const state = createState({
activeAgentId: 'agent-1',
agentMap: {
@@ -154,10 +154,10 @@ describe('agentChatConfigSelectors', () => {
},
});
expect(agentChatConfigSelectors.enableHistoryCount(state)).toBe(false);
expect(agentChatConfigSelectors.enableHistoryCount(state)).toBe(true);
});
it('should return false when search enabled and model is claude-3-7-sonnet', () => {
it('should return enableHistoryCount value even when search is enabled', () => {
const state = createState({
activeAgentId: 'agent-1',
agentMap: {
@@ -172,10 +172,10 @@ describe('agentChatConfigSelectors', () => {
},
});
expect(agentChatConfigSelectors.enableHistoryCount(state)).toBe(false);
expect(agentChatConfigSelectors.enableHistoryCount(state)).toBe(true);
});
it('should return enableHistoryCount value when no special cases apply', () => {
it('should return enableHistoryCount value directly from config', () => {
const state = createState({
activeAgentId: 'agent-1',
agentMap: {

View File

@@ -21,8 +21,9 @@ const useModelBuiltinSearch = (s: AgentStoreState) =>
const searchFCModel = (s: AgentStoreState) =>
chatConfigByIdSelectors.getSearchFCModelById(s.activeAgentId || '')(s);
// Use raw chatConfig value, not the selector with business logic that may force false
const enableHistoryCount = (s: AgentStoreState) =>
chatConfigByIdSelectors.getEnableHistoryCountById(s.activeAgentId || '')(s);
chatConfigByIdSelectors.getChatConfigById(s.activeAgentId || '')(s).enableHistoryCount;
const historyCount = (s: AgentStoreState): number =>
chatConfigByIdSelectors.getHistoryCountById(s.activeAgentId || '')(s);

View File

@@ -103,7 +103,7 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
"enableAutoCreateTopic": true,
"enableCompressHistory": true,
"enableContextCompression": true,
"enableHistoryCount": true,
"enableHistoryCount": false,
"enableReasoning": false,
"enableStreaming": true,
"historyCount": 20,
@@ -148,7 +148,7 @@ exports[`settingsSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CON
"enableAutoCreateTopic": true,
"enableCompressHistory": true,
"enableContextCompression": true,
"enableHistoryCount": true,
"enableHistoryCount": false,
"enableReasoning": false,
"enableStreaming": true,
"historyCount": 20,