@esso0428/pi-subagents 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
2
+ import { Markdown, Text, type Component } from "@earendil-works/pi-tui";
3
+ import type { Theme } from "./agent-widget.js";
4
+
5
+ const TRUNCATION_MARKER = "… (more output available)";
6
+
7
+ /**
8
+ * Render a child result as Markdown while keeping notification output bounded.
9
+ *
10
+ * Markdown is rendered at paint time so the TUI's current width controls wrapping.
11
+ * If the host's Markdown implementation is unavailable or throws, the original
12
+ * text is rendered through Text instead of making the result disappear.
13
+ */
14
+ class CappedMarkdownResult implements Component {
15
+ private readonly text: string;
16
+ private readonly theme: Theme;
17
+ private readonly maxLines: number;
18
+ private markdown: Component | undefined;
19
+
20
+ constructor(text: string, theme: Theme, maxLines: number) {
21
+ this.text = text;
22
+ this.theme = theme;
23
+ this.maxLines = Math.max(0, Math.floor(maxLines));
24
+
25
+ try {
26
+ this.markdown = new Markdown(text, 0, 0, getMarkdownTheme());
27
+ } catch {
28
+ // A mismatched host Pi version should still leave the child output visible.
29
+ }
30
+ }
31
+
32
+ invalidate(): void {
33
+ this.markdown?.invalidate();
34
+ }
35
+
36
+ render(width: number): string[] {
37
+ let lines: string[];
38
+ try {
39
+ lines = this.markdown?.render(width) ?? new Text(this.text, 0, 0).render(width);
40
+ } catch {
41
+ lines = new Text(this.text, 0, 0).render(width);
42
+ }
43
+
44
+ if (lines.length <= this.maxLines) return lines;
45
+ if (this.maxLines === 0) return [];
46
+
47
+ return [
48
+ ...lines.slice(0, this.maxLines - 1),
49
+ this.theme.fg("muted", TRUNCATION_MARKER),
50
+ ];
51
+ }
52
+ }
53
+
54
+ export function createMarkdownResult(text: string, theme: Theme, maxLines: number): Component {
55
+ return new CappedMarkdownResult(text, theme, maxLines);
56
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMarkdownResult } from "../../src/ui/markdown-result.js";
3
+
4
+ const theme = {
5
+ fg: (_name: string, text: string) => text,
6
+ bold: (text: string) => text,
7
+ };
8
+
9
+ describe("createMarkdownResult", () => {
10
+ it("renders headings, lists, inline code, and fenced code", () => {
11
+ const component = createMarkdownResult(
12
+ [
13
+ "# Result",
14
+ "",
15
+ "- first",
16
+ "- `inline()`",
17
+ "",
18
+ "```ts",
19
+ "const answer: number = 42;",
20
+ "```",
21
+ ].join("\n"),
22
+ theme,
23
+ 50,
24
+ );
25
+
26
+ const rendered = component.render(100).join("\n");
27
+ expect(rendered).toContain("Result");
28
+ expect(rendered).toContain("first");
29
+ expect(rendered).toContain("inline()");
30
+ expect(rendered).toContain("answer");
31
+ });
32
+
33
+ it("caps rendered lines and adds a truncation marker", () => {
34
+ const component = createMarkdownResult("line\n".repeat(20), theme, 5);
35
+ const rendered = component.render(80);
36
+
37
+ expect(rendered).toHaveLength(5);
38
+ expect(rendered.at(-1)).toContain("more");
39
+ });
40
+
41
+ it("keeps unlabelled code blocks safe without language guessing", () => {
42
+ const component = createMarkdownResult("```\nplain text\n```", theme, 20);
43
+ expect(component.render(80).join("\n")).toContain("plain text");
44
+ });
45
+ });