import { type AgentToolResult, initTheme, type Theme, } from "@earendil-works/pi-coding-agent"; import type { Component } from "@earendil-works/pi-tui"; import { beforeAll, describe, expect, it } from "vitest"; import { ephemletRenderers } from "../src/tool-renderer.ts"; beforeAll(() => initTheme("dark", false)); const theme = { fg: (_color: string, text: string) => text, bold: (text: string) => text, } as Theme; const source = "private-source-body"; const state = { selected: "private-state-body" }; const record = { id: "stable-id", title: "Counter", kind: "interactive", status: "ready", revision: 4, source, state, jobId: null, error: null, }; function context( expanded: boolean, lastComponent?: Component, ): Parameters>[2] { return { args: {}, toolCallId: "row", invalidate() {}, lastComponent, state: undefined, cwd: "/fixture", executionStarted: true, argsComplete: true, isPartial: false, expanded, showImages: false, isError: false, durationMs: undefined, outputPad: 0, }; } function output(component: Component): string { return component.render(120).join("\n"); } function result(details: unknown): AgentToolResult { return { content: [{ type: "text", text: "private-fallback-body" }], details, }; } describe("native ephemlet tool rows", () => { it.each(["put", "build", "get", "state", "list", "delete", "show", "hide"])( "keeps %s calls compact, expands args, and reuses the native component", (action) => { const args = { action, id: "stable-id", source, state, brief: "private-brief-body", }; const collapsed = ephemletRenderers.renderCall!( args, theme, context(false), ); expect(output(collapsed)).toContain(`ephemlet ${action}`); expect(output(collapsed)).toContain("to expand"); for (const hidden of [source, state.selected, args.brief]) expect(output(collapsed)).not.toContain(hidden); const expanded = ephemletRenderers.renderCall!( args, theme, context(true, collapsed), ); expect(expanded).toBe(collapsed); for (const visible of [source, state.selected, args.brief]) expect(output(expanded)).toContain(visible); const folded = ephemletRenderers.renderCall!( args, theme, context(false, expanded), ); expect(output(folded)).not.toContain(source); }, ); it.each([ [record, "ready"], [{ ...record, status: "building", jobId: "job-id" }, "building"], [{ ...record, status: "failed", error: "private-error-body" }, "failed"], [{ ephemlets: [record], guidance: "/private-guidance-path" }, "1 ephemlet"], [{ deleted: "stable-id" }, "deleted stable-id"], [{ visible: false, selected: "stable-id" }, "hidden"], [{ visible: true, selected: "stable-id" }, "shown"], ] as const)("keeps result bodies behind native expansion", (data, status) => { const collapsed = ephemletRenderers.renderResult!( result(data), { expanded: false, isPartial: false }, theme, context(false), ); expect(output(collapsed)).toContain(status); expect(output(collapsed)).toContain("to expand"); for (const hidden of [ source, state.selected, "private-error-body", "/private-guidance-path", ]) expect(output(collapsed)).not.toContain(hidden); const expanded = ephemletRenderers.renderResult!( result(data), { expanded: true, isPartial: false }, theme, context(true, collapsed), ); expect(expanded).toBe(collapsed); expect(output(expanded)).toContain("stable-id"); if ("source" in data || "ephemlets" in data) { expect(output(expanded)).toContain(source); expect(output(expanded)).toContain(state.selected); } }); it.each([ undefined, null, 42, "private-malformed-body", [source], { unknown: source }, ])("honors expansion for malformed details %s", (data) => { const collapsed = ephemletRenderers.renderResult!( result(data), { expanded: false, isPartial: false }, theme, context(false), ); expect(output(collapsed)).toContain("finished"); expect(output(collapsed)).toContain("to expand"); expect(output(collapsed)).not.toContain("private-"); const expanded = ephemletRenderers.renderResult!( result(data), { expanded: true, isPartial: false }, theme, context(true), ); const expected = data === undefined || data === null ? "private-fallback-body" : typeof data === "string" || typeof data === "number" ? String(data) : source; expect(output(expanded)).toContain(expected); }); it("honors expansion for progress and errors, including empty error details", () => { const progress = ephemletRenderers.renderResult!( result(record), { expanded: false, isPartial: true }, theme, context(false), ); expect(output(progress)).toContain("working"); expect(output(progress)).not.toContain(source); const expandedProgress = ephemletRenderers.renderResult!( result(record), { expanded: true, isPartial: true }, theme, context(true), ); expect(output(expandedProgress)).toContain(source); const collapsedErrorContext = context(false); collapsedErrorContext.isError = true; const error = ephemletRenderers.renderResult!( result({}), { expanded: false, isPartial: false }, theme, collapsedErrorContext, ); expect(output(error)).toContain("failed"); expect(output(error)).not.toContain("private-fallback-body"); const expandedErrorContext = context(true, error); expandedErrorContext.isError = true; const expandedError = ephemletRenderers.renderResult!( result({}), { expanded: true, isPartial: false }, theme, expandedErrorContext, ); expect(output(expandedError)).toContain("private-fallback-body"); }); it("bounds expanded previews and escapes controls without invoking custom inspectors", () => { let invoked = false; const data = { source: "x".repeat(128 * 1024), state, title: "Counter\x1b[2J\n", [Symbol.for("nodejs.util.inspect.custom")]() { invoked = true; return "untrusted"; }, }; const expanded = ephemletRenderers.renderResult!( result(data), { expanded: true, isPartial: false }, theme, context(true), ); const text = output(expanded); expect(text.length).toBeLessThan(18000); expect(text).toContain("truncated"); expect(text).not.toContain("\x1b"); expect(invoked).toBe(false); const cyclic: Record = {}; cyclic["self"] = cyclic; expect(() => ephemletRenderers.renderResult!( result(cyclic), { expanded: true, isPartial: false }, theme, context(true), ), ).not.toThrow(); }); });