Handle skill tool details and skip synthetic timeline events

This commit is contained in:
Mohamed Boudra
2026-02-23 18:06:38 +07:00
parent ff4f2eeb56
commit cbf5621a73
8 changed files with 86 additions and 0 deletions

View File

@@ -245,6 +245,14 @@ export function ToolCallDetailsContent({
<Text selectable style={styles.scrollText}>{detail.query}</Text>
</View>
);
} else if (detail?.type === "plain_text") {
if (detail.text) {
sections.push(
<View key="plain-text" style={styles.plainTextSection}>
<Text selectable style={styles.plainText}>{detail.text}</Text>
</View>
);
}
} else if (detail?.type === "unknown") {
const plainInputText =
typeof detail.input === "string" && detail.output === null

View File

@@ -6,6 +6,7 @@ import {
MicVocal,
Pencil,
Search,
Sparkles,
SquareTerminal,
Wrench,
} from "lucide-react-native";
@@ -21,6 +22,7 @@ const TOOL_DETAIL_ICONS: Record<ToolCallDetail["type"], ToolCallIconComponent> =
search: Search,
worktree_setup: SquareTerminal,
sub_agent: Bot,
plain_text: Wrench,
unknown: Wrench,
};
@@ -37,6 +39,9 @@ export function resolveToolCallIcon(toolName: string, detail?: ToolCallDetail):
if (lowerName === "task") {
return Bot;
}
if (lowerName === "skill") {
return Sparkles;
}
if (detail) {
return TOOL_DETAIL_ICONS[detail.type];

View File

@@ -160,6 +160,11 @@ export type ToolCallDetail =
summary?: string;
}>;
}
| {
type: "plain_text";
label?: string;
text?: string;
}
| {
type: "unknown";
input: unknown | null;

View File

@@ -1460,6 +1460,28 @@ describe("convertClaudeHistoryEntry", () => {
expect(result).toEqual([]);
});
test("skips synthetic user entries", () => {
const entry = {
type: "user",
isSynthetic: true,
message: {
role: "user",
content: [
{
type: "text",
text: "Base directory for this skill: /tmp/skill",
},
],
},
};
const mapBlocks = vi.fn().mockReturnValue([]);
const result = convertClaudeHistoryEntry(entry, mapBlocks);
expect(result).toEqual([]);
expect(mapBlocks).not.toHaveBeenCalled();
});
test("passes thinking blocks to mapBlocks for assistant entries", () => {
const entry = {
type: "assistant",

View File

@@ -1344,9 +1344,19 @@ function isMetadataOnlySdkMessage(message: SDKMessage): boolean {
if (message.type !== "user") {
return false;
}
if (isSyntheticUserEntry(message)) {
return true;
}
return isTaskNotificationUserContent(message.message?.content);
}
function isSyntheticUserEntry(entry: unknown): boolean {
if (!entry || typeof entry !== "object") {
return false;
}
return (entry as { isSynthetic?: unknown }).isSynthetic === true;
}
export function readEventIdentifiers(message: SDKMessage): EventIdentifiers {
const root = message as unknown as Record<string, unknown>;
const messageType = readTrimmedString(root.type);
@@ -3429,6 +3439,9 @@ class ClaudeAgentSession implements AgentSession {
}
break;
case "user": {
if (isSyntheticUserEntry(message)) {
break;
}
if (this.compacting) {
this.compacting = false;
break;
@@ -4380,6 +4393,9 @@ export function convertClaudeHistoryEntry(
if (entry.isCompactSummary) {
return [];
}
if (entry.type === "user" && isSyntheticUserEntry(entry)) {
return [];
}
const message = entry?.message;
if (!message || !("content" in message)) {
@@ -4556,6 +4572,9 @@ async function parseClaudeSessionDescriptor(
if (entry?.isSidechain) {
continue;
}
if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
continue;
}
if (!sessionId && typeof entry.sessionId === "string") {
sessionId = entry.sessionId;
}

View File

@@ -102,6 +102,25 @@ const ClaudeToolDetailPass2Schema = z.union([
toolDetailBranchByName("glob", ToolSearchInputSchema, z.unknown(), (input) =>
toSearchToolDetail(input)
),
toolDetailBranchByName(
"Skill",
z.object({ skill: z.string() }).passthrough(),
z.union([
z.object({ output: z.string() }).passthrough().transform((value) => value.output),
z.string(),
]).nullable(),
(input, output) => {
const skillName = input?.skill;
if (!skillName) {
return undefined;
}
return {
type: "plain_text" as const,
label: skillName,
...(output ? { text: output } : {}),
} satisfies ToolCallDetail;
}
),
ClaudeSpeakToolDetailSchema,
]);

View File

@@ -210,6 +210,11 @@ const ToolCallDetailPayloadSchema: z.ZodType<ToolCallDetail> = z.discriminatedUn
})
),
}),
z.object({
type: z.literal('plain_text'),
label: z.string().optional(),
text: z.string().optional(),
}),
z.object({
type: z.literal('unknown'),
input: UnknownValueSchema,

View File

@@ -91,6 +91,9 @@ export function buildToolCallDisplayModel(input: ToolCallDisplayInput): ToolCall
displayName = readString(input.detail.subAgentType) ?? "Task";
summary = readString(input.detail.description);
break;
case "plain_text":
summary = input.detail.label;
break;
case "unknown":
break;
}