mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 21:33:41 +02:00
handleCopyAll built its text from traceEventSummary — the one-line, whitespace-collapsed, 200-char row preview — so every copied tool output and agent reply was truncated with "...". Add traceEventCopyText, which returns the complete body (full tool input JSON / result output / prose), redact it like the detail view, and join events with blank lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
119 lines
4.9 KiB
TypeScript
119 lines
4.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
stripShellWrapper,
|
|
traceEventCopyText,
|
|
traceEventDefaultExpanded,
|
|
traceEventHasDetail,
|
|
traceEventKind,
|
|
traceEventLabel,
|
|
traceEventSummary,
|
|
traceToolArgSummary,
|
|
} from "./trace-event-presenter";
|
|
|
|
describe("traceEventKind / traceEventLabel", () => {
|
|
it("maps the five persisted types and keeps unknown types as generic", () => {
|
|
expect(traceEventKind({ type: "text" })).toBe("agent");
|
|
expect(traceEventKind({ type: "thinking" })).toBe("thinking");
|
|
expect(traceEventKind({ type: "tool_use" })).toBe("tool_use");
|
|
expect(traceEventKind({ type: "tool_result" })).toBe("tool_result");
|
|
expect(traceEventKind({ type: "error" })).toBe("error");
|
|
expect(traceEventKind({ type: "provider_custom" })).toBe("generic");
|
|
});
|
|
|
|
it("shows provider-native tool names verbatim and surfaces raw unknown types", () => {
|
|
expect(traceEventLabel({ type: "tool_use", tool: "exec_command" })).toBe("exec_command");
|
|
expect(traceEventLabel({ type: "tool_result", tool: "patch_apply" })).toBe("patch_apply");
|
|
expect(traceEventLabel({ type: "tool_use" })).toBe("Tool");
|
|
expect(traceEventLabel({ type: "provider_custom" })).toBe("provider_custom");
|
|
});
|
|
});
|
|
|
|
describe("stripShellWrapper", () => {
|
|
it("strips login-shell wrappers but keeps bare commands", () => {
|
|
expect(stripShellWrapper("/bin/zsh -lc 'rm ./reply.md'")).toBe("rm ./reply.md");
|
|
expect(stripShellWrapper('/bin/bash -c "git status"')).toBe("git status");
|
|
expect(stripShellWrapper("sh -c 'ls -la'")).toBe("ls -la");
|
|
expect(stripShellWrapper("pnpm test")).toBe("pnpm test");
|
|
// Mismatched quotes are not a wrapper match.
|
|
expect(stripShellWrapper("/bin/zsh -lc 'echo hi\"")).toBe("/bin/zsh -lc 'echo hi\"");
|
|
});
|
|
});
|
|
|
|
describe("traceToolArgSummary", () => {
|
|
it("prefers query, then paths (shortened), then command with wrapper stripped", () => {
|
|
expect(traceToolArgSummary({ query: "flaky tests", command: "x" })).toBe("flaky tests");
|
|
expect(traceToolArgSummary({ file_path: "/a/b/c/d/e.ts" })).toBe(".../d/e.ts");
|
|
expect(traceToolArgSummary({ command: "/bin/zsh -lc 'kubectl get pods -n prd'" })).toBe(
|
|
"kubectl get pods -n prd",
|
|
);
|
|
});
|
|
|
|
it("falls back to the first short string value and tolerates empty input", () => {
|
|
expect(traceToolArgSummary({ n: 3, note: "short value" })).toBe("short value");
|
|
expect(traceToolArgSummary(undefined)).toBe("");
|
|
expect(traceToolArgSummary({})).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("traceEventSummary", () => {
|
|
it("takes the first non-empty line for agent text", () => {
|
|
expect(traceEventSummary({ type: "text", content: "\n\nFirst line\nrest" })).toBe(
|
|
"First line",
|
|
);
|
|
});
|
|
|
|
it("collapses pretty-printed JSON output to a content preview, not a lone bracket", () => {
|
|
const output = '[\n {\n "id": "694c",\n "title": "x"\n }\n]';
|
|
expect(traceEventSummary({ type: "tool_result", output })).toBe(
|
|
'[ { "id": "694c", "title": "x" } ]',
|
|
);
|
|
});
|
|
|
|
it("retains unknown events instead of dropping them", () => {
|
|
expect(traceEventSummary({ type: "custom", content: "payload" })).toBe("payload");
|
|
});
|
|
});
|
|
|
|
describe("traceEventCopyText", () => {
|
|
it("copies the full untruncated body, not the one-line summary", () => {
|
|
const longOutput = "line 1\n".repeat(60);
|
|
expect(traceEventCopyText({ type: "tool_result", tool: "Bash", output: longOutput })).toBe(
|
|
`[Bash] ${longOutput}`,
|
|
);
|
|
expect(
|
|
traceEventCopyText({ type: "tool_use", tool: "Bash", input: { command: "ls" } }),
|
|
).toBe('[Bash] {\n "command": "ls"\n}');
|
|
expect(traceEventCopyText({ type: "text", content: "full\nagent\nreply" })).toBe(
|
|
"[Agent] full\nagent\nreply",
|
|
);
|
|
});
|
|
|
|
it("emits a bare label when the event has no body", () => {
|
|
expect(traceEventCopyText({ type: "tool_use", tool: "Bash" })).toBe("[Bash]");
|
|
});
|
|
});
|
|
|
|
describe("traceEventDefaultExpanded", () => {
|
|
const agent = { type: "text", content: "hello" };
|
|
const error = { type: "error", content: "boom" };
|
|
const thinking = { type: "thinking", content: "hmm" };
|
|
const tool = { type: "tool_use", tool: "Bash", input: { command: "ls" } };
|
|
|
|
it("smart: agent and error read without a click, process noise stays folded", () => {
|
|
expect(traceEventDefaultExpanded(agent, "smart")).toBe(true);
|
|
expect(traceEventDefaultExpanded(error, "smart")).toBe(true);
|
|
expect(traceEventDefaultExpanded(thinking, "smart")).toBe(false);
|
|
expect(traceEventDefaultExpanded(tool, "smart")).toBe(false);
|
|
});
|
|
|
|
it("expanded/collapsed override the hierarchy wholesale", () => {
|
|
expect(traceEventDefaultExpanded(thinking, "expanded")).toBe(true);
|
|
expect(traceEventDefaultExpanded(agent, "collapsed")).toBe(false);
|
|
});
|
|
|
|
it("a row without detail never expands", () => {
|
|
expect(traceEventDefaultExpanded({ type: "text" }, "expanded")).toBe(false);
|
|
expect(traceEventHasDetail({ type: "tool_use", input: {} })).toBe(false);
|
|
});
|
|
});
|