mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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>
|
||||
</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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -160,6 +160,11 @@ export type ToolCallDetail =
|
||||
summary?: string;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
type: "plain_text";
|
||||
label?: string;
|
||||
text?: string;
|
||||
}
|
||||
| {
|
||||
type: "unknown";
|
||||
input: unknown | null;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user