diff --git a/packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx b/packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx index 46ae7513f0..e96ed83084 100644 --- a/packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx +++ b/packages/ui/src/features/sessions/components/chat-thread/ChatThread.tsx @@ -91,6 +91,7 @@ import { MentionChip, parseFileMentions, } from "@posthog/ui/features/sessions/components/session-update/parseFileMentions"; +import { collapsePiSkillInvocation } from "@posthog/ui/features/sessions/components/session-update/piSkillInvocation"; import { SessionUpdateView } from "@posthog/ui/features/sessions/components/session-update/SessionUpdateView"; import { UserShellExecuteView } from "@posthog/ui/features/sessions/components/session-update/UserShellExecuteView"; import { UserMessageAttachments } from "@posthog/ui/features/sessions/components/UserMessageAttachments"; @@ -361,9 +362,9 @@ function UserBubble({ () => extractCustomInstructions(afterCanvasInstructions), [afterCanvasInstructions], ); - const displayContent = customInstructions - ? customInstructions.stripped - : afterCanvasInstructions; + const displayContent = collapsePiSkillInvocation( + customInstructions ? customInstructions.stripped : afterCanvasInstructions, + ); const showChannelContextTag = !!channelContext && bluebirdEnabled; const showCanvasInstructionsTag = !!canvasInstructions && bluebirdEnabled; const showHeaderChips = showChannelContextTag || showCanvasInstructionsTag; diff --git a/packages/ui/src/features/sessions/components/session-update/UserMessage.test.tsx b/packages/ui/src/features/sessions/components/session-update/UserMessage.test.tsx index f78b4b091c..6d5f0bf51a 100644 --- a/packages/ui/src/features/sessions/components/session-update/UserMessage.test.tsx +++ b/packages/ui/src/features/sessions/components/session-update/UserMessage.test.tsx @@ -30,6 +30,9 @@ const PROMPT_WITH_CONTEXT = const PROMPT_WITH_CANVAS_INSTRUCTIONS = "add a retention chart\n\n\nauthoring contract\n"; +const PROMPT_WITH_PI_SKILL = + '\nReferences are relative to /skills/code-review.\n\n# Review\n\nInspect the diff.\n\n\nReview this pull request.'; + describe("UserMessage", () => { // useFeatureFlag falls back to import.meta.env.DEV, which is true under // vitest. Pin DEV off in the flag-gating cases so they exercise the flag @@ -79,6 +82,14 @@ describe("UserMessage", () => { expect(screen.queryByText(/channel_context/)).not.toBeInTheDocument(); }); + it("renders Pi skill invocations as a command chip", () => { + renderWithFlags(, true); + + expect(screen.getByText("/code-review")).toBeInTheDocument(); + expect(screen.getByText("Review this pull request.")).toBeInTheDocument(); + expect(screen.queryByText("Inspect the diff.")).not.toBeInTheDocument(); + }); + it("shows the canvas-instructions tag when project-bluebird is enabled", () => { vi.stubEnv("DEV", false); renderWithFlags( diff --git a/packages/ui/src/features/sessions/components/session-update/UserMessage.tsx b/packages/ui/src/features/sessions/components/session-update/UserMessage.tsx index 00cf6ff966..74d7a5dc05 100644 --- a/packages/ui/src/features/sessions/components/session-update/UserMessage.tsx +++ b/packages/ui/src/features/sessions/components/session-update/UserMessage.tsx @@ -24,6 +24,7 @@ import { MentionChip, parseFileMentions, } from "./parseFileMentions"; +import { collapsePiSkillInvocation } from "./piSkillInvocation"; interface UserMessageProps { content: string; @@ -92,9 +93,9 @@ export const UserMessage = memo(function UserMessage({ () => extractCustomInstructions(afterCanvasInstructions), [afterCanvasInstructions], ); - const displayContent = customInstructions - ? customInstructions.stripped - : afterCanvasInstructions; + const displayContent = collapsePiSkillInvocation( + customInstructions ? customInstructions.stripped : afterCanvasInstructions, + ); const showChannelContextTag = !!channelContext && bluebirdEnabled; const showCanvasInstructionsTag = !!canvasInstructions && bluebirdEnabled; const openChannelContextInSplit = usePanelLayoutStore( diff --git a/packages/ui/src/features/sessions/components/session-update/parseFileMentions.test.ts b/packages/ui/src/features/sessions/components/session-update/parseFileMentions.test.ts new file mode 100644 index 0000000000..bd4fda670e --- /dev/null +++ b/packages/ui/src/features/sessions/components/session-update/parseFileMentions.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { collapsePiSkillInvocation } from "./piSkillInvocation"; + +describe("collapsePiSkillInvocation", () => { + it("replaces Pi skill instructions with the command and user request", () => { + expect( + collapsePiSkillInvocation( + '\nReferences are relative to /skills/code-review.\n\n# Review\n\nInspect the diff.\n\n\nReview this pull request.', + ), + ).toBe("/code-review\n\nReview this pull request."); + }); + + it("keeps non-skill messages unchanged", () => { + expect(collapsePiSkillInvocation("Review this pull request.")).toBe( + "Review this pull request.", + ); + }); +}); diff --git a/packages/ui/src/features/sessions/components/session-update/piSkillInvocation.ts b/packages/ui/src/features/sessions/components/session-update/piSkillInvocation.ts new file mode 100644 index 0000000000..efbfd1dd0e --- /dev/null +++ b/packages/ui/src/features/sessions/components/session-update/piSkillInvocation.ts @@ -0,0 +1,15 @@ +import { unescapeXmlAttr } from "@posthog/shared"; + +const PI_SKILL_INVOCATION = + /^\n[\s\S]*?\n<\/skill>(?:\n\n([\s\S]+))?$/; + +export function collapsePiSkillInvocation(content: string): string { + const match = content.match(PI_SKILL_INVOCATION); + if (!match) { + return content; + } + + const name = unescapeXmlAttr(match[1]); + const userMessage = match[2]?.trim(); + return userMessage ? `/${name}\n\n${userMessage}` : `/${name}`; +}