Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/ultra/__tests__/call.test.ts

Raw
import { stripVTControlCharacters } from "node:util";
import {
	initTheme,
	type Theme,
	ToolExecutionComponent,
} from "@earendil-works/pi-coding-agent";
import { type TUI, visibleWidth } from "@earendil-works/pi-tui";
import { beforeAll, describe, expect, it, vi } from "vitest";
import { renderUltraCall } from "../call.ts";
import { initialReducerState, reduceProgress } from "../progress.ts";
import { renderUltraResult } from "../ui.ts";

beforeAll(() => initTheme());
const theme = {
	fg: (_color: string, text: string) => text,
	bold: (text: string) => text,
} as Theme;
const drafting = {
	expanded: false,
	argsComplete: false,
	executionStarted: false,
	isPartial: true,
	isError: false,
};
const request = {
	spec: {
		name: "auth-review",
		description: "Review authentication boundaries.",
		phases: [
			{
				id: "inspect",
				kind: "fanout",
				over: ["tokens", "sessions", "permissions"],
				step: {
					summary: "Inspect authentication boundaries",
					prompt:
						"FIRST-PROMPT\nCheck {item}.\nReport evidence-backed findings.",
					model: "small",
					tools: ["read", "grep", "ls"],
				},
			},
			{
				id: "synthesize",
				kind: "single",
				step: {
					summary: "Consolidate verified findings",
					prompt: "Combine independent findings. Preserve evidence.",
					model: "large",
					thinkingLevel: "high",
					tools: ["read", "grep"],
				},
			},
		],
		return: "{synthesize.results}",
	},
};

function lines(args: unknown, options = drafting, width = 80) {
	return renderUltraCall(args, theme, options)
		.render(width)
		.map(stripVTControlCharacters);
}

function host(args: unknown = {}) {
	return new ToolExecutionComponent(
		"run_workflow",
		"call-1",
		args,
		{},
		{ renderCall: renderUltraCall, renderResult: renderUltraResult },
		{ requestRender: vi.fn() } as unknown as TUI,
		process.cwd(),
	);
}

function screen(component: ToolExecutionComponent) {
	return component.render(100).map(stripVTControlCharacters).join("\n");
}

