feat(intent): 意图删除支持假删除/真删除两种模式
删除入口区分两种模式(待领/运行中/已暂停的意图均可删除): - 假删除(默认):意图置 state='deleted'、删除原因记入新字段 delete_reason, 保留节点与全部产出/血缘,不再在图上另挂 fact。 - 真删除:物理删除意图及「仅由它支撑」的独占子孙(沿 yields/derived_from 级联到叶子),避免孤立数据;共享节点、目标、任务根事实保留。被删意图先做 token rollup 并清理 activity/副会话,被删 finding 先删 findings 行。 两种模式都照常用 cancelled 触发通知 planner(带删除前捕获的意图摘要 + 原因), planner 文案不再声称原因挂为事实。 - DB: 新增 SoftDeleteIntent、重写 CancelIntent 为深度独占级联;schema 加 delete_reason 列与 intent 的 deleted 状态(含旧库迁移)。 - 前端: 删除对话框加模式切换与动态说明,状态徽章/图标新增「已删除」, 播报板展开展示删除原因。
This commit is contained in:
1 parent
6e3dbf6452
commit
4756bb1c45
17 files changed
+593
-238
No files matched your search
@@ -405,6 +405,12 @@ function BroadcastRow({
|
||||
<span>来源 {node.origin || "system"}</span>
|
||||
<span>{Number.isNaN(ts) ? node.ts : new Date(ts).toLocaleString("zh-CN")}</span>
|
||||
</div>
|
||||
{node.state === "deleted" && node.delete_reason && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-xs">
|
||||
<span className="font-medium text-destructive">删除原因</span>
|
||||
<span className="ml-2 break-words text-muted-foreground">{node.delete_reason}</span>
|
||||
</div>
|
||||
)}
|
||||
<AssetList assets={assets[node.id] ?? []} />
|
||||
{(upstream.length > 0 || downstream.length > 0) && (
|
||||
<div className="flex flex-col gap-3 sm:flex-row">
|
||||
|
||||
@@ -27,9 +27,10 @@ import {
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { ApprovalExecutionFocus, useApprovalFocus, useApprovalHistory } from "@/components/approval-execution-focus";
|
||||
import { MentionTextarea } from "@/components/mention-textarea";
|
||||
import { SideQuestionButton, SideQuestionWorkspace } from "@/components/side-question-workspace";
|
||||
import { TodoPopover } from "@/components/todo-popover";
|
||||
import { ApprovalExecutionFocus, useApprovalFocus, useApprovalHistory } from "@/components/approval-execution-focus";
|
||||
import { Transcript } from "@/components/transcript";
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -42,7 +43,6 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { MentionTextarea } from "@/components/mention-textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { InputGroup, InputGroupAddon, InputGroupButton } from "@/components/ui/input-group";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
@@ -181,6 +181,8 @@ function statusIcon(status: SessionStatus) {
|
||||
return <CircleXIcon className="size-3.5 text-red-500" />;
|
||||
case "exhausted": // 步数耗尽(撞 max_turns)
|
||||
return <ZapOffIcon className="size-3.5 text-violet-500" />;
|
||||
case "deleted": // 用户假删除
|
||||
return <CircleSlashIcon className="size-3.5 text-muted-foreground" />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +311,8 @@ function intentStatus(state: string): SessionStatus {
|
||||
return "paused";
|
||||
case "open": // 待领取,区别于执行中
|
||||
return "pending";
|
||||
case "deleted": // 用户假删除
|
||||
return "deleted";
|
||||
default: // running
|
||||
return "running";
|
||||
}
|
||||
@@ -362,7 +366,11 @@ function SessionItem({
|
||||
) : null;
|
||||
|
||||
const cancellable =
|
||||
s.role === "worker" && !s.inherited && !deleted && (s.status === "running" || s.status === "paused");
|
||||
s.role === "worker" &&
|
||||
!s.inherited &&
|
||||
!deleted &&
|
||||
// pending = 待领(open)意图;连同运行中/已暂停都允许删除。
|
||||
(s.status === "running" || s.status === "paused" || s.status === "pending");
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -417,8 +425,8 @@ function SessionItem({
|
||||
size="icon-xs"
|
||||
onClick={onCancel}
|
||||
disabled={controlling}
|
||||
title="删除该意图(需填写原因,保留数据)"
|
||||
aria-label="删除该意图(需填写原因,保留数据)"
|
||||
title="删除该意图(需填写原因,可选假删除/真删除)"
|
||||
aria-label="删除该意图(需填写原因,可选假删除/真删除)"
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2Icon />
|
||||
@@ -528,6 +536,8 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
const [controllingIntent, setControllingIntent] = React.useState<string | null>(null);
|
||||
const [cancelIntent, setCancelIntent] = React.useState<Session | null>(null);
|
||||
const [cancelReason, setCancelReason] = React.useState("");
|
||||
// 删除模式:soft=假删除(默认,置 deleted + 记原因,保留数据)| hard=真删除(级联移除独占子孙)。
|
||||
const [deleteMode, setDeleteMode] = React.useState<"soft" | "hard">("soft");
|
||||
const [workerMessage, setWorkerMessage] = React.useState("");
|
||||
const [workerMessageRequestId, setWorkerMessageRequestId] = React.useState("");
|
||||
const [workerMessageSending, setWorkerMessageSending] = React.useState(false);
|
||||
@@ -583,7 +593,7 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
}, []);
|
||||
|
||||
const controlWorker = React.useCallback(
|
||||
async (session: Session, action: "pause" | "resume" | "cancel", reason?: string) => {
|
||||
async (session: Session, action: "pause" | "resume" | "cancel", reason?: string, mode?: "soft" | "hard") => {
|
||||
if (!session.intent_id || session.inherited || controllingIntent) return;
|
||||
if (action === "cancel" && !reason?.trim()) {
|
||||
toast.error("请填写删除原因");
|
||||
@@ -591,16 +601,23 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
}
|
||||
setControllingIntent(session.intent_id);
|
||||
try {
|
||||
await api.controlIntent(taskId, session.intent_id, action, reason);
|
||||
const res = await api.controlIntent(taskId, session.intent_id, action, reason, mode);
|
||||
if (action === "pause") {
|
||||
patchIntentState(session.intent_id, "paused");
|
||||
toast.success(`Worker #${session.intent_id} 已暂停`);
|
||||
} else if (action === "resume") {
|
||||
patchIntentState(session.intent_id, "open");
|
||||
toast.success(`Worker #${session.intent_id} 已恢复,等待重新领取`);
|
||||
} else if (mode === "hard") {
|
||||
// 真删除:意图及独占下游已物理移除,从列表剔除该行。
|
||||
patchIntentState(session.intent_id);
|
||||
const d = res.deleted;
|
||||
const extra = d ? `(含 ${d.intents} 意图 / ${d.facts} 事实 / ${d.findings} 漏洞)` : "";
|
||||
toast.success(`Worker #${session.intent_id} 及其独占下游已彻底删除${extra}`);
|
||||
setCancelReason("");
|
||||
} else {
|
||||
// 删除 = 停止意图并附原因(不销毁意图与产出);保留会话,状态置为 stopped。
|
||||
patchIntentState(session.intent_id, "stopped");
|
||||
// 假删除:意图置 deleted、记录删除原因,保留节点与产出。
|
||||
patchIntentState(session.intent_id, "deleted");
|
||||
toast.success(`Worker #${session.intent_id} 已删除(原因已记录,规划者将据此重新规划)`);
|
||||
setCancelReason("");
|
||||
}
|
||||
@@ -1139,17 +1156,14 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
for (const node of allIntents) {
|
||||
let title = `Intent ${node.id}`;
|
||||
let parsedPayload: unknown = node.payload;
|
||||
let deleted = false;
|
||||
let deleteReason = "";
|
||||
// 假删除:意图 state='deleted',删除原因在独立字段 delete_reason 上。
|
||||
const deleted = node.state === "deleted";
|
||||
const deleteReason = node.delete_reason ?? "";
|
||||
if (node.payload) {
|
||||
try {
|
||||
const p = JSON.parse(node.payload);
|
||||
parsedPayload = p;
|
||||
if (p?.summary) title = String(p.summary);
|
||||
if (p?.cancelled_by_user) {
|
||||
deleted = true;
|
||||
deleteReason = String(p.cancel_reason ?? "");
|
||||
}
|
||||
} catch {
|
||||
title = node.payload.trim() || title;
|
||||
}
|
||||
@@ -1288,16 +1302,27 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
}, [activeKey]);
|
||||
|
||||
const focusKey = approvalFocus.state?.source?.session;
|
||||
const loadFocusPage = React.useCallback((before: number) => api.activityHistory(taskId, focusKey ?? "main", before, PAGE), [taskId, focusKey]);
|
||||
const mergeFocusPage = React.useCallback((page: { items: Activity[]; hasMore: boolean }) => {
|
||||
if (!focusKey) return;
|
||||
patchStore(focusKey, (s) => {
|
||||
const items = mergeBySeq(page.items, s.items);
|
||||
return { ...s, items, hasMore: page.hasMore, earliestSeq: items[0]?.seq ?? s.earliestSeq };
|
||||
});
|
||||
}, [focusKey, patchStore]);
|
||||
const focusHistory = useApprovalHistory(approvalFocus.state?.source, !!focusKey && !!store[focusKey]?.loaded,
|
||||
focusKey ? store[focusKey]?.items ?? [] : [], loadFocusPage, mergeFocusPage);
|
||||
const loadFocusPage = React.useCallback(
|
||||
(before: number) => api.activityHistory(taskId, focusKey ?? "main", before, PAGE),
|
||||
[taskId, focusKey],
|
||||
);
|
||||
const mergeFocusPage = React.useCallback(
|
||||
(page: { items: Activity[]; hasMore: boolean }) => {
|
||||
if (!focusKey) return;
|
||||
patchStore(focusKey, (s) => {
|
||||
const items = mergeBySeq(page.items, s.items);
|
||||
return { ...s, items, hasMore: page.hasMore, earliestSeq: items[0]?.seq ?? s.earliestSeq };
|
||||
});
|
||||
},
|
||||
[focusKey, patchStore],
|
||||
);
|
||||
const focusHistory = useApprovalHistory(
|
||||
approvalFocus.state?.source,
|
||||
!!focusKey && !!store[focusKey]?.loaded,
|
||||
focusKey ? (store[focusKey]?.items ?? []) : [],
|
||||
loadFocusPage,
|
||||
mergeFocusPage,
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (approvalFocus.state) atBottomRef.current = false;
|
||||
}, [approvalFocus.state]);
|
||||
@@ -1828,10 +1853,16 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<ApprovalExecutionFocus focus={{ ...approvalFocus, close: () => {
|
||||
setActiveId(activeId);
|
||||
approvalFocus.close();
|
||||
} }} history={focusHistory} />
|
||||
<ApprovalExecutionFocus
|
||||
focus={{
|
||||
...approvalFocus,
|
||||
close: () => {
|
||||
setActiveId(activeId);
|
||||
approvalFocus.close();
|
||||
},
|
||||
}}
|
||||
history={focusHistory}
|
||||
/>
|
||||
{/* Force Radix's internal viewport wrapper (display:table, sizes to content)
|
||||
to block so wide/unbreakable steps (long commands, code, URLs) can't blow
|
||||
out the width and defeat the truncation below — the transcript wraps to
|
||||
@@ -1863,7 +1894,13 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
</Button>
|
||||
</div>
|
||||
) : activity.length ? (
|
||||
<Transcript activity={activity} live={active.live} taskId={taskId} chat={isMain} focusedSeq={focusHistory.ready ? approvalFocus.state?.source?.seq : undefined} />
|
||||
<Transcript
|
||||
activity={activity}
|
||||
live={active.live}
|
||||
taskId={taskId}
|
||||
chat={isMain}
|
||||
focusedSeq={focusHistory.ready ? approvalFocus.state?.source?.seq : undefined}
|
||||
/>
|
||||
) : (
|
||||
<div className="pl-9 text-xs text-muted-foreground">
|
||||
{isMain ? "还没有对话。在下方给主 Agent 发消息,引导探索方向或介入流程。" : "暂无活动记录。"}
|
||||
@@ -1908,7 +1945,9 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
inputGroup
|
||||
rows={1}
|
||||
aria-label="给主 Agent 发消息"
|
||||
placeholder={mainBusy ? "主 Agent 正在运行,可输入 /btw 提问…" : "给主 Agent 发消息,@ 引用漏洞、资产等…"}
|
||||
placeholder={
|
||||
mainBusy ? "主 Agent 正在运行,可输入 /btw 提问…" : "给主 Agent 发消息,@ 引用漏洞、资产等…"
|
||||
}
|
||||
value={input}
|
||||
disabled={sending}
|
||||
onValueChange={setInput}
|
||||
@@ -2069,6 +2108,7 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
if (!open) {
|
||||
setCancelIntent(null);
|
||||
setCancelReason("");
|
||||
setDeleteMode("soft");
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -2076,33 +2116,68 @@ export function SessionsTab({ taskId }: { taskId: string }) {
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>删除 Worker #{cancelIntent?.intent_id}?</AlertDialogTitle>
|
||||
<AlertDialogDescription className="break-words whitespace-normal">
|
||||
删除会<strong>停止该意图</strong>
|
||||
(不再执行),并把删除原因作为一条事实挂到该意图上;意图、执行记录、已登记的事实和漏洞
|
||||
<strong>都会保留</strong>。规划者会收到「该意图由用户删除 + 原因」并据此重新规划。
|
||||
{deleteMode === "hard" ? (
|
||||
<>
|
||||
<strong>真删除</strong>会物理移除该意图,以及<strong>仅由它支撑</strong>
|
||||
的下游节点(级联到叶子,避免留下孤立数据);共享节点、目标和任务根事实会保留。
|
||||
<strong>此操作不可恢复。</strong>规划者会收到删除通知并据此重新规划。
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>假删除</strong>会把该意图置为「已删除」并记录删除原因,意图节点、执行记录、
|
||||
已登记的事实和漏洞<strong>都会保留</strong>。规划者会收到「该意图由用户删除 + 原因」并据此重新规划。
|
||||
</>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="grid gap-2 py-1">
|
||||
<label htmlFor="cancel-reason" className="text-sm font-medium">
|
||||
删除原因(必填)
|
||||
</label>
|
||||
<Textarea
|
||||
id="cancel-reason"
|
||||
value={cancelReason}
|
||||
onChange={(e) => setCancelReason(e.target.value)}
|
||||
placeholder="说明为什么删除这条意图,例如:方向判断错误 / 目标已失效 / 与其他意图重复…"
|
||||
rows={3}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="grid gap-3 py-1">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeleteMode("soft")}
|
||||
className={cn(
|
||||
"rounded-md border px-3 py-2 text-left text-sm transition-colors",
|
||||
deleteMode === "soft" ? "border-primary bg-primary/5" : "hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
<div className="font-medium">假删除</div>
|
||||
<div className="text-xs text-muted-foreground">保留数据,可追溯</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeleteMode("hard")}
|
||||
className={cn(
|
||||
"rounded-md border px-3 py-2 text-left text-sm transition-colors",
|
||||
deleteMode === "hard" ? "border-destructive bg-destructive/5" : "hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
<div className="font-medium">真删除</div>
|
||||
<div className="text-xs text-muted-foreground">级联移除,不可恢复</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<label htmlFor="cancel-reason" className="text-sm font-medium">
|
||||
删除原因(必填)
|
||||
</label>
|
||||
<Textarea
|
||||
id="cancel-reason"
|
||||
value={cancelReason}
|
||||
onChange={(e) => setCancelReason(e.target.value)}
|
||||
placeholder="说明为什么删除这条意图,例如:方向判断错误 / 目标已失效 / 与其他意图重复…"
|
||||
rows={3}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>返回</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
variant="destructive"
|
||||
disabled={!cancelIntent || controllingIntent !== null || !cancelReason.trim()}
|
||||
onClick={() => cancelIntent && void controlWorker(cancelIntent, "cancel", cancelReason)}
|
||||
onClick={() => cancelIntent && void controlWorker(cancelIntent, "cancel", cancelReason, deleteMode)}
|
||||
>
|
||||
{controllingIntent ? <Loader2Icon className="animate-spin" /> : <Trash2Icon />}
|
||||
确认删除
|
||||
{deleteMode === "hard" ? "彻底删除" : "确认删除"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
+10
-3
@@ -315,12 +315,19 @@ export const api = {
|
||||
deleteTaskArchive: (id: number) => del<TaskArchive>(`/task-archives/${id}`),
|
||||
deleteTaskArchives: (archiveIds: number[]) =>
|
||||
post<{ items: ArchiveBatchItem[] }>("/task-archives/delete/batch", { archive_ids: archiveIds }),
|
||||
controlIntent: (taskId: string, intentId: string, action: "pause" | "resume" | "cancel", reason?: string) =>
|
||||
controlIntent: (
|
||||
taskId: string,
|
||||
intentId: string,
|
||||
action: "pause" | "resume" | "cancel",
|
||||
reason?: string,
|
||||
// cancel 专用:soft(默认,假删除,意图置 deleted + 记删除原因)| hard(真删除,级联移除独占子孙)。
|
||||
mode?: "soft" | "hard",
|
||||
) =>
|
||||
post<{
|
||||
id: number;
|
||||
state: "paused" | "open" | "stopped";
|
||||
state: "paused" | "open" | "deleted" | "";
|
||||
deleted?: { intents: number; facts: number; findings: number; activities: number };
|
||||
}>(`/tasks/${taskId}/intents/${intentId}/control`, { action, reason: reason ?? "" }),
|
||||
}>(`/tasks/${taskId}/intents/${intentId}/control`, { action, reason: reason ?? "", mode: mode ?? "soft" }),
|
||||
sendWorkerMessage: (taskId: string, intentId: string, message: string, requestId: string) =>
|
||||
post<{
|
||||
id: number;
|
||||
|
||||
+24
-12
@@ -1452,20 +1452,32 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
if (b.action === "cancel") {
|
||||
const index = mockIntents.findIndex((item) => item.id === id);
|
||||
const intent = mockIntents[index];
|
||||
if (!intent || intent.inherited || (intent.state !== "running" && intent.state !== "paused")) {
|
||||
if (
|
||||
!intent ||
|
||||
intent.inherited ||
|
||||
(intent.state !== "running" && intent.state !== "paused" && intent.state !== "open")
|
||||
) {
|
||||
throw new Error("Worker 状态已变化");
|
||||
}
|
||||
mockIntents.splice(index, 1);
|
||||
return {
|
||||
id: Number(id.replace(/\D/g, "")) || 0,
|
||||
state: "cancelled",
|
||||
deleted: {
|
||||
intents: 1,
|
||||
facts: 1,
|
||||
findings: 1,
|
||||
activities: mockActivity.filter((item) => item.intent_id === id).length,
|
||||
},
|
||||
};
|
||||
const numId = Number(id.replace(/\D/g, "")) || 0;
|
||||
if (b.mode === "hard") {
|
||||
// 真删除:从列表移除,返回级联删除计数。
|
||||
mockIntents.splice(index, 1);
|
||||
return {
|
||||
id: numId,
|
||||
state: "",
|
||||
deleted: {
|
||||
intents: 1,
|
||||
facts: 1,
|
||||
findings: 1,
|
||||
activities: mockActivity.filter((item) => item.intent_id === id).length,
|
||||
},
|
||||
};
|
||||
}
|
||||
// 假删除(默认):置 deleted + 记删除原因,保留节点。
|
||||
intent.state = "deleted";
|
||||
intent.delete_reason = String(b.reason ?? "");
|
||||
return { id: numId, state: "deleted" };
|
||||
}
|
||||
const action = b.action === "resume" ? "resume" : "pause";
|
||||
const result = controlMockIntent(id, action);
|
||||
|
||||
+9
-20
@@ -1,31 +1,18 @@
|
||||
// Centralised status → color/label semantics, reused across the whole app.
|
||||
// Spec §8.3: 意图 / 覆盖 / 任务 / 严重度 each have a consistent color set.
|
||||
|
||||
export type Tone =
|
||||
| "neutral"
|
||||
| "blue"
|
||||
| "green"
|
||||
| "amber"
|
||||
| "red"
|
||||
| "rose"
|
||||
| "violet"
|
||||
| "slate";
|
||||
export type Tone = "neutral" | "blue" | "green" | "amber" | "red" | "rose" | "violet" | "slate";
|
||||
|
||||
export const toneClasses: Record<Tone, string> = {
|
||||
neutral:
|
||||
"bg-muted text-muted-foreground border-transparent",
|
||||
neutral: "bg-muted text-muted-foreground border-transparent",
|
||||
blue: "bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/20",
|
||||
green:
|
||||
"bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
|
||||
amber:
|
||||
"bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||
green: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/20",
|
||||
amber: "bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||
red: "bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20",
|
||||
// rose 用作「严重」——实心高强调,视觉上明显高于「高危」的软红描边。
|
||||
rose: "bg-rose-600 text-white border-rose-600 dark:bg-rose-600 dark:text-white",
|
||||
violet:
|
||||
"bg-violet-500/15 text-violet-600 dark:text-violet-400 border-violet-500/20",
|
||||
slate:
|
||||
"bg-slate-500/15 text-slate-600 dark:text-slate-400 border-slate-500/20",
|
||||
violet: "bg-violet-500/15 text-violet-600 dark:text-violet-400 border-violet-500/20",
|
||||
slate: "bg-slate-500/15 text-slate-600 dark:text-slate-400 border-slate-500/20",
|
||||
};
|
||||
|
||||
export const toneDot: Record<Tone, string> = {
|
||||
@@ -53,8 +40,10 @@ const intent: Record<string, StatusMeta> = {
|
||||
blocked: { label: "执行出错", tone: "red" },
|
||||
// exhausted = 达到步数/时间预算被中途掐断、只写回部分结果(非方向已探尽)。
|
||||
exhausted: { label: "预算耗尽", tone: "violet" },
|
||||
// stopped = 用户手动删除/停止了该 work(保留已产出的事实)。
|
||||
// stopped = 历史软删除状态(保留,历史数据)。
|
||||
stopped: { label: "已停止", tone: "slate" },
|
||||
// deleted = 用户假删除了该意图(保留节点与血缘,删除原因见 delete_reason 字段)。
|
||||
deleted: { label: "已删除", tone: "slate" },
|
||||
};
|
||||
|
||||
const task: Record<string, StatusMeta> = {
|
||||
|
||||
@@ -418,6 +418,7 @@ export interface TaskNode {
|
||||
ts: string;
|
||||
source_task_id?: string;
|
||||
inherited?: boolean;
|
||||
delete_reason?: string; // 意图假删除(state='deleted')时的删除原因
|
||||
}
|
||||
|
||||
// 播报板一页:按创建顺序分页的节点 + 这一页涉及的边 + 边另一端的节点(refs,按 id 索引),
|
||||
@@ -754,7 +755,7 @@ export interface LogLine {
|
||||
}
|
||||
|
||||
export type SessionRole = "mainagent" | "planner" | "worker" | "system";
|
||||
export type SessionStatus = "running" | "paused" | "done" | "blocked" | "exhausted" | "pending" | "stopped";
|
||||
export type SessionStatus = "running" | "paused" | "done" | "blocked" | "exhausted" | "pending" | "stopped" | "deleted";
|
||||
|
||||
// Daily token aggregate bucket (GET /api/tokens/daily).
|
||||
export interface DailyTokenBucket {
|
||||
|
||||
Reference in new issue
Block a user