import { type AgentCheckConfig, type AgentRun, CAPABILITY_CATALOG, type KnownCapabilityId, type ScheduledJob, } from "@umt/contracts"; import { History, LoaderCircle, Pencil, Play, RotateCcw, SkipForward, Trash2 } from "lucide-react"; import { type ReactNode, useEffect, useRef } from "react"; import { Link } from "react-router"; import { toast } from "sonner"; import { useDeleteSchedule } from "~/api/schedules.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Switch } from "~/components/ui/switch.tsx"; import { errorMessage } from "~/lib/api.ts"; import { agentRunLabel, formatRelative } from "~/lib/labels.ts"; import { cn } from "~/lib/utils.ts"; import { NextRun, useJobActions } from "./auto-task-list.tsx"; import { IntervalSelect } from "./interval-select.tsx"; import { CHECK_TRIGGER_LABEL, formatUsd, RUN_STATUS } from "./labels.ts"; // 计划页「Agent 巡检」:到点按目标模板发起一次 Agent 会话(kind=agent_check),一个巡检一张卡片。 /** 卡片上能力白名单最多列几个,其余折成"+N" */ const MAX_CAPS = 6; /** 会话摘要是 Markdown:列表里只取纯文字 */ const plainSummary = (t: string) => t .replace(/[#*`>_]+/g, " ") .replace(/\s+/g, " ") .trim(); const capTitle = (id: string) => CAPABILITY_CATALOG[id as KnownCapabilityId]?.title ?? id; /** 默认上游(巡检没有单独的上游设置,跟随 Agent 设置里的默认上游) */ export type DefaultUpstream = { name: string; model: string | null } | null; function Field({ label, children }: { label: string; children: ReactNode }) { return ( <>
{label}
{children}
); } export function AgentCheckCard({ job, now, runs, upstream, highlight, onEdit, onHistory, }: { job: ScheduledJob; now: number; /** 该巡检最近的会话(新 → 旧) */ runs: AgentRun[]; upstream: DefaultUpstream; highlight: boolean; onEdit: (job: ScheduledJob) => void; onHistory: (job: ScheduledJob) => void; }) { const a = useJobActions(job); const del = useDeleteSchedule(); const ref = useRef(null); useEffect(() => { if (highlight) ref.current?.scrollIntoView({ block: "center" }); }, [highlight]); const c = job.config as Partial; const budget = c.budget ?? {}; const allow = c.capabilityAllowlist ?? []; const last = job.lastStatus ? RUN_STATUS[job.lastStatus] : null; const latest = runs[0] ?? null; const costs = runs.map((r) => r.usage.costUsd).filter((v): v is number => typeof v === "number"); const avg = costs.length ? costs.reduce((s, v) => s + v, 0) / costs.length : null; const budgetParts = [ budget.maxToolCalls ? `${budget.maxToolCalls} 次调用` : null, budget.maxDurationSec ? `${budget.maxDurationSec} 秒` : null, budget.maxTokens ? `${budget.maxTokens.toLocaleString("zh-CN")} tokens` : null, budget.maxCostUsd ? `${formatUsd(budget.maxCostUsd)} 上限` : null, ].filter(Boolean); return (

{job.name}

{job.description ? (

{job.description}

) : null}

{c.goalTemplate ?? "—"}

{allow.length ? ( {allow.slice(0, MAX_CAPS).map((id) => ( {capTitle(id)} ))} {allow.length > MAX_CAPS ? ( +{allow.length - MAX_CAPS} ) : null} ) : ( "全部只读能力" )} {c.allowRecover ? · 允许授权内恢复 : null} {budgetParts.length ? budgetParts.join(" · ") : "默认"} {latest?.model ? ( {latest.provider ? `${latest.provider} · ` : ""} {latest.model} ) : upstream ? ( 默认上游 {upstream.name} {upstream.model ? ` · ${upstream.model}` : ""} ) : ( 未配置 Agent 上游 )} {CHECK_TRIGGER_LABEL[c.triggerCondition ?? "always"]} {c.reportAsDecision ? · 结果写入待处理 : null} {costs.length ? ( 上次 {formatUsd(costs[0])} {" "} · 近 {costs.length} 次平均 {formatUsd(avg)} ) : ( 还没有会话 )} {last ? {last.label} : 未运行} {job.lastRunAt ? {formatRelative(job.lastRunAt)} : null}
{job.lastStatus === "failed" && job.lastError ? (
失败原因:{job.lastError}

{job.lastError}

) : null} {runs.length ? (

最近会话

    {runs.slice(0, 3).map((r) => { const s = agentRunLabel(r.status); return (
  • {s.label} {plainSummary(r.summary ?? r.goal)} {formatUsd(r.usage.costUsd)} {formatRelative(r.finishedAt ?? r.createdAt)}
  • ); })}
) : null}
); }