From aa4d5e872cb870b7acec2ea977caa3eb2b8799fa Mon Sep 17 00:00:00 2001 From: Dylan Martin Date: Wed, 15 Jul 2026 14:27:44 -0700 Subject: [PATCH] fix(agent-builder): prefill seeded prompt instead of auto-sending Opening the Agent Builder dock via a contextual action ("New agent", "Edit configuration", etc.) used to auto-send the seeded prompt, so the sidebar started a chat on its own. Instead, prefill the composer with the seeded prompt and let the user review and hit send. Since prefilling is non-destructive, the start-fresh-vs-continue confirmation dialog is no longer needed and is removed. Generated-By: PostHog Code Task-Id: fc2c5587-1464-4cb1-9116-0e7a34f70734 --- .../agent-builder/AgentBuilderDock.tsx | 47 ++++----------- .../agent-builder/AgentBuilderSeedDialog.tsx | 58 ------------------- .../agent-builder/agentBuilderStore.ts | 5 +- .../components/AgentChatSurface.tsx | 29 +++++++++- 4 files changed, 43 insertions(+), 96 deletions(-) delete mode 100644 packages/ui/src/features/agent-applications/agent-builder/AgentBuilderSeedDialog.tsx diff --git a/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx b/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx index fdb975bbe4..f041a01e67 100644 --- a/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx +++ b/packages/ui/src/features/agent-applications/agent-builder/AgentBuilderDock.tsx @@ -23,7 +23,6 @@ import { useAgentChatPendingApproval } from "../hooks/useAgentChatPendingApprova import { agentIngressBaseUrl } from "../utils/ingress"; import { AgentBuilderMcpConnectDialog } from "./AgentBuilderMcpConnectDialog"; import { AgentBuilderSecretForm } from "./AgentBuilderSecretForm"; -import { AgentBuilderSeedDialog } from "./AgentBuilderSeedDialog"; import { AGENT_BUILDER_CHAT_ID, AGENT_BUILDER_SLUG, @@ -322,37 +321,22 @@ export function AgentBuilderDock() { setPendingMcpConnect(null); } - // Edit-with-AI hand-offs: send the seeded prompt once when a new seed lands. - // An empty dock starts immediately; if a chat is already in progress, confirm - // whether to start fresh or continue (so a deliberate "New agent" / "Edit with - // AI" doesn't silently wipe or append onto an unrelated conversation). + // Contextual hand-offs ("New agent" / "Edit with AI" / …): prefill the + // seeded prompt into the composer when a new seed lands — never send it. The + // user reviews and hits send, so opening the dock doesn't fire a chat on its + // own. Prefilling is non-destructive, so no start-fresh-vs-continue prompt is + // needed: it drops into whatever chat is open, and the header "New chat" (+) + // clears first if the user wants a fresh conversation. const lastSeedRef = useRef(0); - const [seedConfirm, setSeedConfirm] = useState(null); + const [draft, setDraft] = useState<{ text: string; token: number } | null>( + null, + ); useEffect(() => { if (!seed || seed.seq === lastSeedRef.current) return; lastSeedRef.current = seed.seq; consumeSeed(seed.seq); - if (chat.messages.length === 0) { - chat.send(seed.prompt); - } else { - setSeedConfirm(seed.prompt); - } - }, [seed, chat, consumeSeed]); - - function seedStartFresh() { - if (!seedConfirm) return; - setPendingSecret(null); - setPendingMcpConnect(null); - chat.newChat(); - setLastSession(null); - chat.send(seedConfirm); - setSeedConfirm(null); - } - function seedContinue() { - if (!seedConfirm) return; - chat.send(seedConfirm); - setSeedConfirm(null); - } + setDraft({ text: seed.prompt, token: seed.seq }); + }, [seed, consumeSeed]); return ( } emptyHint="Ask the agent builder to inspect, debug, or edit your agents. It can see what you're looking at and walk you there." + draft={draft ?? undefined} belowConversation={ pendingApproval ? ( )} - setSeedConfirm(null)} - /> - void; - onContinue: () => void; - onCancel: () => void; -}) { - return ( - { - if (!isOpen) onCancel(); - }} - > - - - - - Start a new chat? - - - - You have an Agent Builder chat in progress. Start a fresh chat for - this, or continue the current one? - - - “{prompt}” - - - - - - - - - - ); -} diff --git a/packages/ui/src/features/agent-applications/agent-builder/agentBuilderStore.ts b/packages/ui/src/features/agent-applications/agent-builder/agentBuilderStore.ts index bf9e2f55dc..3609a191d4 100644 --- a/packages/ui/src/features/agent-applications/agent-builder/agentBuilderStore.ts +++ b/packages/ui/src/features/agent-applications/agent-builder/agentBuilderStore.ts @@ -27,7 +27,8 @@ export type AgentBuilderPageContext = | { kind: "agent-chat"; slug: string } | { kind: "unknown" }; -/** A pending "Edit with AI" hand-off: open the dock and send `prompt`. */ +/** A pending contextual hand-off: open the dock and prefill the composer with + * `prompt` (the user reviews and sends — it is never auto-sent). */ export interface AgentBuilderSeed { /** Monotonic id so a consumer can mark exactly one seed handled. */ seq: number; @@ -113,7 +114,7 @@ interface AgentBuilderStore { setVisible: (visible: boolean) => void; setFollowMode: (followMode: boolean) => void; setPage: (page: AgentBuilderPageContext) => void; - /** Open the dock and queue a prompt to send. */ + /** Open the dock and prefill the composer with a prompt (not sent). */ startAgentBuilder: (prompt: string, agentSlug?: string | null) => void; /** Mark a seed handled (no-op if a newer seed has since replaced it). */ consumeSeed: (seq: number) => void; diff --git a/packages/ui/src/features/agent-applications/components/AgentChatSurface.tsx b/packages/ui/src/features/agent-applications/components/AgentChatSurface.tsx index 368c683f6e..bda6edc3fe 100644 --- a/packages/ui/src/features/agent-applications/components/AgentChatSurface.tsx +++ b/packages/ui/src/features/agent-applications/components/AgentChatSurface.tsx @@ -8,7 +8,13 @@ import { import type { AcpMessage } from "@posthog/shared"; import { ThreadView } from "@posthog/ui/features/sessions/components/ThreadView"; import { Flex, Text, Tooltip } from "@radix-ui/themes"; -import { type KeyboardEvent, type ReactNode, useState } from "react"; +import { + type KeyboardEvent, + type ReactNode, + useEffect, + useRef, + useState, +} from "react"; /** * The conversation + composer half of a deployed-agent chat, shared by the @@ -28,6 +34,7 @@ export function AgentChatSurface({ composerDisabledReason, scrollX = true, placeholder = "Message this agent…", + draft, onSend, onCancel, }: { @@ -50,6 +57,10 @@ export function AgentChatSurface({ scrollX?: boolean; /** Composer placeholder. */ placeholder?: string; + /** When set, prefills the composer with `text` (without sending). Bump + * `token` each time a new draft should repopulate the input — the same text + * re-applies on a fresh token so re-triggering a seeded prompt works. */ + draft?: { text: string; token: number }; onSend: (text: string) => void; onCancel: () => void; }) { @@ -84,6 +95,7 @@ export function AgentChatSurface({ isStreaming={isStreaming} placeholder={placeholder} disabledReason={composerDisabledReason} + draft={draft} onSend={onSend} onCancel={onCancel} /> @@ -95,17 +107,31 @@ function Composer({ isStreaming, placeholder, disabledReason, + draft, onSend, onCancel, }: { isStreaming: boolean; placeholder: string; disabledReason?: string; + draft?: { text: string; token: number }; onSend: (text: string) => void; onCancel: () => void; }) { const [text, setText] = useState(""); const parked = !!disabledReason; + const textareaRef = useRef(null); + + // Prefill (but don't send) when a new draft lands — a seeded prompt drops into + // the composer for the user to review, edit, and send. Keyed on `token` so the + // same prompt re-applies on a fresh trigger. + const lastDraftToken = useRef(null); + useEffect(() => { + if (!draft || draft.token === lastDraftToken.current) return; + lastDraftToken.current = draft.token; + setText(draft.text); + textareaRef.current?.focus(); + }, [draft]); function submit() { if (parked) return; @@ -132,6 +158,7 @@ function Composer({
setText(e.target.value)} onKeyDown={onKeyDown}