Render sub-agent activity from log

This commit is contained in:
Mohamed Boudra
2026-05-02 17:20:22 +07:00
parent 771f164e26
commit 49e3011a28
21 changed files with 208 additions and 303 deletions
@@ -244,31 +244,62 @@ function resolveSubAgentFallbackHeader(
interface SubAgentDetailProps {
log: string;
actions: Extract<ToolCallDetail, { type: "sub_agent" }>["actions"];
childSessionId: string | null | undefined;
subAgentType: string | null | undefined;
description: string | null | undefined;
ds: DetailStyles;
}
function buildSubAgentActionLog(actions: SubAgentDetailProps["actions"]): string {
return actions
.map((action) =>
action.summary ? `[${action.toolName}] ${action.summary}` : `[${action.toolName}]`,
)
.join("\n");
interface SubAgentActivityRow {
index: number;
toolName: string;
summary?: string;
}
function stripSubAgentActionLog(log: string, actions: SubAgentDetailProps["actions"]): string {
const actionLog = buildSubAgentActionLog(actions);
const trimmedLog = log.replace(/^\n+/, "");
if (!actionLog || !trimmedLog.startsWith(actionLog)) {
return trimmedLog;
interface ParsedSubAgentLog {
actions: SubAgentActivityRow[];
remainingLog: string;
}
function parseBracketedSubAgentLine(line: string, index: number): SubAgentActivityRow | null {
const match = line.match(/^\[([^\]]+)\](?:\s+(.*))?$/);
if (!match) {
return null;
}
return trimmedLog.slice(actionLog.length).replace(/^\n+/, "");
const toolName = match[1]?.trim();
if (!toolName) {
return null;
}
const summary = match[2]?.trim();
return {
index,
toolName,
...(summary ? { summary } : {}),
};
}
function SubAgentActionRow({ action }: { action: SubAgentDetailProps["actions"][number] }) {
function parseSubAgentLog(log: string): ParsedSubAgentLog {
const actions: SubAgentActivityRow[] = [];
const remainingLines: string[] = [];
for (const line of log.replace(/^\n+/, "").split("\n")) {
const normalizedLine = line.trim();
if (!normalizedLine) {
continue;
}
const parsedAction = parseBracketedSubAgentLine(normalizedLine, actions.length + 1);
if (parsedAction) {
actions.push(parsedAction);
} else {
remainingLines.push(line);
}
}
return {
actions,
remainingLog: remainingLines.join("\n").replace(/^\n+/, ""),
};
}
function SubAgentActionRow({ action }: { action: SubAgentActivityRow }) {
return (
<View style={styles.subAgentActionRow}>
<Text selectable style={styles.subAgentActionTool}>
@@ -324,13 +355,12 @@ function SubAgentLogText({
function SubAgentDetailSection({
log,
actions,
childSessionId,
subAgentType,
description,
ds,
}: SubAgentDetailProps) {
const activityLog = stripSubAgentActionLog(log, actions);
const { actions, remainingLog } = useMemo(() => parseSubAgentLog(log), [log]);
const fallbackHeader = resolveSubAgentFallbackHeader(subAgentType, description);
const hasActions = actions.length > 0;
return (
@@ -363,7 +393,7 @@ function SubAgentDetailSection({
</View>
) : null}
<SubAgentLogText
activityLog={activityLog}
activityLog={remainingLog}
fallbackHeader={fallbackHeader}
hasActions={hasActions}
/>
@@ -624,7 +654,6 @@ function buildDetailSections(
<SubAgentDetailSection
key="sub-agent"
log={detail.log}
actions={detail.actions}
childSessionId={detail.childSessionId}
subAgentType={detail.subAgentType}
description={detail.description}
@@ -844,7 +873,6 @@ const styles = StyleSheet.create((theme) => {
gap: theme.spacing[2],
},
subAgentActionTool: {
minWidth: 56,
fontFamily: Fonts.mono,
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
-24
View File
@@ -367,18 +367,6 @@ describe("stream reducer canonical tool calls", () => {
subAgentType: "Explore",
description: "Inspect repository structure",
log: "[Read] README.md\n[Bash] ls",
actions: [
{
index: 1,
toolName: "Read",
summary: "README.md",
},
{
index: 2,
toolName: "Bash",
summary: "ls",
},
],
},
}),
timestamp: new Date("2025-01-01T10:12:00Z"),
@@ -406,18 +394,6 @@ describe("stream reducer canonical tool calls", () => {
subAgentType: "Explore",
description: "Inspect repository structure",
log: "[Read] README.md\n[Bash] ls",
actions: [
{
index: 1,
toolName: "Read",
summary: "README.md",
},
{
index: 2,
toolName: "Bash",
summary: "ls",
},
],
});
const display = buildToolCallDisplayModel({
@@ -49,9 +49,7 @@ function hasMeaningfulWorktreeSetupDetail(
function hasMeaningfulSubAgentDetail(
detail: Extract<ToolCallDetail, { type: "sub_agent" }>,
): boolean {
return Boolean(
detail.subAgentType || detail.description || detail.log || detail.actions.length > 0,
);
return Boolean(detail.subAgentType || detail.description || detail.log);
}
function hasMeaningfulPlainTextDetail(
@@ -48,13 +48,6 @@ describe("tool-call-display", () => {
subAgentType: "Explore",
description: "Inspect repository structure",
log: "[Read] README.md",
actions: [
{
index: 1,
toolName: "Read",
summary: "README.md",
},
],
},
});
@@ -23,7 +23,6 @@ describe("tool-call-icon", () => {
subAgentType: "Explore",
description: "Inspect repository",
log: "[Read] README.md",
actions: [{ index: 1, toolName: "Read", summary: "README.md" }],
});
expect(icon).toBe(iconMocks.Bot);