feat(ui): render subagent results as markdown

This commit is contained in:
2026-05-12 13:29:06 -04:00
parent 9488aab237
commit d5f432f4cd

View File

@@ -11,7 +11,7 @@ import {
parseFrontmatter, parseFrontmatter,
withFileMutationQueue, withFileMutationQueue,
} from "@mariozechner/pi-coding-agent"; } from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui"; import { Markdown, Text, type MarkdownTheme } from "@mariozechner/pi-tui";
import { Type } from "typebox"; import { Type } from "typebox";
interface PromptConfig { interface PromptConfig {
@@ -62,6 +62,27 @@ const PROMPTS_DIR = path.join(EXTENSION_DIR, "prompts");
const FINALIZE_TOOL_NAME = "subagent_finalize"; const FINALIZE_TOOL_NAME = "subagent_finalize";
const MAX_FINALIZE_RETRIES = 2; const MAX_FINALIZE_RETRIES = 2;
// Markdown Theme - Render finalized subagent text with Pi's terminal markdown component.
function getSubagentMarkdownTheme(theme: Theme): MarkdownTheme {
return {
heading: (text) => theme.fg("mdHeading", text),
link: (text) => theme.fg("mdLink", text),
linkUrl: (text) => theme.fg("mdLinkUrl", text),
code: (text) => theme.fg("mdCode", text),
codeBlock: (text) => theme.fg("mdCodeBlock", text),
codeBlockBorder: (text) => theme.fg("mdCodeBlockBorder", text),
quote: (text) => theme.fg("mdQuote", text),
quoteBorder: (text) => theme.fg("mdQuoteBorder", text),
hr: (text) => theme.fg("mdHr", text),
listBullet: (text) => theme.fg("mdListBullet", text),
bold: (text) => theme.bold(text),
italic: (text) => theme.italic(text),
strikethrough: (text) => theme.strikethrough(text),
underline: (text) => theme.underline(text),
codeBlockIndent: " ",
};
}
// Format Tool Content - Some clients hide structured details from the model. // Format Tool Content - Some clients hide structured details from the model.
function formatSubagentContent( function formatSubagentContent(
status: "SUCCESS" | "ERROR", status: "SUCCESS" | "ERROR",
@@ -73,7 +94,9 @@ function formatSubagentContent(
if (error?.trim()) header.push(`**Error:** ${error.trim()}`); if (error?.trim()) header.push(`**Error:** ${error.trim()}`);
const body = result?.trimEnd(); const body = result?.trimEnd();
return body ? `${header.join(" \n")}\n\n---\n\n${body}` : header.join(" \n"); return body
? `${header.join(" \n")}\n\n---\n\n${body}`
: header.join(" \n");
} }
// Parse Tool List - Frontmatter may use YAML arrays or comma-delimited strings. // Parse Tool List - Frontmatter may use YAML arrays or comma-delimited strings.
@@ -180,7 +203,9 @@ function getSubagentSessionPath(
} }
// Validate Finalize Payload - Keep the parent contract strict and small. // Validate Finalize Payload - Keep the parent contract strict and small.
function validateFinalizePayload(value: unknown): SubagentFinalizePayload | null { function validateFinalizePayload(
value: unknown,
): SubagentFinalizePayload | null {
if (!value || typeof value !== "object") return null; if (!value || typeof value !== "object") return null;
const payload = value as Record<string, unknown>; const payload = value as Record<string, unknown>;
if (payload.status === "SUCCESS") { if (payload.status === "SUCCESS") {
@@ -437,7 +462,8 @@ async function runAgent(
); );
const toolName = String(event.toolName ?? "tool"); const toolName = String(event.toolName ?? "tool");
if (toolName === FINALIZE_TOOL_NAME) { if (toolName === FINALIZE_TOOL_NAME) {
result.finalized = validateFinalizePayload(event.args) ?? undefined; result.finalized =
validateFinalizePayload(event.args) ?? undefined;
} }
activeToolIds.add(id); activeToolIds.add(id);
result.status.state = "running"; result.status.state = "running";
@@ -647,7 +673,11 @@ export default function (pi: ExtensionAPI) {
await fs.promises.mkdir(path.dirname(sessionPath), { recursive: true }); await fs.promises.mkdir(path.dirname(sessionPath), { recursive: true });
let result: SubagentResult | null = null; let result: SubagentResult | null = null;
for (let retryCount = 0; retryCount <= MAX_FINALIZE_RETRIES; retryCount += 1) { for (
let retryCount = 0;
retryCount <= MAX_FINALIZE_RETRIES;
retryCount += 1
) {
const task = const task =
retryCount === 0 retryCount === 0
? `Task: ${params.task}` ? `Task: ${params.task}`
@@ -700,7 +730,12 @@ export default function (pi: ExtensionAPI) {
content: [ content: [
{ {
type: "text", type: "text",
text: formatSubagentContent("ERROR", sessionId, undefined, fallback), text: formatSubagentContent(
"ERROR",
sessionId,
undefined,
fallback,
),
}, },
], ],
details: { sessionId }, details: { sessionId },
@@ -760,7 +795,12 @@ export default function (pi: ExtensionAPI) {
} }
const text = result.content[0]; const text = result.content[0];
return new Text(text?.type === "text" ? text.text : "", 0, 0); return new Markdown(
text?.type === "text" ? text.text : "",
0,
0,
getSubagentMarkdownTheme(theme),
);
}, },
}); });
} }