feat: 完善 Agent 标准交互与安全运行时
- 接入 AG-UI 运行投影、Turn 时间线和审批隔离 - 增加 Agent Skill 冻结绑定与运行时消费闭环 - 增加受控工作区、内置工具和私有 Artifact 生命周期
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import type { EasyFlowAguiRunOptions } from '../../shared/agent-agui/client';
|
||||
import type { AgentInfo } from '../types';
|
||||
|
||||
import { EventType } from '@ag-ui/client';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { useAgentTryoutStream } from './useAgentTryoutStream';
|
||||
|
||||
const aguiMocks = vi.hoisted(() => ({
|
||||
abort: vi.fn(),
|
||||
runs: [] as EasyFlowAguiRunOptions[],
|
||||
}));
|
||||
|
||||
vi.mock('../../shared/agent-agui/client', () => ({
|
||||
EasyFlowAguiClient: class {
|
||||
abort = aguiMocks.abort;
|
||||
|
||||
run(options: EasyFlowAguiRunOptions) {
|
||||
aguiMocks.runs.push(options);
|
||||
return new Promise(() => {});
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../api', () => ({
|
||||
clearAgentDraftSession: vi.fn(async () => ({ errorCode: 0 })),
|
||||
}));
|
||||
|
||||
describe('useAgentTryoutStream', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear();
|
||||
aguiMocks.runs.length = 0;
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('ignores late events from a stopped draft run after resending', async () => {
|
||||
const stream = useAgentTryoutStream();
|
||||
const payload = {
|
||||
agent: { id: 'agent-1', name: 'Agent' } as AgentInfo,
|
||||
knowledgeBindings: [],
|
||||
prompt: '旧问题',
|
||||
sessionId: 'draft-race',
|
||||
skillBindings: [],
|
||||
toolBindings: [],
|
||||
};
|
||||
|
||||
void stream.sendDraft(payload);
|
||||
await Promise.resolve();
|
||||
stream.stop();
|
||||
void stream.sendDraft({ ...payload, prompt: '新问题' });
|
||||
await Promise.resolve();
|
||||
|
||||
aguiMocks.runs[0]?.onEvent({
|
||||
delta: '旧流迟到正文',
|
||||
messageId: 'old-assistant',
|
||||
type: EventType.TEXT_MESSAGE_CONTENT,
|
||||
});
|
||||
aguiMocks.runs[1]?.onEvent({
|
||||
delta: '新流正文',
|
||||
messageId: 'new-assistant',
|
||||
type: EventType.TEXT_MESSAGE_CONTENT,
|
||||
});
|
||||
|
||||
const timeline = JSON.stringify(stream.timelineItems.value);
|
||||
expect(timeline).toContain('新流正文');
|
||||
expect(timeline).not.toContain('旧流迟到正文');
|
||||
});
|
||||
|
||||
it('sends only the draft runtime snapshot through forwardedProps', async () => {
|
||||
const stream = useAgentTryoutStream();
|
||||
void stream.sendDraft({
|
||||
agent: {
|
||||
created: '2026-08-14 10:00:00',
|
||||
displayPublishStatus: 'PUBLISHED',
|
||||
id: 'agent-1',
|
||||
modelId: 'model-1',
|
||||
name: 'Agent',
|
||||
publishedSnapshotJson: { internal: true },
|
||||
},
|
||||
knowledgeBindings: [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
enabled: true,
|
||||
id: 'knowledge-binding-1',
|
||||
knowledgeId: 'knowledge-1',
|
||||
localId: 'local-knowledge',
|
||||
optionsJson: { topK: 5 },
|
||||
resourceSnapshot: { internal: true },
|
||||
resourceSummary: { displayName: '知识库' },
|
||||
retrievalMode: 'HYBRID',
|
||||
sortNo: 2,
|
||||
},
|
||||
],
|
||||
prompt: '问题',
|
||||
sessionId: 'draft-snapshot',
|
||||
skillBindings: [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
id: 'skill-binding-1',
|
||||
resourceSummary: {
|
||||
displayName: '合同审查',
|
||||
toolCount: 2,
|
||||
},
|
||||
skillId: 'skill-1',
|
||||
sortNo: 3,
|
||||
},
|
||||
],
|
||||
toolBindings: [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
enabled: true,
|
||||
hitlConfigJson: { message: '请确认' },
|
||||
hitlEnabled: true,
|
||||
id: 'tool-binding-1',
|
||||
localId: 'local-tool',
|
||||
optionsJson: { timeout: 30 },
|
||||
resourceSnapshot: { internal: true },
|
||||
resourceSummary: { displayName: '工具' },
|
||||
sortNo: 1,
|
||||
targetId: 'tool-1',
|
||||
toolName: 'search',
|
||||
toolType: 'PLUGIN',
|
||||
},
|
||||
],
|
||||
});
|
||||
await Promise.resolve();
|
||||
|
||||
const forwardedProps = aguiMocks.runs[0]?.forwardedProps as {
|
||||
easyflow: {
|
||||
draft: {
|
||||
agent: Record<string, unknown>;
|
||||
knowledgeBindings: Record<string, unknown>[];
|
||||
skillBindings: Record<string, unknown>[];
|
||||
toolBindings: Record<string, unknown>[];
|
||||
};
|
||||
};
|
||||
};
|
||||
expect(forwardedProps.easyflow.draft.agent).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 'agent-1',
|
||||
modelId: 'model-1',
|
||||
name: 'Agent',
|
||||
}),
|
||||
);
|
||||
expect(forwardedProps.easyflow.draft.agent).not.toHaveProperty('created');
|
||||
expect(forwardedProps.easyflow.draft.agent).not.toHaveProperty(
|
||||
'publishedSnapshotJson',
|
||||
);
|
||||
expect(forwardedProps.easyflow.draft.toolBindings).toEqual([
|
||||
{
|
||||
enabled: true,
|
||||
hitlConfigJson: { message: '请确认' },
|
||||
hitlEnabled: true,
|
||||
id: 'tool-binding-1',
|
||||
optionsJson: { timeout: 30 },
|
||||
sortNo: 1,
|
||||
targetId: 'tool-1',
|
||||
toolName: 'search',
|
||||
toolType: 'PLUGIN',
|
||||
},
|
||||
]);
|
||||
expect(forwardedProps.easyflow.draft.knowledgeBindings).toEqual([
|
||||
{
|
||||
enabled: true,
|
||||
id: 'knowledge-binding-1',
|
||||
knowledgeId: 'knowledge-1',
|
||||
optionsJson: { topK: 5 },
|
||||
retrievalMode: 'HYBRID',
|
||||
sortNo: 2,
|
||||
},
|
||||
]);
|
||||
expect(forwardedProps.easyflow.draft.skillBindings).toEqual([
|
||||
{
|
||||
skillId: 'skill-1',
|
||||
sortNo: 3,
|
||||
},
|
||||
]);
|
||||
stream.stop();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user