feat(ui): 添加子 Agent 进度显示功能
当 build agent 调用 guide/explore 等子 agent 时, 用户可以在 web 页面实时看到子 agent 的执行进度 实现方案: - Core: 使用 EventEmitter 模式发射子 agent 事件 - Server: 订阅事件并转发到 WebSocket - UI: 处理事件并渲染 SubagentProgress 组件 新增文件: - packages/core/src/agent/events.ts - packages/ui/src/components/SubagentProgress.tsx 修改文件: - core: executor.ts, manager.ts, types.ts, task.ts - server: adapter.ts, types.ts - ui: useChat.ts, types.ts - web: Chat.tsx
This commit is contained in:
@@ -18,6 +18,7 @@ import type {
|
||||
import { checkBashPermission, isPathInAllowedWritePaths } from './permission-merger.js';
|
||||
import { getProviderRegistry } from '../provider/index.js';
|
||||
import { renderPromptTemplate, createPlanContext } from '../template/index.js';
|
||||
import { agentEventEmitter } from './events.js';
|
||||
|
||||
/**
|
||||
* Agent 执行器
|
||||
@@ -54,7 +55,10 @@ export class AgentExecutor {
|
||||
prompt: string,
|
||||
context: AgentExecutionContext
|
||||
): Promise<AgentExecutionResult> {
|
||||
const { onStream, onToolCall, onToolResult, images } = context;
|
||||
const { onStream, onToolCall, onToolResult, images, sessionId, agentId, emitEvents } = context;
|
||||
|
||||
// 是否发射子 Agent 事件
|
||||
const shouldEmitEvents = emitEvents && sessionId && agentId;
|
||||
|
||||
// 获取过滤后的工具
|
||||
const tools = this.getFilteredTools();
|
||||
@@ -82,9 +86,12 @@ export class AgentExecutor {
|
||||
let fullResponse = '';
|
||||
let steps = 0;
|
||||
|
||||
// 工具调用时间追踪(用于计算持续时间)
|
||||
const toolStartTimes = new Map<string, number>();
|
||||
|
||||
try {
|
||||
if (onStream) {
|
||||
// 流式模式
|
||||
if (onStream || shouldEmitEvents) {
|
||||
// 流式模式(或需要发射事件时使用流式模式)
|
||||
const result = streamText({
|
||||
model: this.getModel(modelName),
|
||||
system: systemPrompt,
|
||||
@@ -96,23 +103,72 @@ export class AgentExecutor {
|
||||
if (chunk.type === 'tool-call') {
|
||||
steps++;
|
||||
const toolArgs = 'input' in chunk ? chunk.input : {};
|
||||
const toolCallId = `tool-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
// 记录工具开始时间
|
||||
toolStartTimes.set(chunk.toolName, Date.now());
|
||||
|
||||
onToolCall?.(chunk.toolName, toolArgs as Record<string, unknown>);
|
||||
onStream(`\n[调用工具: ${chunk.toolName}]\n`);
|
||||
onStream?.(`\n[调用工具: ${chunk.toolName}]\n`);
|
||||
|
||||
// 发射子 Agent 工具开始事件
|
||||
if (shouldEmitEvents) {
|
||||
agentEventEmitter.emit({
|
||||
type: 'subagent:tool_start',
|
||||
sessionId: sessionId!,
|
||||
agentId: agentId!,
|
||||
toolCallId,
|
||||
toolName: chunk.toolName,
|
||||
args: toolArgs as Record<string, unknown>,
|
||||
});
|
||||
}
|
||||
} else if (chunk.type === 'tool-result') {
|
||||
const output = (chunk as { output?: ToolResult }).output;
|
||||
onToolResult?.(
|
||||
(chunk as { toolName?: string }).toolName ?? 'unknown',
|
||||
output
|
||||
);
|
||||
const toolName = (chunk as { toolName?: string }).toolName ?? 'unknown';
|
||||
const toolCallId = `tool-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
// 计算工具执行时间
|
||||
const startTime = toolStartTimes.get(toolName);
|
||||
const duration = startTime ? Date.now() - startTime : undefined;
|
||||
toolStartTimes.delete(toolName);
|
||||
|
||||
onToolResult?.(toolName, output);
|
||||
|
||||
if (output && typeof output === 'object') {
|
||||
if (output.success) {
|
||||
const displayOutput =
|
||||
output.output.length > 500
|
||||
? output.output.substring(0, 500) + '...(截断)'
|
||||
: output.output;
|
||||
onStream(`[结果: ${displayOutput}]\n`);
|
||||
onStream?.(`[结果: ${displayOutput}]\n`);
|
||||
|
||||
// 发射子 Agent 工具完成事件
|
||||
if (shouldEmitEvents) {
|
||||
agentEventEmitter.emit({
|
||||
type: 'subagent:tool_end',
|
||||
sessionId: sessionId!,
|
||||
agentId: agentId!,
|
||||
toolCallId,
|
||||
status: 'completed',
|
||||
result: output.output,
|
||||
duration,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
onStream(`[错误: ${output.error}]\n`);
|
||||
onStream?.(`[错误: ${output.error}]\n`);
|
||||
|
||||
// 发射子 Agent 工具错误事件
|
||||
if (shouldEmitEvents) {
|
||||
agentEventEmitter.emit({
|
||||
type: 'subagent:tool_end',
|
||||
sessionId: sessionId!,
|
||||
agentId: agentId!,
|
||||
toolCallId,
|
||||
status: 'error',
|
||||
error: output.error,
|
||||
duration,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +177,17 @@ export class AgentExecutor {
|
||||
|
||||
for await (const chunk of result.textStream) {
|
||||
fullResponse += chunk;
|
||||
onStream(chunk);
|
||||
onStream?.(chunk);
|
||||
|
||||
// 发射子 Agent 流式输出事件
|
||||
if (shouldEmitEvents && chunk) {
|
||||
agentEventEmitter.emit({
|
||||
type: 'subagent:stream',
|
||||
sessionId: sessionId!,
|
||||
agentId: agentId!,
|
||||
content: chunk,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await result.response;
|
||||
|
||||
Reference in New Issue
Block a user