feat: 先进智能体功能上线

- 基于 agent-runtime 打造,默认 ReAct agent
- 支持 agent 能力对接,已对接工作流、插件、知识库等 tool 能力
- 全新 agent 编排界面,支持可视化便捷配置 agent
- 全新 agent 聊天界面,支持快捷操作、额外知识库选择等
This commit is contained in:
2026-05-28 11:29:18 +08:00
parent 11e595b088
commit 1c205c3720
39 changed files with 3546 additions and 217 deletions

View File

@@ -111,6 +111,135 @@ describe('agentTimelineAdapter', () => {
).toBe(true);
});
it('hides knowledge retrieval cards when restoring agent chat history', () => {
const items = recordsToTimelineItems([
{
id: '417197643647811584',
senderRole: 'assistant',
contentText:
'根据知识库中的信息2026年暑假的时间安排是7月1日到8月15日。',
roundId: '417197622424633344',
contentPayload: {
chains: [
{
id: 'call_0fc660e9d203416983ccca7e',
name: 'retrieve_knowledge',
result: 'Retrieved 2 relevant document(s)',
status: 'TOOL_RESULT',
arguments: {
query: '暑假时间',
},
},
],
agentResult: {
text: '根据知识库中的信息2026年暑假的时间安排是7月1日到8月15日。',
knowledgeReferences: [
{
chunkContent:
'问题2026 年暑假安排\n答案2026 年7 月 1 日到 8 月 15 日',
documentId: '411358369563336704',
knowledgeName: 'faq',
knowledgeType: 'FAQ',
},
],
},
messageChain: [
{
role: 'assistant',
toolCalls: [
{
id: 'call_0fc660e9d203416983ccca7e',
name: 'retrieve_knowledge',
arguments: '{query=暑假时间}',
},
],
},
{
role: 'tool',
content: 'Retrieved 2 relevant document(s)',
toolCallId: 'call_0fc660e9d203416983ccca7e',
},
{
role: 'assistant',
content:
'根据知识库中的信息2026年暑假的时间安排是7月1日到8月15日。',
},
],
},
},
]);
expect(items.some((item) => item.type === 'tool')).toBe(false);
expect(
items.some(
(item) => item.type === 'status' && item.label === '已检索知识库',
),
).toBe(true);
expect(
items.some((item) => item.type === 'message' && item.role === 'assistant'),
).toBe(true);
const assistant = items.find(
(item): item is ChatTimelineMessageItem =>
item.type === 'message' && item.role === 'assistant',
);
expect(assistant?.knowledgeItems?.[0]?.knowledgeName).toBe('faq');
});
it('hides internal fragment and context reload tools from history', () => {
const items = recordsToTimelineItems([
{
id: 'internal-tools',
senderRole: 'assistant',
contentText: '已处理',
roundId: 'round-internal',
contentPayload: {
chains: [
{
id: 'fragment-1',
name: '__fragment__',
status: 'TOOL_RESULT',
result: 'fragment',
},
{
id: 'context-1',
name: 'context_reload',
status: 'TOOL_RESULT',
result: 'reload',
},
],
agentResult: {
text: '已处理',
},
messageChain: [
{
role: 'assistant',
toolCalls: [
{ id: 'fragment-1', name: '__fragment__' },
{ id: 'context-1', name: 'context_reload' },
],
},
{
role: 'tool',
toolCallId: 'fragment-1',
content: 'fragment',
},
{
role: 'tool',
toolCallId: 'context-1',
content: 'reload',
},
],
},
},
]);
expect(items.some((item) => item.type === 'tool')).toBe(false);
expect(items.some((item) => item.type === 'status')).toBe(false);
expect(
items.some((item) => item.type === 'message' && item.role === 'assistant'),
).toBe(true);
});
it('parses raw SSE text as message delta', () => {
const envelope = parseAgentSseMessage({
data: 'hello',
@@ -227,4 +356,123 @@ describe('agentTimelineAdapter', () => {
expect(assistant?.roundId).toBe('runtime-round-1');
expect(assistant?.parts[0]?.content).toBe('准备调用工具');
});
it('updates memory compression status within the current round', () => {
const items: any[] = [];
applyAgentSseEnvelope(
items,
{
domain: 'BUSINESS',
type: 'STATUS',
payload: {
label: '正在整理上下文',
phase: 'started',
status: 'running',
statusKey: 'memory-compression',
},
},
{ roundId: 'round-a' },
);
applyAgentSseEnvelope(
items,
{
domain: 'BUSINESS',
type: 'STATUS',
payload: {
compressed: true,
label: '已整理上下文',
phase: 'completed',
status: 'done',
statusKey: 'memory-compression',
},
},
{ roundId: 'round-a' },
);
const statuses = items.filter((item) => item.type === 'status');
expect(statuses).toHaveLength(1);
expect(statuses[0]?.label).toBe('已整理上下文');
expect(statuses[0]?.status).toBe('done');
expect(statuses[0]?.statusKey).toBe('memory-compression:round-a');
});
it('keeps memory compression statuses isolated by round', () => {
const items: any[] = [];
applyAgentSseEnvelope(
items,
{
domain: 'BUSINESS',
type: 'STATUS',
payload: {
compressed: true,
label: '已整理上下文',
phase: 'completed',
status: 'done',
statusKey: 'memory-compression',
},
},
{ roundId: 'round-a' },
);
applyAgentSseEnvelope(
items,
{
domain: 'BUSINESS',
type: 'STATUS',
payload: {
compressed: false,
label: '无需压缩上下文',
phase: 'completed',
status: 'done',
statusKey: 'memory-compression',
},
},
{ roundId: 'round-b' },
);
const statuses = items.filter((item) => item.type === 'status');
expect(statuses).toHaveLength(1);
expect(statuses[0]?.statusKey).toBe('memory-compression:round-a');
expect(statuses[0]?.label).toBe('已整理上下文');
});
it('does not show no-compression status before a later compression run', () => {
const items: any[] = [];
applyAgentSseEnvelope(
items,
{
domain: 'BUSINESS',
type: 'STATUS',
payload: {
compressed: false,
label: '无需压缩上下文',
phase: 'completed',
status: 'done',
statusKey: 'memory-compression',
},
},
{ roundId: 'round-a' },
);
applyAgentSseEnvelope(
items,
{
domain: 'BUSINESS',
type: 'STATUS',
payload: {
label: '正在整理上下文',
phase: 'started',
status: 'running',
statusKey: 'memory-compression',
},
},
{ roundId: 'round-a' },
);
const statuses = items.filter((item) => item.type === 'status');
expect(statuses).toHaveLength(1);
expect(statuses[0]?.label).toBe('正在整理上下文');
expect(statuses[0]?.status).toBe('running');
});
});