feat: 添加swarms团队编排功能并优化agent委派系统

- 引入AgentTeamOrchestrator支持多agent协同任务执行
- 增加第三方swarms库依赖并配置git协议替换以改善包管理
- 扩展DelegationManager支持团队任务调度和进度跟踪
- 实现中文bigram分词算法提升中文任务检索准确性
- 调整A2AClient和DelegationManager超时时间从30秒增至600秒
- 优化AgentRunResult状态判断逻辑增加有意义摘要检测
- 修改Dockerfile配置npm仓库镜像地址和git协议映射
- 更新CLI命令行接口支持网关端口配置传递
- 调整提供者超时配置机制增强请求稳定性
- 移除过时的support_group字段简化agent描述符结构
- 增强错误处理和进度事件报告机制改进用户体验
This commit is contained in:
2026-04-14 14:34:23 +08:00
parent fee9007da6
commit cdfc222c9f
85 changed files with 5443 additions and 1392 deletions

View File

@ -6,6 +6,7 @@ import type {
ProcessRunStatus,
Session,
} from '@/types';
import { getCurrentAppLocale, pickAppText, type AppLocale } from '@/lib/i18n/core';
const TERMINAL_STATUSES = new Set<OfficeTaskStatus>(['done', 'error', 'cancelled']);
const STALE_WAITING_MS = 2 * 60 * 1000;
@ -200,8 +201,8 @@ function latestTimestamp(values: Array<string | null | undefined>): string | nul
return selected;
}
function getSessionLabel(sessions: Session[], sessionId: string | null): string {
if (!sessionId) return '未关联会话';
function getSessionLabel(sessions: Session[], sessionId: string | null, locale: AppLocale): string {
if (!sessionId) return pickAppText(locale, '未关联会话', 'No session linked');
const session = sessions.find((item) => item.key === sessionId);
if (!session) return sessionId;
return session.path?.trim() || session.key;
@ -279,6 +280,7 @@ function deriveStageLabel(
run: ProcessRun,
runEvents: ProcessEvent[],
fallbackStatus: OfficeTaskStatus,
locale: AppLocale,
): string | null {
const runMetadataLabel = readMetadataString(run.metadata, [
'stage_label',
@ -299,13 +301,13 @@ function deriveStageLabel(
if (label) return label;
}
if (fallbackStatus === 'running') return '执行中';
if (fallbackStatus === 'waiting') return '等待中';
if (fallbackStatus === 'queued') return '排队中';
if (fallbackStatus === 'done') return '已完成';
if (fallbackStatus === 'error') return '失败';
if (fallbackStatus === 'cancelled') return '已取消';
if (fallbackStatus === 'blocked') return '阻塞';
if (fallbackStatus === 'running') return pickAppText(locale, '执行中', 'Running');
if (fallbackStatus === 'waiting') return pickAppText(locale, '等待中', 'Waiting');
if (fallbackStatus === 'queued') return pickAppText(locale, '排队中', 'Queued');
if (fallbackStatus === 'done') return pickAppText(locale, '已完成', 'Done');
if (fallbackStatus === 'error') return pickAppText(locale, '失败', 'Error');
if (fallbackStatus === 'cancelled') return pickAppText(locale, '已取消', 'Cancelled');
if (fallbackStatus === 'blocked') return pickAppText(locale, '阻塞', 'Blocked');
return null;
}
@ -330,13 +332,13 @@ function mapZoneId(status: OfficeTaskStatus, actorType: ProcessActorType): Offic
return 'alert';
}
function zoneLabel(zoneId: OfficeZoneId): string {
if (zoneId === 'reception') return '接待区';
if (zoneId === 'workspace') return '工位区';
if (zoneId === 'collab') return '协作区';
if (zoneId === 'research') return '研究区';
if (zoneId === 'alert') return '异常区';
return '完成区';
function zoneLabel(zoneId: OfficeZoneId, locale: AppLocale): string {
if (zoneId === 'reception') return pickAppText(locale, '接待区', 'Reception');
if (zoneId === 'workspace') return pickAppText(locale, '工位区', 'Workspace');
if (zoneId === 'collab') return pickAppText(locale, '协作区', 'Collaboration');
if (zoneId === 'research') return pickAppText(locale, '研究区', 'Research');
if (zoneId === 'alert') return pickAppText(locale, '异常区', 'Alerts');
return pickAppText(locale, '完成区', 'Completed');
}
function zoneTone(zoneId: OfficeZoneId): OfficeZoneView['tone'] {
@ -378,7 +380,7 @@ function selectDisplayRun(
return sorted[0];
}
function deriveErrorText(run: ProcessRun, runEvents: ProcessEvent[]): string | null {
function deriveErrorText(run: ProcessRun, runEvents: ProcessEvent[], locale: AppLocale): string | null {
if (run.status !== 'error') return null;
const direct = firstString(run.summary);
if (direct) return direct;
@ -388,13 +390,14 @@ function deriveErrorText(run: ProcessRun, runEvents: ProcessEvent[]): string | n
return event.text!.trim();
}
}
return '任务执行失败';
return pickAppText(locale, '任务执行失败', 'Task execution failed');
}
function deriveProgress(
rootRun: ProcessRun,
taskRuns: ProcessRun[],
taskViews: OfficeTaskView[],
locale: AppLocale,
): OfficeProgressView {
const stageValue = readMetadataNumber(rootRun.metadata, ['stage_index', 'step_index', 'phase_index']);
const stageMax = readMetadataNumber(rootRun.metadata, ['stage_total', 'step_total', 'phase_total']);
@ -403,7 +406,11 @@ function deriveProgress(
if (stageValue !== null && stageMax !== null && stageMax > 0) {
return {
mode: 'ratio',
label: `阶段 ${Math.min(stageValue, stageMax)} / ${stageMax}`,
label: pickAppText(
locale,
`阶段 ${Math.min(stageValue, stageMax)} / ${stageMax}`,
`Stage ${Math.min(stageValue, stageMax)} / ${stageMax}`
),
value: stageValue,
max: stageMax,
stageLabel,
@ -414,7 +421,11 @@ function deriveProgress(
if (taskRuns.length > 0) {
return {
mode: 'ratio',
label: `已完成子任务 ${doneRuns} / ${taskRuns.length}`,
label: pickAppText(
locale,
`已完成子任务 ${doneRuns} / ${taskRuns.length}`,
`Subtasks completed ${doneRuns} / ${taskRuns.length}`
),
value: doneRuns,
max: taskRuns.length,
stageLabel: stageLabel ?? taskViews.find((item) => item.isRoot)?.stageLabel ?? null,
@ -423,7 +434,7 @@ function deriveProgress(
return {
mode: 'status',
label: '等待任务数据',
label: pickAppText(locale, '等待任务数据', 'Waiting for task data'),
value: null,
max: null,
stageLabel,
@ -433,6 +444,7 @@ function deriveProgress(
function buildAlerts(
taskViews: OfficeTaskView[],
now: number,
locale: AppLocale,
): OfficeAlertView[] {
const alerts: OfficeAlertView[] = [];
@ -441,7 +453,7 @@ function buildAlerts(
alerts.push({
id: `error:${task.runId}`,
level: 'error',
title: `${task.actorName} 执行失败`,
title: pickAppText(locale, `${task.actorName} 执行失败`, `${task.actorName} failed`),
description: task.errorText,
runId: task.runId,
actorId: task.actorId,
@ -451,8 +463,8 @@ function buildAlerts(
alerts.push({
id: `blocked:${task.runId}`,
level: 'warn',
title: `${task.actorName} 长时间等待`,
description: '该任务长时间无更新,可能存在阻塞。',
title: pickAppText(locale, `${task.actorName} 长时间等待`, `${task.actorName} has been waiting for a while`),
description: pickAppText(locale, '该任务长时间无更新,可能存在阻塞。', 'This task has not updated for a while and may be blocked.'),
runId: task.runId,
actorId: task.actorId,
createdAt: task.updatedAt,
@ -463,8 +475,8 @@ function buildAlerts(
alerts.push({
id: `stale:${task.runId}`,
level: 'warn',
title: `${task.actorName} 等待时间偏长`,
description: '该任务仍处于等待态,建议查看详情确认依赖是否卡住。',
title: pickAppText(locale, `${task.actorName} 等待时间偏长`, `${task.actorName} has been waiting longer than expected`),
description: pickAppText(locale, '该任务仍处于等待态,建议查看详情确认依赖是否卡住。', 'This task is still waiting. Check the details to confirm whether a dependency is stuck.'),
runId: task.runId,
actorId: task.actorId,
createdAt: task.updatedAt,
@ -476,18 +488,18 @@ function buildAlerts(
return alerts.sort((a, b) => compareIsoDesc(a.createdAt, b.createdAt));
}
function buildZones(members: OfficeMemberView[], tasks: OfficeTaskView[]): OfficeZoneView[] {
function buildZones(members: OfficeMemberView[], tasks: OfficeTaskView[], locale: AppLocale): OfficeZoneView[] {
const ids: OfficeZoneId[] = ['reception', 'workspace', 'collab', 'research', 'alert', 'done'];
return ids.map((id) => ({
id,
label: zoneLabel(id),
label: zoneLabel(id, locale),
memberIds: members.filter((member) => member.zoneId === id).map((member) => member.memberId),
taskIds: tasks.filter((task) => mapZoneId(task.status, task.actorType) === id).map((task) => task.taskId),
tone: zoneTone(id),
}));
}
function buildAssignments(taskRuns: ProcessRun[], childrenMap: Map<string, ProcessRun[]>): OfficeAssignmentView[] {
function buildAssignments(taskRuns: ProcessRun[], childrenMap: Map<string, ProcessRun[]>, locale: AppLocale): OfficeAssignmentView[] {
return taskRuns
.filter((run) => (childrenMap.get(run.run_id) ?? []).length > 0)
.map((run) => {
@ -497,7 +509,7 @@ function buildAssignments(taskRuns: ProcessRun[], childrenMap: Map<string, Proce
ownerActorName: run.actor_name,
assigneeRunIds: children.map((item) => item.run_id),
assigneeActorNames: children.map((item) => item.actor_name),
label: `${run.actor_name} 分派了 ${children.length} 个子任务`,
label: pickAppText(locale, `${run.actor_name} 分派了 ${children.length} 个子任务`, `${run.actor_name} assigned ${children.length} subtasks`),
};
});
}
@ -506,19 +518,20 @@ export function isOfficeTaskTerminal(status: OfficeTaskStatus): boolean {
return TERMINAL_STATUSES.has(status);
}
export function officeTaskStatusLabel(status: OfficeTaskStatus): string {
if (status === 'queued') return '排队中';
if (status === 'running') return '进行中';
if (status === 'waiting') return '等待中';
if (status === 'blocked') return '阻塞';
if (status === 'done') return '已完成';
if (status === 'error') return '失败';
return '已取消';
export function officeTaskStatusLabel(status: OfficeTaskStatus, locale: AppLocale = getCurrentAppLocale()): string {
if (status === 'queued') return pickAppText(locale, '排队中', 'Queued');
if (status === 'running') return pickAppText(locale, '进行中', 'In Progress');
if (status === 'waiting') return pickAppText(locale, '等待中', 'Waiting');
if (status === 'blocked') return pickAppText(locale, '阻塞', 'Blocked');
if (status === 'done') return pickAppText(locale, '已完成', 'Done');
if (status === 'error') return pickAppText(locale, '失败', 'Error');
return pickAppText(locale, '已取消', 'Cancelled');
}
export function buildOfficeView(
taskId: string,
input: BuildOfficeInput,
locale: AppLocale = getCurrentAppLocale(),
): OfficeView | null {
const { sessions, processRuns, processEvents, processArtifacts } = input;
const runById = new Map(processRuns.map((run) => [run.run_id, run]));
@ -539,7 +552,7 @@ export function buildOfficeView(
const runEvents = eventsByRun.get(run.run_id) ?? [];
const updatedAt = getRunUpdatedAt(run, eventsByRun, artifactsByRun);
const status = deriveRunStatus(run, updatedAt, now);
const stageLabel = deriveStageLabel(run, runEvents, status);
const stageLabel = deriveStageLabel(run, runEvents, status, locale);
const childTaskIds = (childrenMap.get(run.run_id) ?? [])
.filter((child) => taskRunIds.has(child.run_id))
.map((child) => child.run_id);
@ -560,7 +573,7 @@ export function buildOfficeView(
finishedAt: run.finished_at ?? null,
childTaskIds,
artifactCount: (artifactsByRun.get(run.run_id) ?? []).length,
errorText: deriveErrorText(run, runEvents),
errorText: deriveErrorText(run, runEvents, locale),
isRoot: run.run_id === rootRun.run_id,
};
})
@ -620,9 +633,9 @@ export function buildOfficeView(
rootRun.started_at,
]) ?? rootRun.started_at;
const derivedRootStatus = deriveRunStatus(rootRun, updatedAt, now);
const alerts = buildAlerts(taskViews, now);
const progress = deriveProgress(rootRun, taskRuns, taskViews);
const sourceSessionLabel = getSessionLabel(sessions, sessionId);
const alerts = buildAlerts(taskViews, now, locale);
const progress = deriveProgress(rootRun, taskRuns, taskViews, locale);
const sourceSessionLabel = getSessionLabel(sessions, sessionId, locale);
const createdAt = rootRun.started_at;
const finishedAt = rootRun.finished_at ?? null;
const durationStart = toTime(createdAt);
@ -636,7 +649,7 @@ export function buildOfficeView(
officeId: rootRun.run_id,
taskId: rootRun.run_id,
sessionId,
title: rootRun.title || `Task ${rootRun.run_id.slice(0, 8)}`,
title: rootRun.title || pickAppText(locale, `任务 ${rootRun.run_id.slice(0, 8)}`, `Task ${rootRun.run_id.slice(0, 8)}`),
status: derivedRootStatus,
createdAt,
updatedAt,
@ -645,7 +658,7 @@ export function buildOfficeView(
sourceSessionLabel,
rootRunId: rootRun.run_id,
rootActorName: rootRun.actor_name,
currentStageLabel: deriveStageLabel(rootRun, eventsByRun.get(rootRun.run_id) ?? [], derivedRootStatus),
currentStageLabel: deriveStageLabel(rootRun, eventsByRun.get(rootRun.run_id) ?? [], derivedRootStatus, locale),
progress,
stats: {
totalRuns: taskRuns.length,
@ -657,25 +670,25 @@ export function buildOfficeView(
artifactCount: taskArtifacts.length,
},
alerts,
zones: buildZones(members, taskViews),
zones: buildZones(members, taskViews, locale),
members,
tasks: taskViews,
assignments: buildAssignments(taskRuns, childrenMap),
assignments: buildAssignments(taskRuns, childrenMap, locale),
detailRunIds: taskViews.map((task) => task.runId),
};
}
export function buildOfficeTaskList(
input: BuildOfficeInput & { sessionId?: string | null },
locale: AppLocale = getCurrentAppLocale(),
): OfficeTaskListItem[] {
const rootRuns = findRootRuns(input.processRuns);
const filteredRoots = input.sessionId
? rootRuns.filter((run) => run.session_id === input.sessionId)
: rootRuns;
return filteredRoots
.map((rootRun) => buildOfficeView(rootRun.run_id, input))
const offices = rootRuns
.map((rootRun) => buildOfficeView(rootRun.run_id, input, locale))
.filter((office): office is OfficeView => office !== null)
.filter((office) => !input.sessionId || office.sessionId === input.sessionId);
return offices
.map((office) => ({
officeId: office.officeId,
taskId: office.taskId,