mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 21:23:04 +00:00
* feat(agents): show agent messages and completion notifications with sender identity * fix(agents): preserve human envelope examples and attachment context * fix(agents): separate message provenance from timeline presentation
134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
import type { ToolCallDetail } from "@getpaseo/protocol/agent-types";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { buildToolCallPresentation, type ToolCallPresentationIcon } from "./presentation";
|
|
|
|
const fakeIcons = {
|
|
brain: (() => null) as ToolCallPresentationIcon,
|
|
eye: (() => null) as ToolCallPresentationIcon,
|
|
wrench: (() => null) as ToolCallPresentationIcon,
|
|
};
|
|
|
|
function fakeResolveIcon(
|
|
toolName: string,
|
|
detail: ToolCallDetail | undefined,
|
|
): ToolCallPresentationIcon {
|
|
if (detail?.type === "plan") {
|
|
return fakeIcons.brain;
|
|
}
|
|
if (detail?.type === "read") {
|
|
return fakeIcons.eye;
|
|
}
|
|
if (toolName === "exec_command") {
|
|
return fakeIcons.wrench;
|
|
}
|
|
return fakeIcons.wrench;
|
|
}
|
|
|
|
describe("tool-call presentation", () => {
|
|
it("builds badge, detail, icon, and file-open policy in one model", () => {
|
|
const presentation = buildToolCallPresentation({
|
|
toolName: "read_file",
|
|
status: "completed",
|
|
error: null,
|
|
cwd: "/tmp/repo",
|
|
detail: {
|
|
type: "read",
|
|
filePath: "/tmp/repo/src/index.ts",
|
|
content: "console.log('hi');",
|
|
},
|
|
resolveIcon: fakeResolveIcon,
|
|
});
|
|
|
|
expect(presentation).toMatchObject({
|
|
displayName: "Read",
|
|
summary: "src/index.ts",
|
|
icon: fakeIcons.eye,
|
|
isLoadingDetails: false,
|
|
hasDetails: true,
|
|
canOpenDetails: true,
|
|
openFilePath: "/tmp/repo/src/index.ts",
|
|
isPlan: false,
|
|
});
|
|
});
|
|
|
|
it("marks running calls without meaningful detail as loading details", () => {
|
|
const presentation = buildToolCallPresentation({
|
|
toolName: "exec_command",
|
|
status: "running",
|
|
error: null,
|
|
detail: {
|
|
type: "unknown",
|
|
input: {},
|
|
output: null,
|
|
},
|
|
resolveIcon: fakeResolveIcon,
|
|
});
|
|
|
|
expect(presentation).toMatchObject({
|
|
displayName: "Exec command",
|
|
icon: fakeIcons.wrench,
|
|
isLoadingDetails: true,
|
|
hasDetails: false,
|
|
canOpenDetails: true,
|
|
openFilePath: null,
|
|
isPlan: false,
|
|
});
|
|
});
|
|
|
|
it("keeps plan calls out of the expandable badge path", () => {
|
|
const presentation = buildToolCallPresentation({
|
|
toolName: "ExitPlanMode",
|
|
status: "completed",
|
|
error: null,
|
|
detail: {
|
|
type: "plan",
|
|
text: "1. Do the thing",
|
|
},
|
|
resolveIcon: fakeResolveIcon,
|
|
});
|
|
|
|
expect(presentation.isPlan).toBe(true);
|
|
expect(presentation.icon).toBe(fakeIcons.brain);
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
["message", "Message from QA messenger"],
|
|
["finished", "QA messenger finished"],
|
|
["errored", "QA messenger errored"],
|
|
["permission-required", "QA messenger needs permission"],
|
|
["closed", "QA messenger closed"],
|
|
] as const)("presents %s from structured provenance without a second label", (event, heading) => {
|
|
const presentation = buildToolCallPresentation({
|
|
toolName: "internal_name_should_not_be_visible",
|
|
status: "completed",
|
|
error: null,
|
|
detail: { type: "plain_text", label: "legacy summary", text: "legacy body" },
|
|
agentMessage: {
|
|
event,
|
|
sender: { id: "server::sender", title: "QA messenger" },
|
|
text: "The delivered message.",
|
|
},
|
|
resolveIcon: fakeResolveIcon,
|
|
});
|
|
expect(presentation.displayName).toBe(heading);
|
|
expect(presentation.summary).toBeUndefined();
|
|
expect(presentation.detail).toEqual({
|
|
type: "plain_text",
|
|
icon: "bot",
|
|
text: "From agent: server::sender\n\nThe delivered message.",
|
|
});
|
|
});
|
|
|
|
it("uses an opaque sender ID when no title is available", () => {
|
|
const presentation = buildToolCallPresentation({
|
|
toolName: "agent_message",
|
|
status: "completed",
|
|
error: null,
|
|
agentMessage: { event: "message", sender: { id: "remote::worker" }, text: "Hello" },
|
|
resolveIcon: fakeResolveIcon,
|
|
});
|
|
expect(presentation.displayName).toBe("Message from remote::worker");
|
|
});
|