126 lines
5.0 KiB
TypeScript
126 lines
5.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import type { AgentRunProvenance, ChatMessage, TraceEvent } from "../src/types/index.ts";
|
|
import { canCancelMessage, canReplayTrace, canRetryMessage, messageTraceId, renderSafeMarkdown, traceEventBody, traceEventLabel, traceIdentityText, visibleTraceEvents } from "../src/components/workbench/message-rendering.ts";
|
|
|
|
test("R1 markdown rendering keeps structure and strips unsafe HTML", () => {
|
|
const html = renderSafeMarkdown("# 标题\n\n- 项目\n\n<script>alert('x')</script>\n\n[link](https://example.com)");
|
|
assert.match(html, /<h1>标题<\/h1>/u);
|
|
assert.match(html, /<li>项目<\/li>/u);
|
|
assert.match(html, /href="https:\/\/example.com"/u);
|
|
assert.doesNotMatch(html, /<script|alert/u);
|
|
});
|
|
|
|
test("R1 message actions follow per-message status and trace rules", () => {
|
|
const running = agentMessage({ status: "running", traceId: "trc_running", retryInput: "retry me" });
|
|
assert.equal(messageTraceId(running), "trc_running");
|
|
assert.equal(canCancelMessage(running), true);
|
|
assert.equal(canRetryMessage(running), true);
|
|
assert.equal(canReplayTrace(running), true);
|
|
|
|
const completed = agentMessage({ status: "completed", traceId: "trc_done", retryInput: "retry me" });
|
|
assert.equal(canCancelMessage(completed), false);
|
|
assert.equal(canRetryMessage(completed), false);
|
|
assert.equal(canReplayTrace(completed), true);
|
|
|
|
const failed = agentMessage({ status: "failed", traceId: "trc_failed", retryInput: "retry me" });
|
|
assert.equal(canCancelMessage(failed), false);
|
|
assert.equal(canRetryMessage(failed), true);
|
|
assert.equal(canReplayTrace(failed), true);
|
|
|
|
const user: ChatMessage = { id: "msg_user", role: "user", title: "用户", text: "hi", status: "sent", createdAt: "2026-01-01T00:00:00.000Z", traceId: "trc_user" };
|
|
assert.equal(canReplayTrace(user), false);
|
|
});
|
|
|
|
test("R1 trace rendering hides chunk noise without dropping meaningful events", () => {
|
|
const events: TraceEvent[] = [
|
|
{ label: "assistant:delta", message: "token" },
|
|
{ label: "command:completed", command: "npm test", exitCode: 0, elapsedMs: 42 },
|
|
{ type: "assistant_message", text: "final", final: true }
|
|
];
|
|
const compact = visibleTraceEvents(events, true);
|
|
assert.equal(compact.length, 2);
|
|
assert.equal(traceEventLabel(compact[0]!), "command:completed");
|
|
assert.equal(traceEventBody(compact[0]!), "npm test");
|
|
assert.equal(traceEventLabel(compact[1]!), "assistant_message");
|
|
assert.equal(traceEventBody(compact[1]!), "final");
|
|
assert.equal(visibleTraceEvents(events, false).length, 3);
|
|
});
|
|
|
|
test("R1 status summary keeps the restored 23-row floor", () => {
|
|
const message = agentMessage({
|
|
status: "failed",
|
|
traceId: "trc_summary",
|
|
conversationId: "cnv_summary",
|
|
sessionId: "ses_summary",
|
|
threadId: "thr_summary",
|
|
runnerTrace: {
|
|
traceId: "trc_summary",
|
|
status: "failed",
|
|
eventCount: 2,
|
|
eventsCompacted: false,
|
|
fullTraceLoaded: true,
|
|
lastEventLabel: "assistant:completed",
|
|
waitingFor: "result",
|
|
runnerKind: "agentrun",
|
|
sessionMode: "long-lived",
|
|
agentRun: {
|
|
runId: "run_1",
|
|
commandId: "cmd_1",
|
|
attemptId: "att_1",
|
|
runnerId: "runner_1",
|
|
jobName: "job_1",
|
|
namespace: "hwlab-v03",
|
|
terminalStatus: "failed"
|
|
}
|
|
},
|
|
error: { code: "provider_timeout", category: "provider", providerStatus: 504 }
|
|
});
|
|
const values = [
|
|
message.status,
|
|
message.runnerTrace?.status,
|
|
message.traceId,
|
|
message.conversationId,
|
|
message.sessionId,
|
|
message.threadId,
|
|
message.runnerTrace?.runnerKind,
|
|
message.runnerTrace?.sessionMode,
|
|
message.runnerTrace?.waitingFor,
|
|
message.runnerTrace?.lastEventLabel,
|
|
String(message.runnerTrace?.eventCount),
|
|
String(message.runnerTrace?.eventsCompacted),
|
|
message.runnerTrace?.fullTraceLoaded === true ? "loaded" : "unverified",
|
|
asAgentRun(message.runnerTrace?.agentRun)?.runId,
|
|
asAgentRun(message.runnerTrace?.agentRun)?.commandId,
|
|
asAgentRun(message.runnerTrace?.agentRun)?.attemptId,
|
|
asAgentRun(message.runnerTrace?.agentRun)?.runnerId,
|
|
asAgentRun(message.runnerTrace?.agentRun)?.jobName,
|
|
asAgentRun(message.runnerTrace?.agentRun)?.namespace,
|
|
asAgentRun(message.runnerTrace?.agentRun)?.terminalStatus,
|
|
message.error?.code,
|
|
message.error?.category,
|
|
String(message.error?.providerStatus)
|
|
];
|
|
assert.equal(values.length, 23);
|
|
assert.equal(values.every((value) => value != null && value !== ""), true);
|
|
assert.match(traceIdentityText(message), /traceId=trc_summary/u);
|
|
assert.match(traceIdentityText(message), /sessionId=ses_summary/u);
|
|
});
|
|
|
|
function agentMessage(patch: Partial<ChatMessage>): ChatMessage {
|
|
return {
|
|
id: "msg_agent",
|
|
role: "agent",
|
|
title: "Code Agent",
|
|
text: "done",
|
|
status: "completed",
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
...patch
|
|
} as ChatMessage;
|
|
}
|
|
|
|
function asAgentRun(value: unknown): AgentRunProvenance | null {
|
|
return value && typeof value === "object" ? value as AgentRunProvenance : null;
|
|
}
|