```
feat(agent): 添加对持久化子智能体的支持并增强委派管理 添加了持久化子智能体的完整生命周期管理功能,包括创建、更新、删除和查询API接口。 新增了子智能体的JSON-RPC通信协议支持,实现了远程调用和任务管理功能。 同时增强了委派管理器的功能: - 添加了对本地委派、插件委派和本地回退的开关控制 - 实现了持久化子智能体任务的自动检测和本地执行保护 - 增加了对不同委派类型的权限验证机制 修改了智能体注册表以支持插件智能体的条件性包含,并更新了工具注册逻辑以支持可选工具。 BREAKING CHANGE: 委派管理器的构造函数签名已更改,添加了新的控制参数。 ```
This commit is contained in:
268
app-instance/frontend/app/(app)/office/page.tsx
Normal file
268
app-instance/frontend/app/(app)/office/page.tsx
Normal file
@ -0,0 +1,268 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import {
|
||||
Activity,
|
||||
ArrowRight,
|
||||
Clock3,
|
||||
FolderKanban,
|
||||
Loader2,
|
||||
Sparkles,
|
||||
Users,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { OfficeStatusBadge, formatOfficeTime, progressPercent } from '@/components/office/OfficeShared';
|
||||
import { TaskManagementTabs } from '@/components/task-management/TaskManagementTabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { buildOfficeTaskList, isOfficeTaskTerminal } from '@/lib/office';
|
||||
import { useChatStore } from '@/lib/store';
|
||||
|
||||
function TaskCard({
|
||||
taskId,
|
||||
title,
|
||||
sessionLabel,
|
||||
rootActorName,
|
||||
status,
|
||||
updatedAt,
|
||||
memberCount,
|
||||
activeRuns,
|
||||
artifactCount,
|
||||
errorCount,
|
||||
currentStageLabel,
|
||||
progressLabel,
|
||||
progressValue,
|
||||
}: {
|
||||
taskId: string;
|
||||
title: string;
|
||||
sessionLabel: string;
|
||||
rootActorName: string;
|
||||
status: Parameters<typeof OfficeStatusBadge>[0]['status'];
|
||||
updatedAt: string;
|
||||
memberCount: number;
|
||||
activeRuns: number;
|
||||
artifactCount: number;
|
||||
errorCount: number;
|
||||
currentStageLabel: string | null;
|
||||
progressLabel: string;
|
||||
progressValue: number;
|
||||
}) {
|
||||
return (
|
||||
<Card className="border-border/80 transition-colors hover:border-primary/30">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<CardTitle className="truncate text-lg">{title}</CardTitle>
|
||||
<CardDescription className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
||||
<span>会话: {sessionLabel}</span>
|
||||
<span>主 Agent: {rootActorName}</span>
|
||||
<span>更新于 {formatOfficeTime(updatedAt)}</span>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<OfficeStatusBadge status={status} />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid gap-3 sm:grid-cols-4">
|
||||
<Metric icon={Users} label="成员" value={String(memberCount)} />
|
||||
<Metric icon={Activity} label="活跃" value={String(activeRuns)} />
|
||||
<Metric icon={FolderKanban} label="产物" value={String(artifactCount)} />
|
||||
<Metric icon={Sparkles} label="异常" value={String(errorCount)} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
<span className="truncate text-muted-foreground">{progressLabel}</span>
|
||||
{currentStageLabel ? <span className="truncate font-medium">{currentStageLabel}</span> : null}
|
||||
</div>
|
||||
<div className="h-2.5 overflow-hidden rounded-full bg-secondary">
|
||||
<div
|
||||
className="h-full rounded-full bg-primary transition-all"
|
||||
style={{ width: `${progressValue}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button asChild size="sm">
|
||||
<Link href={`/office/${encodeURIComponent(taskId)}`}>
|
||||
进入办公室
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function Metric({
|
||||
icon: Icon,
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
label: string;
|
||||
value: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-xl border border-border/60 bg-muted/30 px-3 py-3">
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Icon className="h-3.5 w-3.5" />
|
||||
<span>{label}</span>
|
||||
</div>
|
||||
<div className="mt-2 text-lg font-semibold">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function OfficeListPage() {
|
||||
const sessionId = useChatStore((state) => state.sessionId);
|
||||
const sessions = useChatStore((state) => state.sessions);
|
||||
const processRuns = useChatStore((state) => state.processRuns);
|
||||
const processEvents = useChatStore((state) => state.processEvents);
|
||||
const processArtifacts = useChatStore((state) => state.processArtifacts);
|
||||
const wsStatus = useChatStore((state) => state.wsStatus);
|
||||
|
||||
const tasks = React.useMemo(
|
||||
() => buildOfficeTaskList({
|
||||
sessionId,
|
||||
sessions,
|
||||
processRuns,
|
||||
processEvents,
|
||||
processArtifacts,
|
||||
}),
|
||||
[processArtifacts, processEvents, processRuns, sessionId, sessions]
|
||||
);
|
||||
|
||||
const activeTasks = tasks.filter((task) => !isOfficeTaskTerminal(task.status));
|
||||
const recentTasks = tasks.filter((task) => isOfficeTaskTerminal(task.status));
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl space-y-6 p-6">
|
||||
<TaskManagementTabs />
|
||||
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold tracking-tight">Office</h1>
|
||||
<p className="mt-2 max-w-3xl text-sm text-muted-foreground">
|
||||
基于当前会话的真实运行数据,展示主 Agent 与子 Agent 的任务现场。任务结束后会从活跃现场移除,但保留回看入口。
|
||||
</p>
|
||||
</div>
|
||||
<Card className="min-w-[280px] border-border/70">
|
||||
<CardContent className="flex items-center justify-between gap-4 p-4">
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">当前会话</div>
|
||||
<div className="mt-1 font-medium">{sessionId}</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-xs text-muted-foreground">连接状态</div>
|
||||
<div className="mt-1 font-medium">{wsStatus === 'connected' ? '已连接' : wsStatus}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{wsStatus === 'connecting' && tasks.length === 0 ? (
|
||||
<div className="flex items-center gap-3 rounded-xl border border-dashed border-border px-4 py-6 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
正在等待运行时数据...
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tasks.length === 0 ? (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Clock3 className="h-10 w-10 text-muted-foreground/50" />
|
||||
<h2 className="mt-4 text-xl font-semibold">当前没有可展示的任务现场</h2>
|
||||
<p className="mt-2 max-w-xl text-sm text-muted-foreground">
|
||||
先回到对话页发起一次主 Agent 任务。开始执行后,这里会出现活跃的 office 卡片。
|
||||
</p>
|
||||
<Button asChild className="mt-6">
|
||||
<Link href="/">回到对话</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<>
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">活跃 Office</h2>
|
||||
<p className="text-sm text-muted-foreground">正在运行中的任务现场会优先显示。</p>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">{activeTasks.length} 个任务</div>
|
||||
</div>
|
||||
{activeTasks.length === 0 ? (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="py-10 text-center text-sm text-muted-foreground">
|
||||
当前没有活跃任务,下面可以查看最近结束的任务。
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{activeTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={task.taskId}
|
||||
taskId={task.taskId}
|
||||
title={task.title}
|
||||
sessionLabel={task.sessionLabel}
|
||||
rootActorName={task.rootActorName}
|
||||
status={task.status}
|
||||
updatedAt={task.updatedAt}
|
||||
memberCount={task.memberCount}
|
||||
activeRuns={task.activeRuns}
|
||||
artifactCount={task.artifactCount}
|
||||
errorCount={task.errorCount}
|
||||
currentStageLabel={task.currentStageLabel}
|
||||
progressLabel={task.progress.label}
|
||||
progressValue={progressPercent(task.progress.value, task.progress.max)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">最近结束</h2>
|
||||
<p className="text-sm text-muted-foreground">已完成、失败或取消的任务仍保留回看入口。</p>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">{recentTasks.length} 个任务</div>
|
||||
</div>
|
||||
{recentTasks.length === 0 ? (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="py-10 text-center text-sm text-muted-foreground">
|
||||
还没有历史任务。
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{recentTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={task.taskId}
|
||||
taskId={task.taskId}
|
||||
title={task.title}
|
||||
sessionLabel={task.sessionLabel}
|
||||
rootActorName={task.rootActorName}
|
||||
status={task.status}
|
||||
updatedAt={task.updatedAt}
|
||||
memberCount={task.memberCount}
|
||||
activeRuns={task.activeRuns}
|
||||
artifactCount={task.artifactCount}
|
||||
errorCount={task.errorCount}
|
||||
currentStageLabel={task.currentStageLabel}
|
||||
progressLabel={task.progress.label}
|
||||
progressValue={progressPercent(task.progress.value, task.progress.max)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user