feat(ui): render subagent results as markdown
This commit is contained in:
54
index.ts
54
index.ts
@@ -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),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user