Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<string | null>(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 (
<Flex
Expand Down Expand Up @@ -432,6 +416,7 @@ export function AgentBuilderDock() {
placeholder={placeholder}
emptyState={<AgentBuilderEmptyState page={page} onPick={chat.send} />}
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 ? (
<AgentChatPendingApprovalCard
Expand Down Expand Up @@ -459,14 +444,6 @@ export function AgentBuilderDock() {
/>
)}

<AgentBuilderSeedDialog
open={seedConfirm != null}
prompt={seedConfirm ?? ""}
onStartFresh={seedStartFresh}
onContinue={seedContinue}
onCancel={() => setSeedConfirm(null)}
/>

<AgentBuilderMcpConnectDialog
pending={pendingMcpConnect}
busy={mcpConnectBusy}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,6 +34,7 @@ export function AgentChatSurface({
composerDisabledReason,
scrollX = true,
placeholder = "Message this agent…",
draft,
onSend,
onCancel,
}: {
Expand All @@ -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;
}) {
Expand Down Expand Up @@ -84,6 +95,7 @@ export function AgentChatSurface({
isStreaming={isStreaming}
placeholder={placeholder}
disabledReason={composerDisabledReason}
draft={draft}
onSend={onSend}
onCancel={onCancel}
/>
Expand All @@ -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<HTMLTextAreaElement>(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<number | null>(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;
Expand All @@ -132,6 +158,7 @@ function Composer({
<div className="shrink-0 px-3 pt-2 pb-3">
<InputGroup className="h-auto cursor-text bg-card focus-within:ring-1 focus-within:ring-purple-9">
<InputGroupTextarea
ref={textareaRef}
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={onKeyDown}
Expand Down
Loading