Handle skill tool details and skip synthetic timeline events
This commit is contained in:
@@ -245,6 +245,14 @@ export function ToolCallDetailsContent({
|
|||||||
<Text selectable style={styles.scrollText}>{detail.query}</Text>
|
<Text selectable style={styles.scrollText}>{detail.query}</Text>
|
||||||
</View>
|
</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") {
|
} else if (detail?.type === "unknown") {
|
||||||
const plainInputText =
|
const plainInputText =
|
||||||
typeof detail.input === "string" && detail.output === null
|
typeof detail.input === "string" && detail.output === null
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
MicVocal,
|
MicVocal,
|
||||||
Pencil,
|
Pencil,
|
||||||
Search,
|
Search,
|
||||||
|
Sparkles,
|
||||||
SquareTerminal,
|
SquareTerminal,
|
||||||
Wrench,
|
Wrench,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
@@ -21,6 +22,7 @@ const TOOL_DETAIL_ICONS: Record<ToolCallDetail["type"], ToolCallIconComponent> =
|
|||||||
search: Search,
|
search: Search,
|
||||||
worktree_setup: SquareTerminal,
|
worktree_setup: SquareTerminal,
|
||||||
sub_agent: Bot,
|
sub_agent: Bot,
|
||||||
|
plain_text: Wrench,
|
||||||
unknown: Wrench,
|
unknown: Wrench,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +39,9 @@ export function resolveToolCallIcon(toolName: string, detail?: ToolCallDetail):
|
|||||||
if (lowerName === "task") {
|
if (lowerName === "task") {
|
||||||
return Bot;
|
return Bot;
|
||||||
}
|
}
|
||||||
|
if (lowerName === "skill") {
|
||||||
|
return Sparkles;
|
||||||
|
}
|
||||||
|
|
||||||
if (detail) {
|
if (detail) {
|
||||||
return TOOL_DETAIL_ICONS[detail.type];
|
return TOOL_DETAIL_ICONS[detail.type];
|
||||||
|
|||||||
@@ -160,6 +160,11 @@ export type ToolCallDetail =
|
|||||||
summary?: string;
|
summary?: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "plain_text";
|
||||||
|
label?: string;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "unknown";
|
type: "unknown";
|
||||||
input: unknown | null;
|
input: unknown | null;
|
||||||
|
|||||||
@@ -1460,6 +1460,28 @@ describe("convertClaudeHistoryEntry", () => {
|
|||||||
expect(result).toEqual([]);
|
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", () => {
|
test("passes thinking blocks to mapBlocks for assistant entries", () => {
|
||||||
const entry = {
|
const entry = {
|
||||||
type: "assistant",
|
type: "assistant",
|
||||||
|
|||||||
@@ -1344,9 +1344,19 @@ function isMetadataOnlySdkMessage(message: SDKMessage): boolean {
|
|||||||
if (message.type !== "user") {
|
if (message.type !== "user") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (isSyntheticUserEntry(message)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return isTaskNotificationUserContent(message.message?.content);
|
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 {
|
export function readEventIdentifiers(message: SDKMessage): EventIdentifiers {
|
||||||
const root = message as unknown as Record<string, unknown>;
|
const root = message as unknown as Record<string, unknown>;
|
||||||
const messageType = readTrimmedString(root.type);
|
const messageType = readTrimmedString(root.type);
|
||||||
@@ -3429,6 +3439,9 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "user": {
|
case "user": {
|
||||||
|
if (isSyntheticUserEntry(message)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (this.compacting) {
|
if (this.compacting) {
|
||||||
this.compacting = false;
|
this.compacting = false;
|
||||||
break;
|
break;
|
||||||
@@ -4380,6 +4393,9 @@ export function convertClaudeHistoryEntry(
|
|||||||
if (entry.isCompactSummary) {
|
if (entry.isCompactSummary) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
if (entry.type === "user" && isSyntheticUserEntry(entry)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const message = entry?.message;
|
const message = entry?.message;
|
||||||
if (!message || !("content" in message)) {
|
if (!message || !("content" in message)) {
|
||||||
@@ -4556,6 +4572,9 @@ async function parseClaudeSessionDescriptor(
|
|||||||
if (entry?.isSidechain) {
|
if (entry?.isSidechain) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (entry?.type === "user" && isSyntheticUserEntry(entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!sessionId && typeof entry.sessionId === "string") {
|
if (!sessionId && typeof entry.sessionId === "string") {
|
||||||
sessionId = entry.sessionId;
|
sessionId = entry.sessionId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,25 @@ const ClaudeToolDetailPass2Schema = z.union([
|
|||||||
toolDetailBranchByName("glob", ToolSearchInputSchema, z.unknown(), (input) =>
|
toolDetailBranchByName("glob", ToolSearchInputSchema, z.unknown(), (input) =>
|
||||||
toSearchToolDetail(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,
|
ClaudeSpeakToolDetailSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
z.object({
|
||||||
type: z.literal('unknown'),
|
type: z.literal('unknown'),
|
||||||
input: UnknownValueSchema,
|
input: UnknownValueSchema,
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ export function buildToolCallDisplayModel(input: ToolCallDisplayInput): ToolCall
|
|||||||
displayName = readString(input.detail.subAgentType) ?? "Task";
|
displayName = readString(input.detail.subAgentType) ?? "Task";
|
||||||
summary = readString(input.detail.description);
|
summary = readString(input.detail.description);
|
||||||
break;
|
break;
|
||||||
|
case "plain_text":
|
||||||
|
summary = input.detail.label;
|
||||||
|
break;
|
||||||
case "unknown":
|
case "unknown":
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user