diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 62aa7a7..0f6df85 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -588,11 +588,12 @@ export default function App() { } else if (selectedSession && patternForSession && projectForSession) { content = ( api.sendSessionMessage({ + onSend={(c, attachments, messageMode, promptInvocation) => api.sendSessionMessage({ sessionId: selectedSession.id, content: c, attachments: attachments?.length ? attachments : undefined, messageMode, + promptInvocation, })} onCancelTurn={() => { void api.cancelSessionTurn({ sessionId: selectedSession.id }); }} onResolveApproval={(approvalId, decision, alwaysApprove) => diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx index b55f7bc..e9c6ee5 100644 --- a/src/renderer/components/ChatPane.tsx +++ b/src/renderer/components/ChatPane.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { AlertCircle, ArrowUp, Bookmark, Bot, Circle, ClipboardList, GitBranch, Loader2, MessageCircleQuestion, Paperclip, RefreshCw, ShieldAlert, Square, User, X } from 'lucide-react'; +import { AlertCircle, ArrowUp, Bookmark, Bot, ChevronDown, ChevronRight, Circle, ClipboardList, FileText, GitBranch, Loader2, MessageCircleQuestion, Paperclip, RefreshCw, ShieldAlert, Square, User, X } from 'lucide-react'; import { MarkdownContent } from '@renderer/components/MarkdownContent'; import { MarkdownComposer, type MarkdownComposerHandle } from '@renderer/components/MarkdownComposer'; @@ -30,6 +30,7 @@ import { import { type PatternDefinition, type ReasoningEffort } from '@shared/domain/pattern'; import { isScratchpadProject, type ProjectRecord } from '@shared/domain/project'; import { resolveSessionToolingSelection, type ChatMessageRecord, type SessionBranchOriginAction, type SessionRecord } from '@shared/domain/session'; +import type { ProjectPromptInvocation } from '@shared/domain/projectCustomization'; import { countApprovedToolsInGroups, groupApprovalToolsByProvider, @@ -59,7 +60,7 @@ interface ChatPaneProps { terminalRunning?: boolean; gitPanelOpen?: boolean; gitDirty?: boolean; - onSend: (content: string, attachments?: ChatMessageAttachment[], messageMode?: MessageMode) => Promise; + onSend: (content: string, attachments?: ChatMessageAttachment[], messageMode?: MessageMode, promptInvocation?: ProjectPromptInvocation) => Promise; onCancelTurn?: () => void; onResolveApproval?: (approvalId: string, decision: ApprovalDecision, alwaysApprove?: boolean) => Promise; onResolveUserInput?: (userInputId: string, answer: string, wasFreeform: boolean) => Promise; @@ -504,6 +505,8 @@ export function ChatPane({ onSave={(content) => handleEditSave(message.id, content)} onCancel={() => setEditingMessageId(undefined)} /> + ) : isUser && message.promptInvocation ? ( + ) : (
0 && ( void onSend(content)} + onSubmit={(promptInvocation) => void onSend('', undefined, undefined, promptInvocation)} promptFiles={promptFiles} /> )} @@ -928,6 +931,54 @@ export function ChatPane({ ); } +/* ── Prompt invocation chrome ───────────────────────────────── */ + +function PromptInvocationChrome({ invocation }: { invocation: ProjectPromptInvocation }) { + const [expanded, setExpanded] = useState(false); + + return ( +
+ + {invocation.description && ( +

{invocation.description}

+ )} +

{invocation.sourcePath}

+ {expanded && ( +
+
+ +
+
+ )} +
+ ); +} + /* ── Branch origin banner ───────────────────────────────────── */ function BranchOriginBanner({ action, label }: { action?: SessionBranchOriginAction; label?: string }) { diff --git a/src/renderer/components/ProjectSettingsPanel.tsx b/src/renderer/components/ProjectSettingsPanel.tsx index 6d5fea2..8282528 100644 --- a/src/renderer/components/ProjectSettingsPanel.tsx +++ b/src/renderer/components/ProjectSettingsPanel.tsx @@ -578,6 +578,11 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) {
{prompt.name} + {prompt.agent && ( + + {prompt.agent} + + )} {prompt.variables.length > 0 && ( {prompt.variables.length} variable{prompt.variables.length === 1 ? '' : 's'} @@ -587,6 +592,18 @@ function PromptCard({ prompt }: { prompt: ProjectPromptFile }) { {prompt.description && (

{prompt.description}

)} + {prompt.tools && prompt.tools.length > 0 && ( +
+ {prompt.tools.map((tool) => ( + + {tool} + + ))} +
+ )} {prompt.variables.length > 0 && (
{prompt.variables.map((v) => ( diff --git a/src/renderer/components/chat/InlinePromptPill.tsx b/src/renderer/components/chat/InlinePromptPill.tsx index 8a6822c..5341fda 100644 --- a/src/renderer/components/chat/InlinePromptPill.tsx +++ b/src/renderer/components/chat/InlinePromptPill.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from 'react'; import { ArrowUp, FileText, X } from 'lucide-react'; import { useClickOutside } from '@renderer/hooks/useClickOutside'; -import type { ProjectPromptFile, ProjectPromptVariable } from '@shared/domain/projectCustomization'; +import type { ProjectPromptFile, ProjectPromptInvocation, ProjectPromptVariable } from '@shared/domain/projectCustomization'; const promptVariablePattern = /\$\{input:([a-zA-Z0-9_-]+):[^}]+\}/g; @@ -12,6 +12,21 @@ function resolvePromptTemplate(template: string, values: Record) }); } +function buildPromptInvocation(prompt: ProjectPromptFile, resolvedTemplate: string): ProjectPromptInvocation { + const invocation: ProjectPromptInvocation = { + id: prompt.id, + name: prompt.name, + sourcePath: prompt.sourcePath, + resolvedPrompt: resolvedTemplate, + }; + + if (prompt.description) invocation.description = prompt.description; + if (prompt.agent) invocation.agent = prompt.agent; + if (prompt.tools?.length) invocation.tools = prompt.tools; + + return invocation; +} + export function InlinePromptPill({ promptFiles, disabled, @@ -19,7 +34,7 @@ export function InlinePromptPill({ }: { promptFiles: ReadonlyArray; disabled: boolean; - onSubmit: (resolvedContent: string) => void; + onSubmit: (invocation: ProjectPromptInvocation) => void; }) { const [open, setOpen] = useState(false); const [selectedPrompt, setSelectedPrompt] = useState(null); @@ -34,7 +49,7 @@ export function InlinePromptPill({ const handleSelectPrompt = useCallback((prompt: ProjectPromptFile) => { if (prompt.variables.length === 0) { - onSubmit(prompt.template.trim()); + onSubmit(buildPromptInvocation(prompt, prompt.template.trim())); handleClose(); } else { setSelectedPrompt(prompt); @@ -46,7 +61,7 @@ export function InlinePromptPill({ if (!selectedPrompt) return; const resolved = resolvePromptTemplate(selectedPrompt.template, variableValues).trim(); if (!resolved) return; - onSubmit(resolved); + onSubmit(buildPromptInvocation(selectedPrompt, resolved)); handleClose(); }, [selectedPrompt, variableValues, onSubmit, handleClose]); @@ -127,26 +142,36 @@ function PromptList({ >
-
- {prompt.name} +
+ + {prompt.name} + + {prompt.agent && ( + + {prompt.agent} + + )}
{prompt.description && (
{prompt.description}
)} - {prompt.variables.length > 0 && ( -
- {prompt.variables.map((v) => ( - - {v.name} - - ))} -
- )} +
+ {prompt.tools && prompt.tools.length > 0 && ( + + {prompt.tools.length} tool{prompt.tools.length === 1 ? '' : 's'} + + )} + {prompt.variables.map((v) => ( + + {v.name} + + ))} +
))}