Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/code/src/renderer/components/HeaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CloudGitInteractionHeader } from "@features/git-interaction/components/
import { GitInteractionHeader } from "@features/git-interaction/components/GitInteractionHeader";
import { SidebarTrigger } from "@features/sidebar/components/SidebarTrigger";
import { useSidebarStore } from "@features/sidebar/stores/sidebarStore";
import { SkillButtonsMenu } from "@features/skill-buttons/components/SkillButtonsMenu";
import { useWorkspace } from "@features/workspace/hooks/useWorkspace";
import { Box, Flex } from "@radix-ui/themes";
import { useHeaderStore } from "@stores/headerStore";
Expand Down Expand Up @@ -100,6 +101,9 @@ export function HeaderRow() {
flexShrink: 0,
}}
>
<div className="no-drag">
<SkillButtonsMenu taskId={view.data.id} />
</div>
<div className="no-drag">
{isCloudTask ? (
<CloudGitInteractionHeader taskId={view.data.id} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useSessionForTask,
} from "@features/sessions/stores/sessionStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { SkillButtonActionMessage } from "@features/skill-buttons/components/SkillButtonActionMessage";
import { ArrowDown, XCircle } from "@phosphor-icons/react";
import { Box, Button, Flex, Text } from "@radix-ui/themes";
import type { AcpMessage } from "@shared/types/session-events";
Expand Down Expand Up @@ -167,6 +168,8 @@ export function ConversationView({
);
case "git_action":
return <GitActionMessage actionType={item.actionType} />;
case "skill_button_action":
return <SkillButtonActionMessage buttonId={item.buttonId} />;
case "session_update":
return (
<SessionUpdateRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import type {
} from "@agentclientprotocol/sdk";
import type { QueuedMessage } from "@features/sessions/stores/sessionStore";
import type { SessionUpdate, ToolCall } from "@features/sessions/types";
import {
extractSkillButtonId,
type SkillButtonId,
} from "@features/skill-buttons/prompts";
import { isNotification, POSTHOG_NOTIFICATIONS } from "@posthog/agent";
import {
type AcpMessage,
Expand Down Expand Up @@ -34,6 +38,7 @@ export type ConversationItem =
attachments?: UserMessageAttachment[];
}
| { type: "git_action"; id: string; actionType: GitActionType }
| { type: "skill_button_action"; id: string; buttonId: SkillButtonId }
| {
type: "session_update";
id: string;
Expand Down Expand Up @@ -215,6 +220,7 @@ function handlePromptRequest(
const turnId = `turn-${ts}-${msg.id}`;
const toolCalls = new Map<string, ToolCall>();
const gitAction = parseGitActionMessage(userContent);
const skillButtonId = extractSkillButtonId(userPrompt.blocks);

const childItems = new Map<string, ConversationItem[]>();
const context: TurnContext = {
Expand Down Expand Up @@ -243,6 +249,12 @@ function handlePromptRequest(
id: `${turnId}-git-action`,
actionType: gitAction.actionType,
});
} else if (skillButtonId) {
b.items.push({
type: "skill_button_action",
id: `${turnId}-skill-action`,
buttonId: skillButtonId,
});
} else {
b.items.push({
type: "user_message",
Expand Down Expand Up @@ -421,16 +433,17 @@ function ensureImplicitTurn(b: ItemBuilder, ts: number) {
function extractUserPrompt(params: unknown): {
content: string;
attachments: UserMessageAttachment[];
blocks: ContentBlock[];
} {
const p = params as { prompt?: ContentBlock[] };
if (!p?.prompt?.length) {
return { content: "", attachments: [] };
return { content: "", attachments: [], blocks: [] };
}

const { text, attachments } = extractPromptDisplayContent(p.prompt, {
filterHidden: true,
});
return { content: text, attachments };
return { content: text, attachments, blocks: p.prompt };
}

function getParentToolCallId(update: SessionUpdate): string | undefined {
Expand Down
19 changes: 14 additions & 5 deletions apps/code/src/renderer/features/sessions/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from "@features/sessions/stores/sessionStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
import { taskViewedApi } from "@features/sidebar/hooks/useTaskViewed";
import { extractSkillButtonId } from "@features/skill-buttons/prompts";
import { isNotification, POSTHOG_NOTIFICATIONS } from "@posthog/agent";
import {
getAvailableCodexModes,
Expand Down Expand Up @@ -1323,11 +1324,19 @@ export class SessionService {
pausedDurationMs: 0,
});

sessionStoreSetters.appendOptimisticItem(session.taskRunId, {
type: "user_message",
content: promptText,
timestamp: Date.now(),
});
const skillButtonId = extractSkillButtonId(blocks);
if (skillButtonId) {
sessionStoreSetters.appendOptimisticItem(session.taskRunId, {
type: "skill_button_action",
buttonId: skillButtonId,
});
} else {
sessionStoreSetters.appendOptimisticItem(session.taskRunId, {
type: "user_message",
content: promptText,
timestamp: Date.now(),
});
}

try {
const result = await trpcClient.agent.prompt.mutate({
Expand Down
27 changes: 19 additions & 8 deletions apps/code/src/renderer/features/sessions/stores/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
SessionConfigSelectOptions,
} from "@agentclientprotocol/sdk";
import type { ExecutionMode, TaskRunStatus } from "@shared/types";
import type { SkillButtonId } from "@shared/types/analytics";
import type { AcpMessage } from "@shared/types/session-events";
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
Expand All @@ -24,12 +25,18 @@ export interface QueuedMessage {

export type { TaskRunStatus };

export type OptimisticItem = {
type: "user_message";
id: string;
content: string;
timestamp: number;
};
export type OptimisticItem =
| {
type: "user_message";
id: string;
content: string;
timestamp: number;
}
| {
type: "skill_button_action";
id: string;
buttonId: SkillButtonId;
};

export interface AgentSession {
taskRunId: string;
Expand Down Expand Up @@ -346,13 +353,17 @@ export const sessionStoreSetters = {

appendOptimisticItem: (
taskRunId: string,
item: Omit<OptimisticItem, "id">,
item: OptimisticItem extends infer T
? T extends { id: string }
? Omit<T, "id">
: never
: never,
): void => {
const id = `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
useSessionStore.setState((state) => {
const session = state.sessions[taskRunId];
if (session) {
session.optimisticItems.push({ ...item, id });
session.optimisticItems.push({ ...item, id } as OptimisticItem);
}
});
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ContentBlock } from "@agentclientprotocol/sdk";
import { useReviewNavigationStore } from "@features/code-review/stores/reviewNavigationStore";
import { DEFAULT_TAB_IDS } from "@features/panels/constants/panelConstants";
import { usePanelLayoutStore } from "@features/panels/store/panelLayoutStore";
Expand All @@ -8,7 +9,10 @@ import { getSessionService } from "@features/sessions/service/service";
* Sends a prompt to the agent session for a task, collapses the review
* panel to split mode if expanded, and switches to the logs/chat tab.
*/
export function sendPromptToAgent(taskId: string, prompt: string): void {
export function sendPromptToAgent(
taskId: string,
prompt: string | ContentBlock[],
): void {
getSessionService().sendPrompt(taskId, prompt);

const { getReviewMode, setReviewMode } = useReviewNavigationStore.getState();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { SkillButtonActionMessage } from "./SkillButtonActionMessage";

const meta: Meta<typeof SkillButtonActionMessage> = {
title: "Skill Buttons/SkillButtonActionMessage",
component: SkillButtonActionMessage,
parameters: {
layout: "centered",
},
};

export default meta;
type Story = StoryObj<typeof SkillButtonActionMessage>;

export const AddAnalytics: Story = {
args: {
buttonId: "add-analytics",
},
};

export const CreateFeatureFlag: Story = {
args: {
buttonId: "create-feature-flags",
},
};

export const RunExperiment: Story = {
args: {
buttonId: "run-experiment",
},
};

export const AddErrorTracking: Story = {
args: {
buttonId: "add-error-tracking",
},
};

export const InstrumentLlmCalls: Story = {
args: {
buttonId: "instrument-llm-calls",
},
};

export const AddLogging: Story = {
args: {
buttonId: "add-logging",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
SKILL_BUTTONS,
type SkillButtonId,
} from "@features/skill-buttons/prompts";

interface SkillButtonActionMessageProps {
buttonId: SkillButtonId;
}

export function SkillButtonActionMessage({
buttonId,
}: SkillButtonActionMessageProps) {
const { Icon, color, actionTitle, actionDescription } =
SKILL_BUTTONS[buttonId];

return (
<div
className="flex items-center gap-2 border-l-2 py-1 pl-3"
style={{ borderColor: color }}
>
<Icon size={16} weight="bold" color={color} className="shrink-0" />
<p className="text-sm leading-relaxed">
<span className="font-medium" style={{ color }}>
{actionTitle}
</span>
<span className="text-gray-11"> — {actionDescription}</span>
</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { SkillButtonsMenu } from "./SkillButtonsMenu";

const meta: Meta<typeof SkillButtonsMenu> = {
title: "Skill Buttons/SkillButtonsMenu",
component: SkillButtonsMenu,
parameters: {
layout: "centered",
},
args: {
taskId: "storybook-task",
},
};

export default meta;
type Story = StoryObj<typeof SkillButtonsMenu>;

export const Default: Story = {};
Loading
Loading