45bfae6e94
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
90 lines
4.1 KiB
TypeScript
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');
|
|
});
|
|
});
|