From 65e41bbc487cacda3bc5e97f156e4453799e45b3 Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Fri, 26 Jun 2026 13:55:21 -0700 Subject: [PATCH] feat(agents): add opt-in save-as-persona-template to create dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an opt-in 'Save as persona template' toggle (off by default) to the end of the create-agent flow. On submit the persona-less agent is created first (unchanged, never sets personaId); if the toggle is on, a persona template is then minted from the same field values via createPersonaMutation. The template mint is non-fatal and byproduct-ordered: the agent is the primary goal, so a failed template surfaces as a toast and never blocks or rolls back the just-created agent. No backend or IPC change — both mutations already exist. The toggle is a self-contained block placed after the env-vars editor so a later avatar-forward visual reskin can relocate it without touching the submit behavior. Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../features/agents/ui/CreateAgentDialog.tsx | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/desktop/src/features/agents/ui/CreateAgentDialog.tsx b/desktop/src/features/agents/ui/CreateAgentDialog.tsx index 16e67dcf6..4f390f518 100644 --- a/desktop/src/features/agents/ui/CreateAgentDialog.tsx +++ b/desktop/src/features/agents/ui/CreateAgentDialog.tsx @@ -1,11 +1,13 @@ import { AlertTriangle, ChevronDown } from "lucide-react"; import * as React from "react"; +import { toast } from "sonner"; import { useAcpRuntimesQuery, useAvailableAcpRuntimes, useBackendProvidersQuery, useCreateManagedAgentMutation, + useCreatePersonaMutation, useManagedAgentPrereqsQuery, } from "@/features/agents/hooks"; import { probeBackendProvider } from "@/shared/api/tauri"; @@ -13,8 +15,10 @@ import type { BackendProviderProbeResult, CreateManagedAgentInput, CreateManagedAgentResponse, + CreatePersonaInput, RespondToMode, } from "@/shared/api/types"; +import { cn } from "@/shared/lib/cn"; import { Button } from "@/shared/ui/button"; import { Dialog, @@ -52,6 +56,7 @@ export function CreateAgentDialog({ onOpenChange: (open: boolean) => void; }) { const createMutation = useCreateManagedAgentMutation(); + const createPersonaMutation = useCreatePersonaMutation(); const providersQuery = useAvailableAcpRuntimes({ enabled: open }); const allProvidersQuery = useAcpRuntimesQuery({ enabled: open }); const backendProvidersQuery = useBackendProvidersQuery({ enabled: open }); @@ -68,6 +73,7 @@ export function CreateAgentDialog({ const [relayUrl, setRelayUrl] = React.useState(""); const [spawnAfterCreate, setSpawnAfterCreate] = React.useState(true); const [startOnAppLaunch, setStartOnAppLaunch] = React.useState(true); + const [saveAsTemplate, setSaveAsTemplate] = React.useState(false); const [turnTimeoutSeconds, setTurnTimeoutSeconds] = React.useState("320"); const [parallelism, setParallelism] = React.useState("24"); const [systemPrompt, setSystemPrompt] = React.useState(""); @@ -422,6 +428,37 @@ export function CreateAgentDialog({ }; const created = await createMutation.mutateAsync(input); + + // Opt-in: also persist this setup as a reusable persona template. The + // agent is the primary goal; the template is a byproduct, so a failed + // template mint must NOT fail or roll back the just-created agent — it + // surfaces as a non-fatal toast. "Persona template" is the UI name for a + // backend `persona` (kind:30175); this builds a `CreatePersonaInput`. + if (saveAsTemplate) { + const personaInput: CreatePersonaInput = { + displayName: name.trim(), + systemPrompt: systemPrompt.trim(), + // Carry the selected ACP runtime when one is chosen; "custom"/manual + // setups leave it unset and fall back to the dialog default later. + runtime: + selectedRuntimeId !== "custom" ? selectedRuntimeId : undefined, + model: useMesh ? meshModelId.trim() || undefined : undefined, + envVars, + }; + try { + await createPersonaMutation.mutateAsync(personaInput); + toast.success( + `Saved ${personaInput.displayName} as a persona template.`, + ); + } catch (error) { + toast.error( + error instanceof Error + ? `Agent created, but saving the persona template failed: ${error.message}` + : "Agent created, but saving the persona template failed.", + ); + } + } + handleOpenChange(false); onCreated(created); } catch { @@ -650,6 +687,33 @@ export function CreateAgentDialog({ value={envVars} /> + {/* + Opt-in: save this setup as a reusable persona template. Off by + default — the template is a byproduct of creating the agent, not + the goal. Kept as a self-contained toggle block so a later visual + reskin can relocate it without untangling the submit behavior. + */} + + {meshClientError ? (

Mesh client failed to start: {meshClientError}