- Port agentui (MIT, ashish200729/agentui@b2b752b) Message/MessageBubble/MessageScroller, StreamingResponse, ToolResult/ToolApproval, TodoList, ApprovalCard, CodeBlock/FileDiff, Citations, PromptInput into components/agentui; drop motion/shiki/Next, switch colors, type scale, radii, durations and easing to semantic tokens, honour reduced motion - Conversation: streaming cursor, collapsible thinking, tool cards with kind/scope/risk/ idempotency badges and audit idempotency key, prominent server-rejection state for both whitelist rejections and unauthorized errors, compaction markers, budget meters with over-limit state, approval cards (admin session only), two-column final report (model said vs system verified with task/record links), follow-scroll with jump-to-bottom - Composer: Enter send / Shift+Enter newline, stop = abort (confirmed), append, execute/plan_only switch, "execute this plan" from finished plan_only runs - Session list sidebar with status filter and usage; reconnect/error banners with retry - Drawer: route-derived context (entity/site/task), minimize to a live mini dock - Fix stream refetch race when the run switches from loading to finished Co-Authored-By: Claude Opus 5.5 <[email protected]>
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { authDenial, isDenied, pairAudit, runPlanIds } from "./agent-conversation.tsx";
|
|
import { contextFromPath, contextPrefix, splitContextGoal } from "./agent-store.ts";
|
|
import type { StreamItem, ToolCallView } from "./use-agent-stream.ts";
|
|
|
|
const call = (p: Partial<ToolCallView>): ToolCallView => ({
|
|
toolCallId: "t1",
|
|
capabilityId: "task.retry",
|
|
inputSummary: null,
|
|
outputSummary: null,
|
|
status: "succeeded",
|
|
reason: null,
|
|
startedAt: "2026-09-27T00:00:00.000Z",
|
|
endedAt: null,
|
|
recovery: false,
|
|
...p,
|
|
});
|
|
|
|
describe("拒绝判定", () => {
|
|
it("授权校验失败(tool_call_end + unauthorized)按拒绝展示", () => {
|
|
const c = call({
|
|
status: "failed",
|
|
outputSummary: '{"error":{"code":"unauthorized","message":"缺少 task.retry 的持续授权"}}',
|
|
});
|
|
expect(authDenial(c)).toBe("缺少 task.retry 的持续授权");
|
|
expect(isDenied(c)).toBe(true);
|
|
});
|
|
it("截断的摘要也能认出授权错误;普通失败不算拒绝", () => {
|
|
expect(authDenial(call({ status: "failed", outputSummary: '{"error":{"code":"forbidden","message":"x"}…' }))).toBe(
|
|
"x",
|
|
);
|
|
expect(isDenied(call({ status: "failed", outputSummary: '{"error":{"code":"internal"}}' }))).toBe(false);
|
|
expect(isDenied(call({ status: "denied", reason: "不在白名单" }))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("计划与审计", () => {
|
|
const plan = "32320000-0000-4000-8000-000700000002";
|
|
it("从提交输入与下载计划输出里收集计划 ID", () => {
|
|
const items: StreamItem[] = [
|
|
{ kind: "tool", key: "a", call: call({ capabilityId: "download.submit", inputSummary: `{"planId":"${plan}"}` }) },
|
|
{
|
|
kind: "tool",
|
|
key: "b",
|
|
call: call({
|
|
toolCallId: "t2",
|
|
capabilityId: "download.plan",
|
|
outputSummary: '{"plan":{"id":"33000000-0000-4000-8000-000000000701","status":"draft"…',
|
|
}),
|
|
},
|
|
];
|
|
expect(runPlanIds(items, null).sort()).toEqual([plan, "33000000-0000-4000-8000-000000000701"]);
|
|
});
|
|
it("同能力的审计按先后与工具调用配对", () => {
|
|
const calls = [
|
|
call({ toolCallId: "a", capabilityId: "download.submit", startedAt: "2026-09-27T00:00:01.000Z" }),
|
|
call({ toolCallId: "b", capabilityId: "download.submit", startedAt: "2026-09-27T00:00:02.000Z" }),
|
|
];
|
|
const audit = ["k1", "k2"].map((k, i) => ({
|
|
capabilityId: "download.submit",
|
|
at: `2026-09-27T00:00:0${i + 1}.500Z`,
|
|
outcome: "succeeded",
|
|
idempotencyKey: k,
|
|
durationMs: 1,
|
|
grantId: null,
|
|
approvalId: null,
|
|
}));
|
|
const m = pairAudit(calls, audit);
|
|
expect(m.get("a")?.idempotencyKey).toBe("k1");
|
|
expect(m.get("b")?.idempotencyKey).toBe("k2");
|
|
});
|
|
});
|
|
|
|
describe("侧栏上下文", () => {
|
|
it("按路由推断上下文并生成前缀,列表里能拆回正文", () => {
|
|
const ctx = contextFromPath("/console/engine/5b4e76e0-f27f-4e7a-8e08-71486387df1d");
|
|
expect(ctx?.taskId).toBe("5b4e76e0-f27f-4e7a-8e08-71486387df1d");
|
|
expect(contextFromPath("/console/sites/hdhome")?.siteId).toBe("hdhome");
|
|
expect(contextFromPath("/console/agent")).toBeNull();
|
|
const goal = `${contextPrefix(ctx)}排查这个任务`;
|
|
expect(splitContextGoal(goal)).toEqual({ context: expect.stringContaining("taskId="), text: "排查这个任务" });
|
|
});
|
|
});
|