Files
penguin-harness/packages/cli/test/tool-render.test.ts
T
Yaowei Zheng 45bfae6e94 Initialize repository with harness code and assets
Initial import of all source code, config, and README assets: the
packages workspace (cli, core, server, web, docs, landing, skills),
build scripts, tooling config, and CI workflows.

Includes the data-layout revision made on this branch: the local data
root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the
installer keeps its binaries in ~/.penguin), and every Agent lives
under <project>/agents/<agent>/ — path helpers, the three
agent-enumeration scans, the system prompt, built-in Skills, tests
and docs all follow the new layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
2026-07-19 14:06:53 +08:00

90 lines
4.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { renderPartialToolCall } from "../src/tool-render.js";
describe("renderPartialToolCall", () => {
it("renders partial exec_command args as $ <cmd-so-far>", () => {
expect(renderPartialToolCall("exec_command", '{"cmd":')).toBeNull();
expect(renderPartialToolCall("exec_command", '{"cmd":"l')).toBe("$ l");
expect(renderPartialToolCall("exec_command", '{"cmd":"ls"}')).toBe("$ ls");
expect(renderPartialToolCall("exec_command", '{"cmd":"echo \\"hi\\"')).toBe('$ echo "hi"');
});
it("renders run_subagent as run_subagent << <prompt>, folded to one line", () => {
expect(renderPartialToolCall("run_subagent", '{"prompt":')).toBeNull();
expect(renderPartialToolCall("run_subagent", '{"prompt":"analy')).toBe("run_subagent << analy");
expect(renderPartialToolCall("run_subagent", '{"prompt":"line1\\nline2"}')).toBe(
"run_subagent << line1 line2",
);
});
it("renders input_command polls (empty chars) without a payload", () => {
expect(renderPartialToolCall("input_command", '{"process_id":')).toBeNull();
expect(renderPartialToolCall("input_command", '{"process_id":"proc-1a2b3c4d"}')).toBe(
"⌨ input_command → proc-1a2b3c4d",
);
expect(
renderPartialToolCall("input_command", '{"process_id":"proc-1a2b3c4d","chars":""}'),
).toBe("⌨ input_command → proc-1a2b3c4d");
});
it("renders non-empty input_command chars with visible control characters", () => {
expect(
renderPartialToolCall("input_command", '{"process_id":"proc-1a2b3c4d","chars":"y\\n"}'),
).toBe("⌨ input_command → proc-1a2b3c4d << y\\n");
// U+0003 (Ctrl-C) is rendered in caret notation.
expect(
renderPartialToolCall("input_command", '{"process_id":"proc-1a2b3c4d","chars":"\\u0003"}'),
).toBe("⌨ input_command → proc-1a2b3c4d << ^C");
// Disambiguates literal backslash escapes: chars "a", "\", "n" render as a\\n, distinct from a real newline \n.
expect(
renderPartialToolCall("input_command", '{"process_id":"proc-1a2b3c4d","chars":"a\\\\n"}'),
).toBe("⌨ input_command → proc-1a2b3c4d << a\\\\n");
});
it("keeps input_command previews append-only across \\uXXXX delta boundaries", () => {
const stages = [
'{"process_id":"proc-1a2b3c4d","chars":"y',
'{"process_id":"proc-1a2b3c4d","chars":"y\\u0',
'{"process_id":"proc-1a2b3c4d","chars":"y\\u0003',
];
const previews = stages.map((s) => renderPartialToolCall("input_command", s)!);
expect(previews[0]).toBe("⌨ input_command → proc-1a2b3c4d << y");
// An incomplete \u escape is treated as "stop here" rather than emitting the raw hex as literal text.
expect(previews[1]).toBe("⌨ input_command → proc-1a2b3c4d << y");
expect(previews[2]).toBe("⌨ input_command → proc-1a2b3c4d << y^C");
for (let i = 1; i < previews.length; i++) {
expect(previews[i]!.startsWith(previews[i - 1]!)).toBe(true);
}
});
it("renders input_subagent polls without a payload and follow-up prompts with one", () => {
expect(
renderPartialToolCall("input_subagent", '{"subagent_id":"subagent-9f8e7d6c","prompt":""}'),
).toBe("⌨ input_subagent → subagent-9f8e7d6c");
expect(
renderPartialToolCall(
"input_subagent",
'{"subagent_id":"subagent-9f8e7d6c","prompt":"continue with the tests"}',
),
).toBe("⌨ input_subagent → subagent-9f8e7d6c << continue with the tests");
});
it("truncates long payload previews and stops growing afterwards", () => {
const long = "x".repeat(130);
const capped = renderPartialToolCall(
"input_subagent",
`{"subagent_id":"subagent-9f8e7d6c","prompt":"${long}"}`,
);
expect(capped).toBe(`⌨ input_subagent → subagent-9f8e7d6c << ${"x".repeat(120)}…`);
const longer = renderPartialToolCall(
"input_subagent",
`{"subagent_id":"subagent-9f8e7d6c","prompt":"${long}yyy"}`,
);
expect(longer).toBe(capped);
});
it("falls back to name(args-prefix) for unknown tools", () => {
expect(renderPartialToolCall("search", '{"q":"hi')).toBe('search({"q":"hi');
});
});