refactor(beaver): 移除Hermes相关引用和迁移代码,完善Beaver后端主线实现

移除了所有Hermes相关的命名引用,包括:
- 从.gitignore中清理相关构建缓存文件
- 将README中的beaver-home路径配置更新
- 完善backend/README.md文档说明Beaver后端主线实现
- 移除Hermes风格的相关注释和兼容性代码
- 清理nanobot环境变量兼容性处理
- 删除技能迁移和服务迁移相关功能代码
- 更新测试用例中相关命名和函数名

BREAKING CHANGE: 移除了Hermes迁移相关API和CLI命令,不再支持nanobot环境变量兼容性
This commit is contained in:
2026-05-14 17:20:32 +08:00
parent b59968167e
commit 3b0af173cc
57 changed files with 245 additions and 4109 deletions

View File

@ -149,7 +149,6 @@ export default function NotificationDetailPage() {
processArtifacts={[]}
selectedRunId={null}
onSelectRun={() => {}}
onCancelRun={() => {}}
onFeedback={() => {}}
/>
</div>

View File

@ -7,7 +7,6 @@ import { Brain, Plus, Send, Trash2, X } from 'lucide-react';
import { ChatWorkbench } from '@/components/chat-workbench/ChatWorkbench';
import { ScrollArea } from '@/components/ui/scroll-area';
import {
cancelDelegation,
archiveSession,
createSession,
getActiveTask,
@ -464,18 +463,6 @@ export default function ChatPage() {
setSessionId(key);
};
const handleCancelRun = useCallback(async (runId: string) => {
try {
await cancelDelegation(runId);
} catch (err: any) {
addMessage({
role: 'assistant',
content: pickAppText(locale, `取消任务 ${runId} 失败:${err.message || '未知错误'}`, `Failed to cancel task ${runId}: ${err.message || 'Unknown error'}`),
timestamp: new Date().toISOString(),
});
}
}, [addMessage, locale]);
const removePendingFile = useCallback((file: File) => {
setPendingFiles((prev) => prev.filter((item) => item.file !== file));
}, []);
@ -566,7 +553,6 @@ export default function ChatPage() {
processArtifacts={sessionProcessArtifacts}
selectedRunId={selectedSessionRunId}
onSelectRun={(runId) => setSelectedRunId(selectedSessionRunId === runId ? null : runId)}
onCancelRun={handleCancelRun}
onFeedback={handleFeedback}
/>
</div>

View File

@ -40,7 +40,6 @@ import {
listSkillCandidates,
listSkillDrafts,
listSkills,
migrateSkills,
publishSkillDraft,
regenerateSkillDraft,
rejectSkillDraft,
@ -207,15 +206,6 @@ export default function SkillsPage() {
<Upload className="mr-2 h-4 w-4" />
{t('上传技能', 'Upload skill')}
</Button>
<Button
onClick={() => void runAction('migrate-skills', () => migrateSkills())}
variant="outline"
size="sm"
disabled={Boolean(actionId)}
>
{actionId === 'migrate-skills' ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Rocket className="mr-2 h-4 w-4" />}
{t('迁移旧技能', 'Migrate legacy skills')}
</Button>
</div>
</div>

View File

@ -15,17 +15,7 @@ import {
Settings2,
ScrollText,
} from 'lucide-react';
import { getStatus, restartSystem, updateProviderConfig } from '@/lib/api';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { getStatus, updateProviderConfig } from '@/lib/api';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
@ -57,9 +47,6 @@ export default function StatusPage() {
const [status, setStatus] = useState<SystemStatus | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [restartDialogOpen, setRestartDialogOpen] = useState(false);
const [restarting, setRestarting] = useState(false);
const [restartError, setRestartError] = useState<string | null>(null);
const [selectedProvider, setSelectedProvider] = useState<ProviderStatus | null>(null);
const [providerForm, setProviderForm] = useState<ProviderFormState>(() => ({
enabled: false,
@ -88,36 +75,6 @@ export default function StatusPage() {
loadStatus();
}, []);
useEffect(() => {
if (!restarting) {
return;
}
const intervalId = window.setInterval(async () => {
try {
await getStatus();
window.location.reload();
} catch {
// Ignore failures until the container is back.
}
}, 3000);
return () => {
window.clearInterval(intervalId);
};
}, [restarting]);
const handleRestart = async () => {
setRestartError(null);
try {
await restartSystem();
setRestartDialogOpen(false);
setRestarting(true);
} catch (err: any) {
setRestartError(err.message || pickAppText(locale, '重启失败', 'Restart failed'));
}
};
const openProviderDialog = (provider: ProviderStatus) => {
setSelectedProvider(provider);
setProviderError(null);
@ -204,7 +161,7 @@ export default function StatusPage() {
)}
</p>
</div>
<Button onClick={loadStatus} variant="outline" size="sm" disabled={restarting}>
<Button onClick={loadStatus} variant="outline" size="sm">
<RefreshCw className="w-4 h-4 mr-2" />
{pickAppText(locale, '刷新', 'Refresh')}
</Button>
@ -223,13 +180,8 @@ export default function StatusPage() {
<div className="space-y-1">
<p className="text-sm font-medium">{pickAppText(locale, '运行与调试', 'Runtime and debugging')}</p>
<p className="text-sm text-muted-foreground">
{restarting
? pickAppText(locale, '正在重启当前 docker服务恢复后页面会自动刷新。', 'Restarting the current Docker container. The page will refresh automatically once the service is back.')
: pickAppText(locale, '查看每次对话的运行日志,或重启当前 docker 容器。重启完成后需要重新登录。', 'Inspect per-chat runtime logs or restart the current Docker container. You will need to sign in again afterwards.')}
{pickAppText(locale, '查看每次对话的运行日志和当前实例运行状态。', 'Inspect per-chat runtime logs and current instance status.')}
</p>
{restartError ? (
<p className="text-sm text-destructive">{restartError}</p>
) : null}
</div>
<div className="flex flex-wrap justify-start gap-2 lg:justify-end">
<Button asChild variant="outline">
@ -238,34 +190,6 @@ export default function StatusPage() {
{pickAppText(locale, '运行日志', 'Runtime Logs')}
</Link>
</Button>
<AlertDialog open={restartDialogOpen} onOpenChange={setRestartDialogOpen}>
<Button
variant="destructive"
onClick={() => setRestartDialogOpen(true)}
disabled={restarting}
>
{restarting ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<RefreshCw className="w-4 h-4 mr-2" />
)}
Restart
</Button>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{pickAppText(locale, '确认重启当前实例?', 'Restart the current instance?')}</AlertDialogTitle>
<AlertDialogDescription>
{pickAppText(locale, '这会重启当前 docker 容器,页面会短暂不可用。由于当前登录态保存在内存里,重启完成后需要重新登录。', 'This restarts the current Docker container and the page will be temporarily unavailable. Because the current sign-in state is stored in memory, you will need to sign in again after the restart.')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={restarting}>{pickAppText(locale, '取消', 'Cancel')}</AlertDialogCancel>
<AlertDialogAction onClick={handleRestart} disabled={restarting}>
{restarting ? pickAppText(locale, '重启中...', 'Restarting...') : pickAppText(locale, '确认重启', 'Confirm restart')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
<div className="mt-5 grid gap-3 border-t pt-5 md:grid-cols-2">

View File

@ -3,21 +3,21 @@
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import React, { useMemo, useState } from 'react';
import { AlertCircle, ArrowLeft, Bot, CheckCircle2, Download, FileText, HelpCircle, MessageSquare, RefreshCw, RotateCcw, Trash2, User, XCircle } from 'lucide-react';
import { AlertCircle, ArrowLeft, Bot, CheckCircle2, Download, FileText, HelpCircle, MessageSquare, RefreshCw, Trash2, User, XCircle } from 'lucide-react';
import { OfficeStatusBadge, formatOfficeDuration, formatOfficeTime, progressPercent } from '@/components/office/OfficeShared';
import { TaskRuntimeStatusBadge, formatTaskRuntimeDuration, formatTaskRuntimeTime, progressPercent } from '@/components/task-runtime/TaskRuntimeShared';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Textarea } from '@/components/ui/textarea';
import { cancelDelegation, deleteBackendTask, getBackendTask, getFileUrl, retryDelegation, submitChatFeedback } from '@/lib/api';
import { deleteBackendTask, getBackendTask, getFileUrl, submitChatFeedback } from '@/lib/api';
import { pickAppText } from '@/lib/i18n/core';
import { useAppI18n } from '@/lib/i18n/provider';
import { buildOfficeView, isOfficeTaskTerminal, type OfficeTaskView } from '@/lib/office';
import { buildTaskRuntimeView, type TaskRuntimeNodeView } from '@/lib/task-runtime';
import { useChatStore } from '@/lib/store';
import type { BackendTask, BackendTaskRun, ProcessArtifact, ProcessEvent } from '@/types';
function taskVisibleStatus(task: OfficeTaskView, locale: 'zh-CN' | 'en-US') {
function taskVisibleStatus(task: TaskRuntimeNodeView, locale: 'zh-CN' | 'en-US') {
if (task.status === 'error') return pickAppText(locale, '任务失败', 'Task failed');
if (task.status === 'cancelled') return pickAppText(locale, '已取消', 'Cancelled');
return task.stageLabel || task.status;
@ -46,7 +46,7 @@ export default function TaskDetailPage() {
const updateMessageFeedback = useChatStore((state) => state.updateMessageFeedback);
const task = useMemo(
() => buildOfficeView(taskId, { sessions, processRuns, processEvents, processArtifacts }, locale),
() => buildTaskRuntimeView(taskId, { sessions, processRuns, processEvents, processArtifacts }, locale),
[locale, processArtifacts, processEvents, processRuns, sessions, taskId]
);
const [backendTask, setBackendTask] = useState<BackendTask | null>(null);
@ -105,7 +105,7 @@ export default function TaskDetailPage() {
return map;
}, [artifacts]);
const phaseGroups = useMemo(() => {
const groups = new Map<string, OfficeTaskView[]>();
const groups = new Map<string, TaskRuntimeNodeView[]>();
for (const item of task?.tasks ?? []) {
const label = item.stageLabel || taskVisibleStatus(item, locale);
groups.set(label, [...(groups.get(label) ?? []), item]);
@ -180,7 +180,7 @@ export default function TaskDetailPage() {
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span>{pickAppText(locale, '来源会话', 'Session')}: {backendTask.session_id}</span>
<span>{pickAppText(locale, '创建者', 'Creator')}: {backendTask.creator}</span>
<span>{pickAppText(locale, '更新', 'Updated')}: {formatOfficeTime(backendTask.updated_at, locale)}</span>
<span>{pickAppText(locale, '更新', 'Updated')}: {formatTaskRuntimeTime(backendTask.updated_at, locale)}</span>
</div>
</CardContent>
</Card>
@ -242,7 +242,7 @@ export default function TaskDetailPage() {
<div key={index} className="rounded-md border border-border p-3">
<div className="font-medium">{humanFeedback(String(item.feedback_type || ''), locale)}</div>
{item.comment ? <p className="mt-1 text-muted-foreground">{String(item.comment)}</p> : null}
{item.created_at ? <p className="mt-1 text-xs text-muted-foreground">{formatOfficeTime(String(item.created_at), locale)}</p> : null}
{item.created_at ? <p className="mt-1 text-xs text-muted-foreground">{formatTaskRuntimeTime(String(item.created_at), locale)}</p> : null}
</div>
))
)}
@ -295,25 +295,6 @@ export default function TaskDetailPage() {
</Link>
</Button>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
variant="outline"
size="sm"
disabled={Boolean(actionBusy) || isOfficeTaskTerminal(task.status)}
onClick={() => void runAction('cancel', () => cancelDelegation(task.rootRunId))}
>
<XCircle className="mr-2 h-4 w-4" />
{pickAppText(locale, '取消任务', 'Cancel task')}
</Button>
<Button
size="sm"
disabled={Boolean(actionBusy) || !isOfficeTaskTerminal(task.status)}
onClick={() => void runAction('retry', () => retryDelegation(task.rootRunId))}
>
<RotateCcw className="mr-2 h-4 w-4" />
{pickAppText(locale, '重试任务', 'Retry task')}
</Button>
</div>
</div>
<Card>
@ -322,20 +303,20 @@ export default function TaskDetailPage() {
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-3">
<h1 className="truncate text-2xl font-semibold">{task.title}</h1>
<OfficeStatusBadge status={task.status} />
<TaskRuntimeStatusBadge status={task.status} />
</div>
<div className="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span>{pickAppText(locale, '来源会话', 'Session')}: {task.sourceSessionLabel}</span>
<span>{pickAppText(locale, '主 Agent', 'Lead agent')}: {task.rootActorName}</span>
<span>{pickAppText(locale, '开始', 'Started')}: {formatOfficeTime(task.createdAt, locale)}</span>
<span>{pickAppText(locale, '耗时', 'Duration')}: {formatOfficeDuration(task.durationMs, locale)}</span>
<span>{pickAppText(locale, '开始', 'Started')}: {formatTaskRuntimeTime(task.createdAt, locale)}</span>
<span>{pickAppText(locale, '耗时', 'Duration')}: {formatTaskRuntimeDuration(task.durationMs, locale)}</span>
</div>
</div>
<div className="grid w-full gap-3 sm:grid-cols-4 lg:w-[520px]">
<Metric label={pickAppText(locale, '节点', 'Nodes')} value={String(task.stats.totalRuns)} />
<Metric label={pickAppText(locale, '活跃', 'Active')} value={String(task.stats.activeRuns)} />
<Metric label={pickAppText(locale, '产物', 'Artifacts')} value={String(task.stats.artifactCount)} />
<Metric label={pickAppText(locale, '异常', 'Alerts')} value={String(task.alerts.length)} />
<Metric label={pickAppText(locale, '异常', 'Alerts')} value={String(task.stats.alertCount)} />
</div>
</div>
<div className="mt-5 space-y-2">
@ -398,7 +379,7 @@ export default function TaskDetailPage() {
<div className="truncate font-medium">{node.title}</div>
<div className="mt-1 text-xs text-muted-foreground">{node.actorName}</div>
</div>
<OfficeStatusBadge status={node.status} />
<TaskRuntimeStatusBadge status={node.status} />
</div>
<div className="mt-3 text-sm text-muted-foreground">
{node.summary || taskVisibleStatus(node, locale)}
@ -426,13 +407,13 @@ export default function TaskDetailPage() {
<div className="font-medium">{selectedNode.title}</div>
<div className="mt-1 text-xs text-muted-foreground">{selectedNode.runId}</div>
</div>
<OfficeStatusBadge status={selectedNode.status} />
<TaskRuntimeStatusBadge status={selectedNode.status} />
<p className="text-sm text-muted-foreground">{selectedNode.summary || '-'}</p>
<div className="space-y-2">
{(eventsByRun.get(selectedNode.runId) ?? []).slice(-5).map((event) => (
<div key={event.event_id} className="rounded-md border border-border bg-muted/30 p-2 text-xs">
<div className="font-medium">{event.kind}</div>
<div className="mt-1 text-muted-foreground">{event.text || formatOfficeTime(event.created_at, locale)}</div>
<div className="mt-1 text-muted-foreground">{event.text || formatTaskRuntimeTime(event.created_at, locale)}</div>
</div>
))}
</div>
@ -548,7 +529,7 @@ function BackendRunConversation({ run, index }: { run: BackendTaskRun; index: nu
<div>
<div className="font-medium">{run.title || pickAppText(locale, `Agent ${index + 1}`, `Agent ${index + 1}`)}</div>
<div className="mt-1 text-xs text-muted-foreground">
{run.started_at ? formatOfficeTime(run.started_at, locale) : pickAppText(locale, '时间未知', 'Unknown time')}
{run.started_at ? formatTaskRuntimeTime(run.started_at, locale) : pickAppText(locale, '时间未知', 'Unknown time')}
{run.finish_reason ? ` · ${humanFinishReason(run.finish_reason, locale)}` : ''}
</div>
</div>
@ -573,7 +554,7 @@ function BackendRunConversation({ run, index }: { run: BackendTaskRun; index: nu
<div className="min-w-0 flex-1">
<div className="mb-1 flex items-center gap-2 text-xs text-muted-foreground">
<span>{isAssistant ? run.title || pickAppText(locale, 'Agent 回复', 'Agent reply') : isTool ? message.tool_name || pickAppText(locale, '工具结果', 'Tool result') : pickAppText(locale, '用户要求', 'User request')}</span>
{message.created_at ? <span>{formatOfficeTime(message.created_at, locale)}</span> : null}
{message.created_at ? <span>{formatTaskRuntimeTime(message.created_at, locale)}</span> : null}
</div>
<div className="whitespace-pre-wrap rounded-md border border-border bg-muted/20 px-3 py-2 text-sm leading-6">
{message.content}

View File

@ -5,7 +5,7 @@ import { useSearchParams } from 'next/navigation';
import React, { useEffect, useMemo, useState } from 'react';
import { AlertCircle, ArrowRight, Clock3, FolderDown, ListTodo, Loader2, Play, Plus, RefreshCw, Trash2, X } from 'lucide-react';
import { formatOfficeTime } from '@/components/office/OfficeShared';
import { formatTaskRuntimeTime } from '@/components/task-runtime/TaskRuntimeShared';
import { TaskManagementTabs } from '@/components/task-management/TaskManagementTabs';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@ -151,7 +151,7 @@ function OrdinaryTasks() {
</TableCell>
<TableCell className="text-sm text-muted-foreground">{task.run_ids.length}</TableCell>
<TableCell className="text-sm text-muted-foreground">{task.skill_names.length}</TableCell>
<TableCell className="text-xs text-muted-foreground">{formatOfficeTime(task.updated_at, locale)}</TableCell>
<TableCell className="text-xs text-muted-foreground">{formatTaskRuntimeTime(task.updated_at, locale)}</TableCell>
<TableCell>
<div className="flex items-center gap-1">
<Button asChild size="sm" variant="outline">

View File

@ -8,7 +8,7 @@ import { pickAppText } from '@/lib/i18n/core';
import { useAppI18n } from '@/lib/i18n/provider';
import { useChatStore } from '@/lib/store';
const HANDOFF_STATE_KEY = 'nanobot_handoff_state';
const HANDOFF_STATE_KEY = 'beaver_handoff_state';
type HandoffState = {
code?: string;