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}`;
+}