@lobehub/lobehub 2.0.0-next.354 → 2.0.0-next.355
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/changelog/v1.json +9 -0
- package/locales/en-US/plugin.json +3 -0
- package/locales/zh-CN/plugin.json +3 -0
- package/package.json +1 -1
- package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +3 -11
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +0 -13
- package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +0 -25
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +3 -3
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +4 -0
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/InboxItem.tsx +19 -29
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/List.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +1 -1
- package/src/locales/default/plugin.ts +3 -0
- package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +0 -25
- package/src/services/chat/chat.test.ts +19 -19
- package/src/services/chat/index.ts +8 -3
- package/src/services/chat/mecha/agentConfigResolver.test.ts +72 -55
- package/src/services/chat/mecha/agentConfigResolver.ts +28 -4
- package/src/services/chat/mecha/contextEngineering.test.ts +21 -14
- package/src/services/chat/mecha/contextEngineering.ts +12 -0
- package/src/services/chat/types.ts +7 -1
- package/src/store/chat/agents/createAgentExecutors.ts +15 -4
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +1 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.355](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.354...v2.0.0-next.355)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-23**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **home**: Use correct CreateGroupModal for session group creation.
|
|
12
|
+
- **misc**: Fix favorite refresh bug and group topic refresh issue.
|
|
13
|
+
|
|
14
|
+
<br/>
|
|
15
|
+
|
|
16
|
+
<details>
|
|
17
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
18
|
+
|
|
19
|
+
#### What's fixed
|
|
20
|
+
|
|
21
|
+
- **home**: Use correct CreateGroupModal for session group creation, closes [#11752](https://github.com/lobehub/lobe-chat/issues/11752) ([36bcc50](https://github.com/lobehub/lobe-chat/commit/36bcc50))
|
|
22
|
+
- **misc**: Fix favorite refresh bug and group topic refresh issue, closes [#11745](https://github.com/lobehub/lobe-chat/issues/11745) ([5d115ef](https://github.com/lobehub/lobe-chat/commit/5d115ef))
|
|
23
|
+
|
|
24
|
+
</details>
|
|
25
|
+
|
|
26
|
+
<div align="right">
|
|
27
|
+
|
|
28
|
+
[](#readme-top)
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
5
32
|
## [Version 2.0.0-next.354](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.353...v2.0.0-next.354)
|
|
6
33
|
|
|
7
34
|
<sup>Released on **2026-01-23**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -177,6 +177,9 @@
|
|
|
177
177
|
"builtins.lobe-user-memory.apiName.searchUserMemory": "Search memory",
|
|
178
178
|
"builtins.lobe-user-memory.apiName.updateIdentityMemory": "Update identity memory",
|
|
179
179
|
"builtins.lobe-user-memory.inspector.noResults": "No results",
|
|
180
|
+
"builtins.lobe-user-memory.render.contexts": "Contexts",
|
|
181
|
+
"builtins.lobe-user-memory.render.experiences": "Experiences",
|
|
182
|
+
"builtins.lobe-user-memory.render.preferences": "Preferences",
|
|
180
183
|
"builtins.lobe-user-memory.title": "Memory",
|
|
181
184
|
"builtins.lobe-web-browsing.apiName.crawlMultiPages": "Read multiple pages",
|
|
182
185
|
"builtins.lobe-web-browsing.apiName.crawlSinglePage": "Read page content",
|
|
@@ -177,6 +177,9 @@
|
|
|
177
177
|
"builtins.lobe-user-memory.apiName.searchUserMemory": "搜索记忆",
|
|
178
178
|
"builtins.lobe-user-memory.apiName.updateIdentityMemory": "更新身份记忆",
|
|
179
179
|
"builtins.lobe-user-memory.inspector.noResults": "无结果",
|
|
180
|
+
"builtins.lobe-user-memory.render.contexts": "情境",
|
|
181
|
+
"builtins.lobe-user-memory.render.experiences": "经验",
|
|
182
|
+
"builtins.lobe-user-memory.render.preferences": "偏好",
|
|
180
183
|
"builtins.lobe-user-memory.title": "记忆",
|
|
181
184
|
"builtins.lobe-web-browsing.apiName.crawlMultiPages": "读取多个页面内容",
|
|
182
185
|
"builtins.lobe-web-browsing.apiName.crawlSinglePage": "读取页面内容",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.355",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -6,8 +6,6 @@ import { createStaticStyles } from 'antd-style';
|
|
|
6
6
|
import { memo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
|
-
import { highlightTextStyles } from '@/styles';
|
|
10
|
-
|
|
11
9
|
import type { SearchMemoryParams, SearchUserMemoryState } from '../../../types';
|
|
12
10
|
|
|
13
11
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
@@ -120,9 +118,7 @@ const SearchUserMemoryRender = memo<BuiltinRenderProps<SearchMemoryParams, Searc
|
|
|
120
118
|
paddingInline={12}
|
|
121
119
|
title={
|
|
122
120
|
<Text className={styles.sectionHeader}>
|
|
123
|
-
<span
|
|
124
|
-
{t('builtins.lobe-user-memory.render.contexts' as any)}
|
|
125
|
-
</span>
|
|
121
|
+
<span>{t('builtins.lobe-user-memory.render.contexts')}</span>
|
|
126
122
|
<Text as={'span'} type={'secondary'}>
|
|
127
123
|
{' '}
|
|
128
124
|
({contexts.length})
|
|
@@ -152,9 +148,7 @@ const SearchUserMemoryRender = memo<BuiltinRenderProps<SearchMemoryParams, Searc
|
|
|
152
148
|
paddingInline={12}
|
|
153
149
|
title={
|
|
154
150
|
<Text className={styles.sectionHeader}>
|
|
155
|
-
<span
|
|
156
|
-
{t('builtins.lobe-user-memory.render.experiences' as any)}
|
|
157
|
-
</span>
|
|
151
|
+
<span>{t('builtins.lobe-user-memory.render.experiences')}</span>
|
|
158
152
|
<Text as={'span'} type={'secondary'}>
|
|
159
153
|
{' '}
|
|
160
154
|
({experiences.length})
|
|
@@ -184,9 +178,7 @@ const SearchUserMemoryRender = memo<BuiltinRenderProps<SearchMemoryParams, Searc
|
|
|
184
178
|
paddingInline={12}
|
|
185
179
|
title={
|
|
186
180
|
<Text className={styles.sectionHeader}>
|
|
187
|
-
<span
|
|
188
|
-
{t('builtins.lobe-user-memory.render.preferences' as any)}
|
|
189
|
-
</span>
|
|
181
|
+
<span>{t('builtins.lobe-user-memory.render.preferences')}</span>
|
|
190
182
|
<Text as={'span'} type={'secondary'}>
|
|
191
183
|
{' '}
|
|
192
184
|
({preferences.length})
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
GroupMessageFlattenProcessor,
|
|
10
10
|
GroupOrchestrationFilterProcessor,
|
|
11
11
|
GroupRoleTransformProcessor,
|
|
12
|
-
HistoryTruncateProcessor,
|
|
13
12
|
InputTemplateProcessor,
|
|
14
13
|
MessageCleanupProcessor,
|
|
15
14
|
MessageContentProcessor,
|
|
@@ -113,8 +112,6 @@ export class MessagesEngine {
|
|
|
113
112
|
provider,
|
|
114
113
|
systemRole,
|
|
115
114
|
inputTemplate,
|
|
116
|
-
enableHistoryCount,
|
|
117
|
-
historyCount,
|
|
118
115
|
historySummary,
|
|
119
116
|
formatHistorySummary,
|
|
120
117
|
knowledge,
|
|
@@ -145,16 +142,6 @@ export class MessagesEngine {
|
|
|
145
142
|
const isGTDTodoEnabled = gtd?.enabled && gtd?.todos;
|
|
146
143
|
|
|
147
144
|
return [
|
|
148
|
-
// =============================================
|
|
149
|
-
// Phase 1: History Management
|
|
150
|
-
// =============================================
|
|
151
|
-
|
|
152
|
-
// 1. History truncation (MUST be first, before any message injection)
|
|
153
|
-
new HistoryTruncateProcessor({
|
|
154
|
-
enableHistoryCount,
|
|
155
|
-
historyCount,
|
|
156
|
-
}),
|
|
157
|
-
|
|
158
145
|
// =============================================
|
|
159
146
|
// Phase 2: System Role Injection
|
|
160
147
|
// =============================================
|
|
@@ -113,31 +113,6 @@ describe('MessagesEngine', () => {
|
|
|
113
113
|
expect(result.messages[0].content).toBe(systemRole);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
it('should truncate history when enabled', async () => {
|
|
117
|
-
const messages: UIChatMessage[] = [];
|
|
118
|
-
for (let i = 0; i < 20; i++) {
|
|
119
|
-
messages.push({
|
|
120
|
-
content: `Message ${i}`,
|
|
121
|
-
createdAt: Date.now(),
|
|
122
|
-
id: `msg-${i}`,
|
|
123
|
-
role: i % 2 === 0 ? 'user' : 'assistant',
|
|
124
|
-
updatedAt: Date.now(),
|
|
125
|
-
} as UIChatMessage);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const params = createBasicParams({
|
|
129
|
-
enableHistoryCount: true,
|
|
130
|
-
historyCount: 5,
|
|
131
|
-
messages,
|
|
132
|
-
});
|
|
133
|
-
const engine = new MessagesEngine(params);
|
|
134
|
-
|
|
135
|
-
const result = await engine.process();
|
|
136
|
-
|
|
137
|
-
// Should have truncated to 5 messages
|
|
138
|
-
expect(result.messages.length).toBeLessThanOrEqual(5);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
116
|
it('should inject history summary when provided', async () => {
|
|
142
117
|
const historySummary = 'We discussed AI and machine learning';
|
|
143
118
|
const params = createBasicParams({ historySummary });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { eq, inArray } from 'drizzle-orm';
|
|
1
|
+
import { asc, eq, inArray } from 'drizzle-orm';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import { getTestDB } from '../../../core/getTestDB';
|
|
@@ -188,12 +188,12 @@ describe('TopicModel - Create', () => {
|
|
|
188
188
|
userId,
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
const items = await serverDB.select().from(topics);
|
|
191
|
+
const items = await serverDB.select().from(topics).orderBy(asc(topics.title));
|
|
192
192
|
expect(items).toHaveLength(2);
|
|
193
193
|
expect(items[0]).toMatchObject({ title: 'Topic 1', favorite: true, sessionId, userId });
|
|
194
194
|
expect(items[1]).toMatchObject({ title: 'Topic 2', favorite: false, sessionId, userId });
|
|
195
195
|
|
|
196
|
-
const updatedMessages = await serverDB.select().from(messages);
|
|
196
|
+
const updatedMessages = await serverDB.select().from(messages).orderBy(asc(messages.id));
|
|
197
197
|
expect(updatedMessages).toHaveLength(3);
|
|
198
198
|
expect(updatedMessages[0].topicId).toBe(createdTopics[0].id);
|
|
199
199
|
expect(updatedMessages[1].topicId).toBe(createdTopics[0].id);
|
|
@@ -110,6 +110,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
|
|
|
110
110
|
fill={fav ? cssVar.colorWarning : 'transparent'}
|
|
111
111
|
icon={Star}
|
|
112
112
|
onClick={(e) => {
|
|
113
|
+
e.preventDefault();
|
|
113
114
|
e.stopPropagation();
|
|
114
115
|
favoriteTopic(id, !fav);
|
|
115
116
|
}}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Flexbox } from '@lobehub/ui';
|
|
4
|
+
import debug from 'debug';
|
|
4
5
|
import { Suspense, memo, useMemo } from 'react';
|
|
5
6
|
|
|
6
7
|
import ChatMiniMap from '@/features/ChatMiniMap';
|
|
@@ -18,6 +19,8 @@ import ThreadHydration from './ThreadHydration';
|
|
|
18
19
|
import { useActionsBarConfig } from './useActionsBarConfig';
|
|
19
20
|
import { useAgentContext } from './useAgentContext';
|
|
20
21
|
|
|
22
|
+
const log = debug('lobe-render:agent:ConversationArea');
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* ConversationArea
|
|
23
26
|
*
|
|
@@ -35,6 +38,7 @@ const Conversation = memo(() => {
|
|
|
35
38
|
);
|
|
36
39
|
const replaceMessages = useChatStore((s) => s.replaceMessages);
|
|
37
40
|
const messages = useChatStore((s) => s.dbMessagesMap[chatKey]);
|
|
41
|
+
log('contextKey %s: %o', chatKey, messages);
|
|
38
42
|
|
|
39
43
|
// Get operation state from ChatStore for reactive updates
|
|
40
44
|
const operationState = useOperationState(context);
|
|
@@ -111,6 +111,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
|
|
|
111
111
|
fill={fav ? cssVar.colorWarning : 'transparent'}
|
|
112
112
|
icon={Star}
|
|
113
113
|
onClick={(e) => {
|
|
114
|
+
e.preventDefault();
|
|
114
115
|
e.stopPropagation();
|
|
115
116
|
favoriteTopic(id, !fav);
|
|
116
117
|
}}
|
|
@@ -106,7 +106,7 @@ const AgentItem = memo<AgentItemProps>(({ item, style, className }) => {
|
|
|
106
106
|
|
|
107
107
|
return (
|
|
108
108
|
<>
|
|
109
|
-
<Link aria-label={
|
|
109
|
+
<Link aria-label={displayTitle} to={agentUrl}>
|
|
110
110
|
<NavItem
|
|
111
111
|
actions={<Actions dropdownMenu={dropdownMenu} />}
|
|
112
112
|
className={className}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_INBOX_AVATAR, SESSION_CHAT_URL } from '@lobechat/const';
|
|
4
4
|
import { Avatar } from '@lobehub/ui';
|
|
5
|
-
import { type CSSProperties, memo
|
|
6
|
-
import {
|
|
5
|
+
import { type CSSProperties, memo } from 'react';
|
|
6
|
+
import { Link } from 'react-router-dom';
|
|
7
7
|
|
|
8
8
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
9
9
|
import { useAgentStore } from '@/store/agent';
|
|
@@ -17,38 +17,28 @@ interface InboxItemProps {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const InboxItem = memo<InboxItemProps>(({ className, style }) => {
|
|
20
|
-
const navigate = useNavigate();
|
|
21
|
-
|
|
22
20
|
const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
|
|
23
|
-
const activeAgentId = useAgentStore((s) => s.activeAgentId);
|
|
24
|
-
const isActive = !!inboxAgentId && activeAgentId === inboxAgentId;
|
|
25
21
|
|
|
26
|
-
const isLoading = useChatStore(
|
|
27
|
-
useCallback(
|
|
28
|
-
(s) => (isActive ? operationSelectors.isAgentRuntimeRunning(s) : false),
|
|
29
|
-
[isActive],
|
|
30
|
-
),
|
|
31
|
-
);
|
|
22
|
+
const isLoading = useChatStore(operationSelectors.isAgentRuntimeRunning);
|
|
32
23
|
const inboxAgentTitle = 'Lobe AI';
|
|
33
24
|
|
|
34
|
-
const handleClick = useCallback(() => {
|
|
35
|
-
if (inboxAgentId) {
|
|
36
|
-
navigate(SESSION_CHAT_URL(inboxAgentId, false));
|
|
37
|
-
}
|
|
38
|
-
}, [inboxAgentId, navigate]);
|
|
39
|
-
|
|
40
25
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
26
|
+
<Link aria-label={inboxAgentTitle} to={SESSION_CHAT_URL(inboxAgentId, false)}>
|
|
27
|
+
<NavItem
|
|
28
|
+
className={className}
|
|
29
|
+
icon={
|
|
30
|
+
<Avatar
|
|
31
|
+
avatar={DEFAULT_INBOX_AVATAR}
|
|
32
|
+
emojiScaleWithBackground
|
|
33
|
+
shape={'square'}
|
|
34
|
+
size={24}
|
|
35
|
+
/>
|
|
36
|
+
}
|
|
37
|
+
loading={isLoading}
|
|
38
|
+
style={style}
|
|
39
|
+
title={inboxAgentTitle}
|
|
40
|
+
/>
|
|
41
|
+
</Link>
|
|
52
42
|
);
|
|
53
43
|
});
|
|
54
44
|
|
|
@@ -4,6 +4,7 @@ import { MoreHorizontal } from 'lucide-react';
|
|
|
4
4
|
import { type CSSProperties, memo, useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
|
+
import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
|
|
7
8
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
8
9
|
import { useGlobalStore } from '@/store/global';
|
|
9
10
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
@@ -11,7 +12,6 @@ import { useHomeStore } from '@/store/home';
|
|
|
11
12
|
import { homeAgentListSelectors } from '@/store/home/selectors';
|
|
12
13
|
import { SessionDefaultGroup } from '@/types/session';
|
|
13
14
|
|
|
14
|
-
import EmptyNavItem from '../../../../../../../../features/NavPanel/components/EmptyNavItem';
|
|
15
15
|
import { useCreateMenuItems } from '../../../hooks';
|
|
16
16
|
import GroupItem from './AgentGroupItem';
|
|
17
17
|
import AgentItem from './AgentItem';
|
|
@@ -5,8 +5,8 @@ import { type ReactNode, createContext, memo, useContext, useMemo, useState } fr
|
|
|
5
5
|
import { ChatGroupWizard } from '@/components/ChatGroupWizard';
|
|
6
6
|
import { MemberSelectionModal } from '@/components/MemberSelectionModal';
|
|
7
7
|
|
|
8
|
-
import CreateGroupModal from '../../CreateGroupModal';
|
|
9
8
|
import ConfigGroupModal from './Modals/ConfigGroupModal';
|
|
9
|
+
import CreateGroupModal from './Modals/CreateGroupModal';
|
|
10
10
|
|
|
11
11
|
interface AgentModalContextValue {
|
|
12
12
|
closeAllModals: () => void;
|
|
@@ -178,6 +178,9 @@ export default {
|
|
|
178
178
|
'builtins.lobe-user-memory.apiName.searchUserMemory': 'Search memory',
|
|
179
179
|
'builtins.lobe-user-memory.apiName.updateIdentityMemory': 'Update identity memory',
|
|
180
180
|
'builtins.lobe-user-memory.inspector.noResults': 'No results',
|
|
181
|
+
'builtins.lobe-user-memory.render.contexts': 'Contexts',
|
|
182
|
+
'builtins.lobe-user-memory.render.experiences': 'Experiences',
|
|
183
|
+
'builtins.lobe-user-memory.render.preferences': 'Preferences',
|
|
181
184
|
'builtins.lobe-user-memory.title': 'Memory',
|
|
182
185
|
'builtins.lobe-web-browsing.apiName.crawlMultiPages': 'Read multiple pages',
|
|
183
186
|
'builtins.lobe-web-browsing.apiName.crawlSinglePage': 'Read page content',
|
|
@@ -68,31 +68,6 @@ describe('serverMessagesEngine', () => {
|
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
describe('history truncation', () => {
|
|
72
|
-
it('should truncate history when enabled', async () => {
|
|
73
|
-
const messages: UIChatMessage[] = [];
|
|
74
|
-
for (let i = 0; i < 20; i++) {
|
|
75
|
-
messages.push({
|
|
76
|
-
content: `Message ${i}`,
|
|
77
|
-
createdAt: Date.now(),
|
|
78
|
-
id: `msg-${i}`,
|
|
79
|
-
role: i % 2 === 0 ? 'user' : 'assistant',
|
|
80
|
-
updatedAt: Date.now(),
|
|
81
|
-
} as UIChatMessage);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const result = await serverMessagesEngine({
|
|
85
|
-
enableHistoryCount: true,
|
|
86
|
-
historyCount: 5,
|
|
87
|
-
messages,
|
|
88
|
-
model: 'gpt-4',
|
|
89
|
-
provider: 'openai',
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
expect(result.length).toBeLessThanOrEqual(5);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
71
|
describe('knowledge injection', () => {
|
|
97
72
|
it('should inject file contents', async () => {
|
|
98
73
|
const messages = createBasicMessages();
|
|
@@ -155,7 +155,7 @@ describe('ChatService', () => {
|
|
|
155
155
|
]),
|
|
156
156
|
messages: expect.anything(),
|
|
157
157
|
}),
|
|
158
|
-
|
|
158
|
+
expect.anything(),
|
|
159
159
|
);
|
|
160
160
|
});
|
|
161
161
|
|
|
@@ -185,7 +185,7 @@ describe('ChatService', () => {
|
|
|
185
185
|
type: 'enabled',
|
|
186
186
|
},
|
|
187
187
|
}),
|
|
188
|
-
|
|
188
|
+
expect.anything(),
|
|
189
189
|
);
|
|
190
190
|
});
|
|
191
191
|
|
|
@@ -214,7 +214,7 @@ describe('ChatService', () => {
|
|
|
214
214
|
type: 'disabled',
|
|
215
215
|
},
|
|
216
216
|
}),
|
|
217
|
-
|
|
217
|
+
expect.anything(),
|
|
218
218
|
);
|
|
219
219
|
});
|
|
220
220
|
|
|
@@ -244,7 +244,7 @@ describe('ChatService', () => {
|
|
|
244
244
|
type: 'enabled',
|
|
245
245
|
},
|
|
246
246
|
}),
|
|
247
|
-
|
|
247
|
+
expect.anything(),
|
|
248
248
|
);
|
|
249
249
|
});
|
|
250
250
|
|
|
@@ -270,7 +270,7 @@ describe('ChatService', () => {
|
|
|
270
270
|
expect.objectContaining({
|
|
271
271
|
reasoning_effort: 'high',
|
|
272
272
|
}),
|
|
273
|
-
|
|
273
|
+
expect.anything(),
|
|
274
274
|
);
|
|
275
275
|
});
|
|
276
276
|
|
|
@@ -296,7 +296,7 @@ describe('ChatService', () => {
|
|
|
296
296
|
expect.objectContaining({
|
|
297
297
|
thinkingBudget: 5000,
|
|
298
298
|
}),
|
|
299
|
-
|
|
299
|
+
expect.anything(),
|
|
300
300
|
);
|
|
301
301
|
});
|
|
302
302
|
});
|
|
@@ -370,7 +370,7 @@ describe('ChatService', () => {
|
|
|
370
370
|
enabledSearch: undefined,
|
|
371
371
|
tools: undefined,
|
|
372
372
|
},
|
|
373
|
-
|
|
373
|
+
expect.anything(),
|
|
374
374
|
);
|
|
375
375
|
});
|
|
376
376
|
|
|
@@ -396,7 +396,7 @@ describe('ChatService', () => {
|
|
|
396
396
|
stream: true,
|
|
397
397
|
tools: undefined,
|
|
398
398
|
},
|
|
399
|
-
|
|
399
|
+
expect.anything(),
|
|
400
400
|
);
|
|
401
401
|
});
|
|
402
402
|
});
|
|
@@ -494,7 +494,7 @@ describe('ChatService', () => {
|
|
|
494
494
|
enabledSearch: undefined,
|
|
495
495
|
tools: undefined,
|
|
496
496
|
},
|
|
497
|
-
|
|
497
|
+
expect.anything(),
|
|
498
498
|
);
|
|
499
499
|
});
|
|
500
500
|
|
|
@@ -583,7 +583,7 @@ describe('ChatService', () => {
|
|
|
583
583
|
enabledSearch: undefined,
|
|
584
584
|
tools: undefined,
|
|
585
585
|
},
|
|
586
|
-
|
|
586
|
+
expect.anything(),
|
|
587
587
|
);
|
|
588
588
|
});
|
|
589
589
|
|
|
@@ -781,7 +781,7 @@ describe('ChatService', () => {
|
|
|
781
781
|
{ content: 'https://vercel.com/ 请分析 chatGPT 关键词\n\n', role: 'user' },
|
|
782
782
|
],
|
|
783
783
|
},
|
|
784
|
-
|
|
784
|
+
expect.anything(),
|
|
785
785
|
);
|
|
786
786
|
});
|
|
787
787
|
|
|
@@ -888,7 +888,7 @@ describe('ChatService', () => {
|
|
|
888
888
|
{ content: 'https://vercel.com/ 请分析 chatGPT 关键词\n\n', role: 'user' },
|
|
889
889
|
],
|
|
890
890
|
},
|
|
891
|
-
|
|
891
|
+
expect.anything(),
|
|
892
892
|
);
|
|
893
893
|
});
|
|
894
894
|
|
|
@@ -926,7 +926,7 @@ describe('ChatService', () => {
|
|
|
926
926
|
{ content: 'https://vercel.com/ 请分析 chatGPT 关键词\n\n', role: 'user' },
|
|
927
927
|
],
|
|
928
928
|
},
|
|
929
|
-
|
|
929
|
+
expect.anything(),
|
|
930
930
|
);
|
|
931
931
|
});
|
|
932
932
|
});
|
|
@@ -985,7 +985,7 @@ describe('ChatService', () => {
|
|
|
985
985
|
}),
|
|
986
986
|
]),
|
|
987
987
|
}),
|
|
988
|
-
|
|
988
|
+
expect.anything(),
|
|
989
989
|
);
|
|
990
990
|
});
|
|
991
991
|
|
|
@@ -1036,7 +1036,7 @@ describe('ChatService', () => {
|
|
|
1036
1036
|
expect.objectContaining({
|
|
1037
1037
|
enabledSearch: true,
|
|
1038
1038
|
}),
|
|
1039
|
-
|
|
1039
|
+
expect.anything(),
|
|
1040
1040
|
);
|
|
1041
1041
|
});
|
|
1042
1042
|
|
|
@@ -1087,7 +1087,7 @@ describe('ChatService', () => {
|
|
|
1087
1087
|
expect.objectContaining({
|
|
1088
1088
|
enabledSearch: undefined,
|
|
1089
1089
|
}),
|
|
1090
|
-
|
|
1090
|
+
expect.anything(),
|
|
1091
1091
|
);
|
|
1092
1092
|
});
|
|
1093
1093
|
});
|
|
@@ -1385,7 +1385,7 @@ describe('ChatService private methods', () => {
|
|
|
1385
1385
|
expect.objectContaining({
|
|
1386
1386
|
enabledContextCaching: false,
|
|
1387
1387
|
}),
|
|
1388
|
-
|
|
1388
|
+
expect.anything(),
|
|
1389
1389
|
);
|
|
1390
1390
|
});
|
|
1391
1391
|
|
|
@@ -1438,7 +1438,7 @@ describe('ChatService private methods', () => {
|
|
|
1438
1438
|
expect.objectContaining({
|
|
1439
1439
|
reasoning_effort: 'high',
|
|
1440
1440
|
}),
|
|
1441
|
-
|
|
1441
|
+
expect.anything(),
|
|
1442
1442
|
);
|
|
1443
1443
|
});
|
|
1444
1444
|
|
|
@@ -1464,7 +1464,7 @@ describe('ChatService private methods', () => {
|
|
|
1464
1464
|
expect.objectContaining({
|
|
1465
1465
|
thinkingBudget: 5000,
|
|
1466
1466
|
}),
|
|
1467
|
-
|
|
1467
|
+
expect.anything(),
|
|
1468
1468
|
);
|
|
1469
1469
|
});
|
|
1470
1470
|
});
|
|
@@ -284,7 +284,7 @@ class ChatService {
|
|
|
284
284
|
stream: chatConfig.enableStreaming !== false,
|
|
285
285
|
tools,
|
|
286
286
|
},
|
|
287
|
-
options,
|
|
287
|
+
{ ...options, agentId: targetAgentId, topicId },
|
|
288
288
|
);
|
|
289
289
|
};
|
|
290
290
|
|
|
@@ -314,7 +314,7 @@ class ChatService {
|
|
|
314
314
|
};
|
|
315
315
|
|
|
316
316
|
getChatCompletion = async (params: Partial<ChatStreamPayload>, options?: FetchOptions) => {
|
|
317
|
-
const { signal, responseAnimation } = options ?? {};
|
|
317
|
+
const { agentId, signal, responseAnimation, topicId } = options ?? {};
|
|
318
318
|
|
|
319
319
|
const { provider = ModelProvider.OpenAI, ...res } = params;
|
|
320
320
|
|
|
@@ -401,7 +401,12 @@ class ChatService {
|
|
|
401
401
|
const traceHeader = createTraceHeader({ ...options?.trace });
|
|
402
402
|
|
|
403
403
|
const headers = await createHeaderWithAuth({
|
|
404
|
-
headers: {
|
|
404
|
+
headers: {
|
|
405
|
+
'Content-Type': 'application/json',
|
|
406
|
+
...traceHeader,
|
|
407
|
+
...(agentId && { 'x-agent-id': agentId }),
|
|
408
|
+
...(topicId && { 'x-topic-id': topicId }),
|
|
409
|
+
},
|
|
405
410
|
provider,
|
|
406
411
|
});
|
|
407
412
|
|
|
@@ -48,6 +48,31 @@ describe('resolveAgentConfig', () => {
|
|
|
48
48
|
expect(result.isBuiltinAgent).toBe(false);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
it('should treat agent with non-builtin slug as regular agent', () => {
|
|
52
|
+
// Agent has a random slug that is NOT a valid builtin agent slug
|
|
53
|
+
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
54
|
+
() => 'sister-religious-mostly-effort',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const result = resolveAgentConfig({ agentId: 'test-agent' });
|
|
58
|
+
|
|
59
|
+
expect(result.isBuiltinAgent).toBe(false);
|
|
60
|
+
expect(result.slug).toBeUndefined();
|
|
61
|
+
expect(result.plugins).toEqual(['plugin-a', 'plugin-b']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should treat agent with random slug as regular agent', () => {
|
|
65
|
+
// Another example of random/custom slug
|
|
66
|
+
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
67
|
+
() => 'my-custom-agent-slug',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = resolveAgentConfig({ agentId: 'test-agent' });
|
|
71
|
+
|
|
72
|
+
expect(result.isBuiltinAgent).toBe(false);
|
|
73
|
+
expect(result.slug).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
51
76
|
it('should return empty array when agent config has no plugins', () => {
|
|
52
77
|
vi.spyOn(agentSelectors.agentSelectors, 'getAgentConfigById').mockReturnValue(
|
|
53
78
|
() =>
|
|
@@ -668,6 +693,29 @@ describe('resolveAgentConfig', () => {
|
|
|
668
693
|
expect(result.agentConfig.systemRole.trim()).toBe('You are a helpful assistant');
|
|
669
694
|
expect(result.chatConfig.enableHistoryCount).toBe(false);
|
|
670
695
|
});
|
|
696
|
+
|
|
697
|
+
it('should not duplicate injection when page-agent itself is used in page scope', () => {
|
|
698
|
+
// page-agent is a builtin agent with slug 'page-agent'
|
|
699
|
+
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
700
|
+
() => 'page-agent',
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
704
|
+
plugins: [PageAgentIdentifier],
|
|
705
|
+
systemRole: 'Page agent system prompt',
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const result = resolveAgentConfig({
|
|
709
|
+
agentId: 'page-agent-id',
|
|
710
|
+
scope: 'page',
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// page-agent should NOT have its tools/systemRole injected again
|
|
714
|
+
expect(result.plugins.filter((p) => p === PageAgentIdentifier)).toHaveLength(1);
|
|
715
|
+
expect(result.agentConfig.systemRole).toBe('Page agent system prompt');
|
|
716
|
+
expect(result.isBuiltinAgent).toBe(true);
|
|
717
|
+
expect(result.slug).toBe('page-agent');
|
|
718
|
+
});
|
|
671
719
|
});
|
|
672
720
|
|
|
673
721
|
describe('supervisor agent (detected via groupId)', () => {
|
|
@@ -695,13 +743,11 @@ describe('resolveAgentConfig', () => {
|
|
|
695
743
|
});
|
|
696
744
|
|
|
697
745
|
describe('supervisor with own slug (priority check)', () => {
|
|
698
|
-
//
|
|
699
|
-
// it should still use 'group-supervisor' slug when in group scope
|
|
700
|
-
|
|
746
|
+
// When supervisor agent has its own slug, it should still use 'group-supervisor' slug when in group scope
|
|
701
747
|
it('should use group-supervisor slug even when agent has its own slug in group scope', () => {
|
|
702
748
|
// Supervisor agent has its own slug (e.g., from being a builtin agent)
|
|
703
749
|
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
704
|
-
() => '
|
|
750
|
+
() => 'agent-builder',
|
|
705
751
|
);
|
|
706
752
|
|
|
707
753
|
// Mock: groupById returns the group
|
|
@@ -734,54 +780,6 @@ describe('resolveAgentConfig', () => {
|
|
|
734
780
|
expect(result.slug).toBe('group-supervisor');
|
|
735
781
|
expect(result.plugins).toContain(GroupManagementIdentifier);
|
|
736
782
|
});
|
|
737
|
-
|
|
738
|
-
it('should use agent own slug when scope is not group', () => {
|
|
739
|
-
// Supervisor agent has its own slug
|
|
740
|
-
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
741
|
-
() => 'some-agent-slug',
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
// Mock: groupById returns the group
|
|
745
|
-
vi.spyOn(agentGroupSelectors.agentGroupByIdSelectors, 'groupById').mockReturnValue(
|
|
746
|
-
() => mockGroupWithSupervisor as any,
|
|
747
|
-
);
|
|
748
|
-
|
|
749
|
-
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
750
|
-
plugins: ['agent-specific-plugin'],
|
|
751
|
-
systemRole: 'Agent specific system role',
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
const result = resolveAgentConfig({
|
|
755
|
-
agentId: 'supervisor-agent-id',
|
|
756
|
-
groupId: 'group-123',
|
|
757
|
-
scope: 'main', // Not 'group' scope
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
// Should use agent's own slug since scope is not 'group'
|
|
761
|
-
expect(result.isBuiltinAgent).toBe(true);
|
|
762
|
-
expect(result.slug).toBe('some-agent-slug');
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
it('should use agent own slug when groupId is not provided', () => {
|
|
766
|
-
// Supervisor agent has its own slug
|
|
767
|
-
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
768
|
-
() => 'some-agent-slug',
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
772
|
-
plugins: ['agent-specific-plugin'],
|
|
773
|
-
systemRole: 'Agent specific system role',
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
const result = resolveAgentConfig({
|
|
777
|
-
agentId: 'supervisor-agent-id',
|
|
778
|
-
scope: 'group', // Even with group scope, no groupId
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
// Should use agent's own slug since no groupId
|
|
782
|
-
expect(result.isBuiltinAgent).toBe(true);
|
|
783
|
-
expect(result.slug).toBe('some-agent-slug');
|
|
784
|
-
});
|
|
785
783
|
});
|
|
786
784
|
|
|
787
785
|
it('should detect supervisor agent using groupId for direct lookup', () => {
|
|
@@ -872,6 +870,25 @@ describe('resolveAgentConfig', () => {
|
|
|
872
870
|
expect(result.plugins).toEqual(['plugin-a', 'plugin-b']); // Falls back to agent config plugins
|
|
873
871
|
});
|
|
874
872
|
|
|
873
|
+
it('should treat as regular agent when scope is not group even with groupId', () => {
|
|
874
|
+
// Mock: groupById returns the group (supervisor agent exists)
|
|
875
|
+
vi.spyOn(agentGroupSelectors.agentGroupByIdSelectors, 'groupById').mockReturnValue(
|
|
876
|
+
() => mockGroupWithSupervisor as any,
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
// groupId is provided but scope is 'main', not 'group'
|
|
880
|
+
const result = resolveAgentConfig({
|
|
881
|
+
agentId: 'supervisor-agent-id',
|
|
882
|
+
groupId: 'group-123',
|
|
883
|
+
scope: 'main', // Not 'group' scope
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// Should NOT be identified as group-supervisor because scope !== 'group'
|
|
887
|
+
expect(result.isBuiltinAgent).toBe(false);
|
|
888
|
+
expect(result.slug).toBeUndefined();
|
|
889
|
+
expect(result.plugins).toEqual(['plugin-a', 'plugin-b']);
|
|
890
|
+
});
|
|
891
|
+
|
|
875
892
|
it('should treat as regular agent when agentId does not match group supervisorAgentId', () => {
|
|
876
893
|
// Mock: groupById returns the group
|
|
877
894
|
vi.spyOn(agentGroupSelectors.agentGroupByIdSelectors, 'groupById').mockReturnValue(
|
|
@@ -1016,7 +1033,7 @@ describe('resolveAgentConfig', () => {
|
|
|
1016
1033
|
|
|
1017
1034
|
it('should filter lobe-gtd for builtin agent when isSubTask is true', () => {
|
|
1018
1035
|
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
1019
|
-
() => '
|
|
1036
|
+
() => 'agent-builder',
|
|
1020
1037
|
);
|
|
1021
1038
|
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
1022
1039
|
plugins: ['lobe-gtd', 'runtime-plugin'],
|
|
@@ -1034,7 +1051,7 @@ describe('resolveAgentConfig', () => {
|
|
|
1034
1051
|
|
|
1035
1052
|
it('should keep lobe-gtd for builtin agent when isSubTask is false', () => {
|
|
1036
1053
|
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
1037
|
-
() => '
|
|
1054
|
+
() => 'agent-builder',
|
|
1038
1055
|
);
|
|
1039
1056
|
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
1040
1057
|
plugins: ['lobe-gtd', 'runtime-plugin'],
|
|
@@ -1089,7 +1106,7 @@ describe('resolveAgentConfig', () => {
|
|
|
1089
1106
|
|
|
1090
1107
|
it('should return empty plugins for builtin agent when disableTools is true', () => {
|
|
1091
1108
|
vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
|
|
1092
|
-
() => '
|
|
1109
|
+
() => 'agent-builder',
|
|
1093
1110
|
);
|
|
1094
1111
|
vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
|
|
1095
1112
|
plugins: ['runtime-plugin-1', 'runtime-plugin-2'],
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BUILTIN_AGENT_SLUGS,
|
|
3
|
+
type BuiltinAgentSlug,
|
|
4
|
+
getAgentRuntimeConfig,
|
|
5
|
+
} from '@lobechat/builtin-agents';
|
|
2
6
|
import { PageAgentIdentifier } from '@lobechat/builtin-tool-page-agent';
|
|
3
7
|
import {
|
|
4
8
|
type LobeAgentChatConfig,
|
|
@@ -15,6 +19,18 @@ import { agentGroupByIdSelectors, agentGroupSelectors } from '@/store/agentGroup
|
|
|
15
19
|
|
|
16
20
|
const log = debug('mecha:agentConfigResolver');
|
|
17
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Set of valid builtin agent slugs for O(1) lookup
|
|
24
|
+
*/
|
|
25
|
+
const VALID_BUILTIN_SLUGS = new Set<string>(Object.values(BUILTIN_AGENT_SLUGS));
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a slug is a valid builtin agent slug
|
|
29
|
+
*/
|
|
30
|
+
const isBuiltinAgentSlug = (slug: string): slug is BuiltinAgentSlug => {
|
|
31
|
+
return VALID_BUILTIN_SLUGS.has(slug);
|
|
32
|
+
};
|
|
33
|
+
|
|
18
34
|
/**
|
|
19
35
|
* Applies params adjustments based on chatConfig settings.
|
|
20
36
|
*
|
|
@@ -187,12 +203,20 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
|
|
|
187
203
|
|
|
188
204
|
// If not identified as supervisor, check agent store for slug
|
|
189
205
|
if (!slug) {
|
|
190
|
-
|
|
191
|
-
log('slug from agentStore: %s (agentId: %s)',
|
|
206
|
+
const storeSlug = agentSelectors.getAgentSlugById(agentId)(agentStoreState) ?? undefined;
|
|
207
|
+
log('slug from agentStore: %s (agentId: %s)', storeSlug, agentId);
|
|
208
|
+
|
|
209
|
+
// Only use the slug if it's a valid builtin agent slug
|
|
210
|
+
// Regular agents may have random slugs that should be ignored
|
|
211
|
+
if (storeSlug && isBuiltinAgentSlug(storeSlug)) {
|
|
212
|
+
slug = storeSlug;
|
|
213
|
+
} else if (storeSlug) {
|
|
214
|
+
log('slug %s is not a valid builtin agent slug, treating as regular agent', storeSlug);
|
|
215
|
+
}
|
|
192
216
|
}
|
|
193
217
|
|
|
194
218
|
if (!slug) {
|
|
195
|
-
log('agentId %s is not a builtin agent (no slug found)', agentId);
|
|
219
|
+
log('agentId %s is not a builtin agent (no valid builtin slug found)', agentId);
|
|
196
220
|
// Regular agent - use provided plugins if available, fallback to agent's plugins
|
|
197
221
|
const finalPlugins = plugins && plugins.length > 0 ? plugins : basePlugins;
|
|
198
222
|
|
|
@@ -583,7 +583,7 @@ describe('contextEngineering', () => {
|
|
|
583
583
|
});
|
|
584
584
|
|
|
585
585
|
describe('Message preprocessing processors', () => {
|
|
586
|
-
it('should
|
|
586
|
+
it('should keep all messages (no history truncation)', async () => {
|
|
587
587
|
const messages: UIChatMessage[] = [
|
|
588
588
|
{
|
|
589
589
|
role: 'user',
|
|
@@ -626,13 +626,12 @@ describe('contextEngineering', () => {
|
|
|
626
626
|
messages,
|
|
627
627
|
model: 'gpt-4',
|
|
628
628
|
provider: 'openai',
|
|
629
|
-
enableHistoryCount: true,
|
|
630
|
-
historyCount: 4, // Should keep last 2 messages
|
|
631
629
|
});
|
|
632
630
|
|
|
633
|
-
// Should
|
|
634
|
-
expect(result).toHaveLength(
|
|
631
|
+
// Should keep all messages
|
|
632
|
+
expect(result).toHaveLength(5);
|
|
635
633
|
expect(result).toEqual([
|
|
634
|
+
{ content: 'Message 1', role: 'user' },
|
|
636
635
|
{ content: 'Response 1', role: 'assistant' },
|
|
637
636
|
{ content: 'Message 2', role: 'user' },
|
|
638
637
|
{ content: 'Response 2', role: 'assistant' },
|
|
@@ -704,7 +703,7 @@ describe('contextEngineering', () => {
|
|
|
704
703
|
]);
|
|
705
704
|
});
|
|
706
705
|
|
|
707
|
-
it('should combine
|
|
706
|
+
it('should combine system role and input template correctly', async () => {
|
|
708
707
|
const messages: UIChatMessage[] = [
|
|
709
708
|
{
|
|
710
709
|
role: 'user',
|
|
@@ -735,16 +734,18 @@ describe('contextEngineering', () => {
|
|
|
735
734
|
provider: 'openai',
|
|
736
735
|
systemRole: 'System instructions.',
|
|
737
736
|
inputTemplate: 'Processed: {{text}}',
|
|
738
|
-
enableHistoryCount: true,
|
|
739
|
-
historyCount: 2, // Should keep last 1 message
|
|
740
737
|
});
|
|
741
738
|
|
|
742
|
-
// System role should be first
|
|
739
|
+
// System role should be first, followed by all messages with input template applied to user messages
|
|
743
740
|
expect(result).toEqual([
|
|
744
741
|
{
|
|
745
742
|
content: 'System instructions.',
|
|
746
743
|
role: 'system',
|
|
747
744
|
},
|
|
745
|
+
{
|
|
746
|
+
content: 'Processed: Old message 1',
|
|
747
|
+
role: 'user',
|
|
748
|
+
},
|
|
748
749
|
{
|
|
749
750
|
role: 'assistant',
|
|
750
751
|
content: 'Old response',
|
|
@@ -782,7 +783,7 @@ describe('contextEngineering', () => {
|
|
|
782
783
|
]);
|
|
783
784
|
});
|
|
784
785
|
|
|
785
|
-
it('should handle
|
|
786
|
+
it('should handle system role injection with all messages (no history truncation)', async () => {
|
|
786
787
|
const messages: UIChatMessage[] = [
|
|
787
788
|
{
|
|
788
789
|
role: 'user',
|
|
@@ -812,18 +813,24 @@ describe('contextEngineering', () => {
|
|
|
812
813
|
model: 'gpt-4',
|
|
813
814
|
provider: 'openai',
|
|
814
815
|
systemRole: 'System role here.',
|
|
815
|
-
enableHistoryCount: true,
|
|
816
|
-
historyCount: 1, // Should keep only 1 message
|
|
817
816
|
});
|
|
818
817
|
|
|
819
|
-
// Should have system role +
|
|
818
|
+
// Should have system role + all messages
|
|
820
819
|
expect(result).toEqual([
|
|
821
820
|
{
|
|
822
821
|
content: 'System role here.',
|
|
823
822
|
role: 'system',
|
|
824
823
|
},
|
|
825
824
|
{
|
|
826
|
-
content: 'Message
|
|
825
|
+
content: 'Message 1',
|
|
826
|
+
role: 'user',
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
content: 'Message 2',
|
|
830
|
+
role: 'user',
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
content: 'Message 3',
|
|
827
834
|
role: 'user',
|
|
828
835
|
},
|
|
829
836
|
]);
|
|
@@ -394,6 +394,18 @@ export const contextEngineering = async ({
|
|
|
394
394
|
...(gtdConfig && { gtd: gtdConfig }),
|
|
395
395
|
});
|
|
396
396
|
|
|
397
|
+
log('Input messages count: %d', messages.length);
|
|
398
|
+
|
|
397
399
|
const result = await engine.process();
|
|
400
|
+
|
|
401
|
+
log('Output messages count: %d', result.messages.length);
|
|
402
|
+
|
|
403
|
+
if (messages.length > 0 && result.messages.length === 0) {
|
|
404
|
+
log(
|
|
405
|
+
'WARNING: Messages were reduced to 0! Input messages: %o',
|
|
406
|
+
messages.map((m) => ({ id: m.id, role: m.role })),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
398
410
|
return result.messages;
|
|
399
411
|
};
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type FetchSSEOptions } from '@lobechat/fetch-sse';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type RuntimeInitialContext,
|
|
4
|
+
type RuntimeStepContext,
|
|
5
|
+
type TracePayload,
|
|
6
|
+
} from '@lobechat/types';
|
|
3
7
|
|
|
4
8
|
export interface FetchOptions extends FetchSSEOptions {
|
|
9
|
+
agentId?: string;
|
|
5
10
|
historySummary?: string;
|
|
6
11
|
/** Initial context for page editor (captured at operation start) */
|
|
7
12
|
initialContext?: RuntimeInitialContext;
|
|
8
13
|
signal?: AbortSignal | undefined;
|
|
9
14
|
/** Step context for page editor (updated each step) */
|
|
10
15
|
stepContext?: RuntimeStepContext;
|
|
16
|
+
topicId?: string;
|
|
11
17
|
trace?: TracePayload;
|
|
12
18
|
}
|
|
@@ -22,10 +22,9 @@ import type { ChatToolPayload, ConversationContext, CreateMessageParams } from '
|
|
|
22
22
|
import debug from 'debug';
|
|
23
23
|
import pMap from 'p-map';
|
|
24
24
|
|
|
25
|
-
import type { ResolvedAgentConfig } from '@/services/chat/mecha';
|
|
26
|
-
|
|
27
25
|
import { LOADING_FLAT } from '@/const/message';
|
|
28
26
|
import { aiAgentService } from '@/services/aiAgent';
|
|
27
|
+
import type { ResolvedAgentConfig } from '@/services/chat/mecha';
|
|
29
28
|
import { agentByIdSelectors } from '@/store/agent/selectors';
|
|
30
29
|
import { getAgentStoreState } from '@/store/agent/store';
|
|
31
30
|
import type { ChatStore } from '@/store/chat/store';
|
|
@@ -96,7 +95,12 @@ export const createAgentExecutors = (context: {
|
|
|
96
95
|
const llmPayload = (instruction as AgentInstructionCallLlm)
|
|
97
96
|
.payload as GeneralAgentCallLLMInstructionPayload;
|
|
98
97
|
|
|
99
|
-
log(
|
|
98
|
+
log(
|
|
99
|
+
`${stagePrefix} Starting session. Input: state.messages=%d, llmPayload.messages=%d, messageKey=%s`,
|
|
100
|
+
state.messages.length,
|
|
101
|
+
llmPayload.messages.length,
|
|
102
|
+
context.messageKey,
|
|
103
|
+
);
|
|
100
104
|
|
|
101
105
|
let assistantMessageId: string;
|
|
102
106
|
|
|
@@ -184,6 +188,12 @@ export const createAgentExecutors = (context: {
|
|
|
184
188
|
// Get latest messages from store (already updated by internal_fetchAIChatMessage)
|
|
185
189
|
const latestMessages = context.get().dbMessagesMap[context.messageKey] || [];
|
|
186
190
|
|
|
191
|
+
log(
|
|
192
|
+
`${stagePrefix} After fetch: dbMessagesMap[${context.messageKey}]=%d messages, available keys=%o`,
|
|
193
|
+
latestMessages.length,
|
|
194
|
+
Object.keys(context.get().dbMessagesMap),
|
|
195
|
+
);
|
|
196
|
+
|
|
187
197
|
// Get updated assistant message to extract usage/cost information
|
|
188
198
|
const assistantMessage = latestMessages.find((m) => m.id === assistantMessageId);
|
|
189
199
|
|
|
@@ -206,10 +216,11 @@ export const createAgentExecutors = (context: {
|
|
|
206
216
|
}
|
|
207
217
|
|
|
208
218
|
log(
|
|
209
|
-
'[%s:%d] call_llm completed, finishType: %s',
|
|
219
|
+
'[%s:%d] call_llm completed, finishType: %s, outputMessages: %d',
|
|
210
220
|
state.operationId,
|
|
211
221
|
state.stepCount,
|
|
212
222
|
finishType,
|
|
223
|
+
latestMessages.length,
|
|
213
224
|
);
|
|
214
225
|
|
|
215
226
|
// Accumulate usage and cost to state
|
|
@@ -259,6 +259,7 @@ export const conversationLifecycle: StateCreator<
|
|
|
259
259
|
if (data?.topics) {
|
|
260
260
|
const pageSize = systemStatusSelectors.topicPageSize(useGlobalStore.getState());
|
|
261
261
|
get().internal_updateTopics(operationContext.agentId, {
|
|
262
|
+
groupId: operationContext.groupId,
|
|
262
263
|
items: data.topics.items,
|
|
263
264
|
pageSize,
|
|
264
265
|
total: data.topics.total,
|
|
@@ -757,20 +757,24 @@ export const streamingExecutor: StateCreator<
|
|
|
757
757
|
nextContext = { ...nextContext, stepContext };
|
|
758
758
|
|
|
759
759
|
log(
|
|
760
|
-
'[internal_execAgentRuntime][step-%d]: phase=%s, status=%s, stepContext=%O',
|
|
760
|
+
'[internal_execAgentRuntime][step-%d]: phase=%s, status=%s, state.messages=%d, dbMessagesMap[%s]=%d, stepContext=%O',
|
|
761
761
|
stepCount,
|
|
762
762
|
nextContext.phase,
|
|
763
763
|
state.status,
|
|
764
|
+
state.messages.length,
|
|
765
|
+
messageKey,
|
|
766
|
+
currentDBMessages.length,
|
|
764
767
|
stepContext,
|
|
765
768
|
);
|
|
766
769
|
|
|
767
770
|
const result = await runtime.step(state, nextContext);
|
|
768
771
|
|
|
769
772
|
log(
|
|
770
|
-
'[internal_execAgentRuntime] Step %d completed, events: %d, newStatus=%s',
|
|
773
|
+
'[internal_execAgentRuntime] Step %d completed, events: %d, newStatus=%s, newState.messages=%d',
|
|
771
774
|
stepCount,
|
|
772
775
|
result.events.length,
|
|
773
776
|
result.newState.status,
|
|
777
|
+
result.newState.messages.length,
|
|
774
778
|
);
|
|
775
779
|
|
|
776
780
|
// After parallel tool batch completes, refresh messages to ensure all tool results are synced
|