describe("streaming workflow call", () => {
	it("renders each partial argument update, without validation or pretending to run agents", () => {
		const row = host();
		expect(screen(row)).toContain("Receiving workflow arguments…");
		row.updateArgs({ spec: { name: "auth-" } });
		expect(screen(row)).toContain("run_workflow auth-");
		expect(screen(row)).toContain("0 phases received");
		row.updateArgs({
			spec: { name: "auth-review", phases: [{ id: "inspect" }] },
		});
		expect(screen(row)).toContain("1 phase received");
		expect(screen(row)).toContain("kind not received");
		expect(screen(row)).toContain("Summary not received");
		row.updateArgs(request);
		const text = screen(row);
		expect(text).toContain("2 phases received · no agents running");
		expect(text).toContain("inspect · fanout · 3 items received · small");
		expect(text).toContain("Consolidate verified findings");
		expect(text).not.toContain("Prompt tail · synthesize");
		expect(text).not.toContain(
			"Combine independent findings. Preserve evidence.",
		);
		expect(text).not.toContain("0/0 done");
		expect(text).not.toContain("FIRST-PROMPT");
	});

	it("keeps arriving prompt chunks expansion-only and never schedules animation", () => {
		const interval = vi.spyOn(globalThis, "setInterval");
		const timeout = vi.spyOn(globalThis, "setTimeout");
		try {
			const args = { spec: { phases: [{ step: { prompt: "Inspect" } }] } };
			expect(lines(args).join("\n")).not.toContain("Inspect");
			expect(lines(args, { ...drafting, expanded: true }).join("\n")).toContain(
				"Inspect",
			);
			args.spec.phases[0].step.prompt += " authentication";
			const component = renderUltraCall(args, theme, {
				...drafting,
				expanded: true,
			});
			expect(component.render(80).join("\n")).toContain(
				"Inspect authentication",
			);
			component.invalidate();
			expect(component.render(80)).toEqual(component.render(80));
			expect(interval).not.toHaveBeenCalled();
			expect(timeout).not.toHaveBeenCalled();
		} finally {
			interval.mockRestore();
			timeout.mockRestore();
		}
	});

	it("distinguishes complete arguments from validated or running work", () => {
		const row = host(request);
		row.setArgsComplete();
		const text = screen(row);
		expect(text).toContain("Arguments received · 2 phases received");
		expect(text).toContain("Awaiting validation and execution.");
		expect(text).not.toContain("▎");
		expect(text).not.toContain("Drafting");
	});

	it("hands off to the real result board and keeps received prompts available on expansion", () => {
		const row = host(request);
		row.setArgsComplete();
		row.markExecutionStarted();
		expect(screen(row)).not.toContain("Prompt tail");
		expect(screen(row)).not.toContain("Consolidate verified findings");
		const details = reduceProgress(
			initialReducerState({ workflowName: "auth-review" }),
			{
				kind: "start",
				phase: "inspect",
				agentId: "inspect#0",
			},
		);
		row.updateResult({ content: [], details, isError: false }, true);
		expect(screen(row)).toContain("1 active");
		expect(screen(row)).not.toContain("Drafting workflow");
		row.setExpanded(true);
		const expanded = screen(row);
		expect(expanded).toContain("Received workflow");
		expect(expanded).toContain("FIRST-PROMPT");
		expect(expanded).toContain("Check {item}.");
		expect(expanded).toContain("tools: read, grep, ls");
		expect(expanded).toContain("thinking: high");
		expect(expanded).toContain("return: {synthesize.results}");
		row.updateResult({ content: [], details, isError: false });
		expect(screen(row)).toContain("FIRST-PROMPT");
		row.setExpanded(false);
		expect(screen(row)).not.toContain("FIRST-PROMPT");
		expect(screen(row)).not.toContain("Combine independent findings");
	});

	it.each([true, false])(
		"clears drafting on failure, including pre-execution validation (started: %s)",
		(started) => {
			const row = host(request);
			if (started) row.markExecutionStarted();
			row.updateResult({
				content: [
					{ type: "text", text: "Invalid workflow spec: missing summary" },
				],
				isError: true,
			});
			const text = screen(row);
			expect(text).toContain(
				"ultra · failed: Invalid workflow spec: missing summary",
			);
			expect(text).not.toContain("Drafting workflow");
			expect(text).not.toContain("Prompt tail");
			expect(text).not.toContain("0/0 done");
			row.setExpanded(true);
			expect(screen(row)).toContain("FIRST-PROMPT");
			expect(screen(row)).toContain("Invalid workflow spec: missing summary");
		},
	);

	it("restores a completed call without reviving the drafting preview", () => {
		const row = host(request);
		row.updateResult({
			content: [],
			details: initialReducerState(),
			isError: false,
		});
		expect(screen(row)).not.toContain("Drafting workflow");
		expect(screen(row)).not.toContain("Prompt tail");
		row.setExpanded(true);
		expect(screen(row)).toContain("Received workflow");
		expect(screen(row)).toContain("FIRST-PROMPT");
	});

	it("bounds collapsed phases and keeps prompt bodies expansion-only", () => {
		const phases = Array.from({ length: 20 }, (_, index) => ({
			id: `phase_${index}`,
			kind: "single",
			step: {
				summary: `Summary ${index}`,
				prompt: `OLD-PROMPT-${index}\n${"x".repeat(100_000)}\nLATEST-TAIL`,
			},
		}));
		const output = lines({ spec: { name: "large", phases } }, drafting, 240);
		expect(output.length).toBeLessThanOrEqual(13);
		const text = output.join("\n");
		expect(text).toContain("20 phases received");
		expect(text).toContain("17 earlier phases");
		expect(text).not.toContain("Summary 16");
		expect(text).toContain("Summary 17");
		expect(text).toContain("Summary 19");
		expect(text).not.toContain("LATEST-TAIL");
		expect(text).not.toContain("OLD-PROMPT");
		expect(text).toContain("to expand");
		const expanded = lines(
			{ spec: { phases } },
			{ ...drafting, expanded: true },
			240,
		).join("\n");
		expect(expanded).toContain("Summary 0");
		expect(expanded).toContain("OLD-PROMPT-0");
		expect(expanded).toContain("OLD-PROMPT-19");
		expect(expanded).toContain("LATEST-TAIL");
	});

	it("labels dynamic and conditional fanouts without evaluating selectors", () => {
		const args = {
			spec: {
				phases: [
					{
						id: "verify",
						kind: "fanout",
						over: "{inspect.results}",
						when: "{inspect.results}",
						step: { summary: "Verify {item}" },
					},
				],
			},
		};
		const text = lines(args).join("\n");
		expect(text).toContain("dynamic items · conditional");
		expect(text).toContain("Verify {item}");
		expect(text).not.toContain("0 items");
		const expanded = lines(args, { ...drafting, expanded: true }).join("\n");
		expect(expanded).toContain("over: {inspect.results}");
		expect(expanded).toContain("when: {inspect.results}");
	});

	it("renders saved names and input counts without loading a workflow or exposing input bodies", () => {
		const text = lines({
			name: "does-not-exist",
			args: { question: "PRIVATE-INPUT", source: "repo" },
		}).join("\n");
		expect(text).toContain("run_workflow does-not-exist");
		expect(text).toContain("Saved workflow · 2 inputs received");
		expect(text).not.toContain("PRIVATE-INPUT");
		expect(text).not.toContain("unknown workflow");
		expect(lines({ name: "ignored", ...request }).join("\n")).not.toContain(
			"ignored",
		);
	});

	it.each([
		undefined,
		null,
		[],
		"broken",
		42,
		{},
		{ spec: null },
		{ spec: [] },
		{ spec: { name: {}, phases: "not-an-array" } },
		{
			spec: {
				phases: [
					null,
					1,
					"partial",
					[],
					{},
					{ id: [], kind: {}, step: null },
					{ step: { summary: {}, prompt: [], tools: [null, {}] } },
				],
			},
		},
	])(
		"tolerates incomplete and malformed args without renderer fallback: %j",
		(args) => {
			for (const expanded of [true, false]) {
				expect(() => lines(args, { ...drafting, expanded })).not.toThrow();
				expect(lines(args, { ...drafting, expanded })[0]).toContain(
					"run_workflow",
				);
			}
		},
	);

	it("stays within narrow terminal widths and sanitizes control bytes from all fields", () => {
		const args = {
			spec: {
				name: "name\n\x1b[31mred\x1b[0m",
				phases: [
					{
						id: "phase\nnext",
						kind: "single",
						step: {
							summary: "wide 界界 and emoji 🔍🔍\nsummary",
							model: "small\r\x00",
							tools: ["read\x07"],
							prompt: "line\twith\x1b[2Jcontrols\nUnicode 界界🔍🔍\x07",
						},
					},
				],
			},
		};
		for (const width of [1, 2, 8, 24, 80]) {
			for (const expanded of [true, false]) {
				const output = lines(args, { ...drafting, expanded }, width);
				expect(output.every((text) => visibleWidth(text) <= width)).toBe(true);
				expect(output.join("\n")).not.toMatch(/[\x00\x07\r\t]/);
			}
		}
		expect(lines(args)[0]).toBe("run_workflow name red");
	});
});