-
Notifications
You must be signed in to change notification settings - Fork 960
fix(automations): resolve agent presets live from host instead of snapshot #4481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6bf5697
c6ff4ea
d1bf4c8
5445ab3
7839d00
e26c8ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,11 +9,12 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||
| } from "@superset/ui/dialog"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from "@superset/ui/sonner"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useMutation } from "@tanstack/react-query"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useCallback, useEffect, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useCallback, useEffect, useRef, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { LuX } from "react-icons/lu"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { EmojiTextInput } from "renderer/components/EmojiTextInput"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { MarkdownEditor } from "renderer/components/MarkdownEditor"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useEnabledAgents } from "renderer/hooks/useEnabledAgents"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useHostUrl } from "renderer/hooks/host-service/useHostTargetUrl"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useV2AgentChoices } from "renderer/hooks/useV2AgentChoices"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { apiTrpcClient } from "renderer/lib/api-trpc-client"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { DevicePicker } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useWorkspaceHostOptions } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions/useWorkspaceHostOptions"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -54,23 +55,29 @@ export function CreateAutomationDialog({ | |||||||||||||||||||||||||||||||||||||||||||||
| const [selectedProjectId, setSelectedProjectId] = useState<string | null>( | ||||||||||||||||||||||||||||||||||||||||||||||
| null, | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [agentType, setAgentType] = useState("claude"); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [agent, setAgent] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [rrule, setRrule] = useState(DEFAULT_RRULE); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [v2WorkspaceId, setV2WorkspaceId] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const { localHostId } = useWorkspaceHostOptions(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const targetHostId = hostId ?? localHostId; | ||||||||||||||||||||||||||||||||||||||||||||||
| const hostUrl = useHostUrl(targetHostId); | ||||||||||||||||||||||||||||||||||||||||||||||
| const { agents: hostAgents } = useV2AgentChoices(hostUrl); | ||||||||||||||||||||||||||||||||||||||||||||||
| const recentProjects = useRecentProjects(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const { agents: enabledAgents } = useEnabledAgents(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const searchFiles = useProjectFileSearch({ | ||||||||||||||||||||||||||||||||||||||||||||||
| hostId, | ||||||||||||||||||||||||||||||||||||||||||||||
| projectId: selectedProjectId, | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| const selectedProject = recentProjects.find( | ||||||||||||||||||||||||||||||||||||||||||||||
| (project) => project.id === selectedProjectId, | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| const selectedAgentConfig = enabledAgents.find( | ||||||||||||||||||||||||||||||||||||||||||||||
| (agent) => agent.id === agentType, | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| const selectedAgent = hostAgents.find((option) => option.id === agent); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (agent && hostAgents.some((option) => option.id === agent)) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| const fallback = hostAgents[0]?.id ?? null; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (fallback !== agent) setAgent(fallback); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [agent, hostAgents]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Default to first project once the Electric-synced list lands. | ||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -80,43 +87,67 @@ export function CreateAutomationDialog({ | |||||||||||||||||||||||||||||||||||||||||||||
| if (first) setSelectedProjectId(first.id); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [open, selectedProjectId, recentProjects]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Track which (open session, template) we've already pre-filled so the | ||||||||||||||||||||||||||||||||||||||||||||||
| // effects don't re-run and stomp on user edits when `hostAgents` lands | ||||||||||||||||||||||||||||||||||||||||||||||
| // asynchronously. | ||||||||||||||||||||||||||||||||||||||||||||||
| const appliedTemplateRef = useRef<AutomationTemplate | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| const appliedAgentForTemplateRef = useRef<AutomationTemplate | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const applyTemplate = useCallback((template: AutomationTemplate) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| setName(template.name); | ||||||||||||||||||||||||||||||||||||||||||||||
| setPrompt(template.prompt); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (template.agentType) setAgentType(template.agentType); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (template.rrule) setRrule(template.rrule); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
96
to
100
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Template selection from the gallery no longer applies the template’s preferred agent, causing automations to run with the wrong agent. Prompt for AI agents
Suggested change
Tip: Review your code locally with the cubic CLI to iterate faster. |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Pre-fill when opened with an initialTemplate (from the empty-state gallery). | ||||||||||||||||||||||||||||||||||||||||||||||
| // Pre-fill scalar fields once when opened with an initialTemplate. | ||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!open) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!initialTemplate) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (appliedTemplateRef.current === initialTemplate) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| appliedTemplateRef.current = initialTemplate; | ||||||||||||||||||||||||||||||||||||||||||||||
| applyTemplate(initialTemplate); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [open, initialTemplate, applyTemplate]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Match the template's preferred agent against the host's choices once | ||||||||||||||||||||||||||||||||||||||||||||||
| // they load. Separate effect so a `hostAgents` refresh doesn't re-trigger | ||||||||||||||||||||||||||||||||||||||||||||||
| // the scalar prefill above. | ||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!open) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!initialTemplate?.agentType) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (appliedAgentForTemplateRef.current === initialTemplate) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (hostAgents.length === 0) return; | ||||||||||||||||||||||||||||||||||||||||||||||
| const match = hostAgents.find( | ||||||||||||||||||||||||||||||||||||||||||||||
| (option) => | ||||||||||||||||||||||||||||||||||||||||||||||
| option.id === initialTemplate.agentType || | ||||||||||||||||||||||||||||||||||||||||||||||
| option.iconId === initialTemplate.agentType, | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (match) setAgent(match.id); | ||||||||||||||||||||||||||||||||||||||||||||||
| appliedAgentForTemplateRef.current = initialTemplate; | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [open, initialTemplate, hostAgents]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!open) { | ||||||||||||||||||||||||||||||||||||||||||||||
| setView("compose"); | ||||||||||||||||||||||||||||||||||||||||||||||
| setName(""); | ||||||||||||||||||||||||||||||||||||||||||||||
| setPrompt(""); | ||||||||||||||||||||||||||||||||||||||||||||||
| setHostId(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| setSelectedProjectId(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| setAgentType("claude"); | ||||||||||||||||||||||||||||||||||||||||||||||
| setAgent(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| setRrule(DEFAULT_RRULE); | ||||||||||||||||||||||||||||||||||||||||||||||
| setV2WorkspaceId(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| appliedTemplateRef.current = null; | ||||||||||||||||||||||||||||||||||||||||||||||
| appliedAgentForTemplateRef.current = null; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [open]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const targetHostId = hostId ?? localHostId; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const createMutation = useMutation({ | ||||||||||||||||||||||||||||||||||||||||||||||
| mutationFn: () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!selectedAgentConfig) throw new Error("No agent selected"); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!selectedAgent) throw new Error("No agent selected"); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!selectedProjectId) throw new Error("No project selected"); | ||||||||||||||||||||||||||||||||||||||||||||||
| return apiTrpcClient.automation.create.mutate({ | ||||||||||||||||||||||||||||||||||||||||||||||
| name, | ||||||||||||||||||||||||||||||||||||||||||||||
| prompt, | ||||||||||||||||||||||||||||||||||||||||||||||
| agentConfig: selectedAgentConfig, | ||||||||||||||||||||||||||||||||||||||||||||||
| agent: selectedAgent.id, | ||||||||||||||||||||||||||||||||||||||||||||||
| targetHostId: targetHostId ?? null, | ||||||||||||||||||||||||||||||||||||||||||||||
| v2ProjectId: selectedProjectId, | ||||||||||||||||||||||||||||||||||||||||||||||
| v2WorkspaceId, | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -149,7 +180,7 @@ export function CreateAutomationDialog({ | |||||||||||||||||||||||||||||||||||||||||||||
| prompt.trim().length > 0 && | ||||||||||||||||||||||||||||||||||||||||||||||
| !!selectedProjectId && | ||||||||||||||||||||||||||||||||||||||||||||||
| !!targetHostId && | ||||||||||||||||||||||||||||||||||||||||||||||
| !!selectedAgentConfig && | ||||||||||||||||||||||||||||||||||||||||||||||
| !!selectedAgent && | ||||||||||||||||||||||||||||||||||||||||||||||
| rrule.trim().length > 0 && | ||||||||||||||||||||||||||||||||||||||||||||||
| !createMutation.isPending; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -258,8 +289,9 @@ export function CreateAutomationDialog({ | |||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <AgentPicker | ||||||||||||||||||||||||||||||||||||||||||||||
| className="w-[100px]" | ||||||||||||||||||||||||||||||||||||||||||||||
| value={agentType} | ||||||||||||||||||||||||||||||||||||||||||||||
| onChange={setAgentType} | ||||||||||||||||||||||||||||||||||||||||||||||
| hostId={targetHostId} | ||||||||||||||||||||||||||||||||||||||||||||||
| value={agent ?? ""} | ||||||||||||||||||||||||||||||||||||||||||||||
| onChange={setAgent} | ||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -481,8 +481,8 @@ function AutomationsPage() { | |
|
|
||
| <span className="min-w-0 text-xs text-muted-foreground"> | ||
| <AgentCell | ||
| agentId={automation.agentConfig.id} | ||
| label={automation.agentConfig.label} | ||
| agentId={automation.agent} | ||
|
Comment on lines
481
to
+484
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx
Line: 481-484
Comment:
**Agent label missing for automations without a pinned host**
`AgentCell` receives `hostId={automation.targetHostId ?? null}`, but automations created before this PR (and CLI-created ones) typically have `targetHostId = null`. With a null `hostId`, `useHostUrl` returns no URL, `useV2AgentChoices` returns `[]`, `match` is undefined, and the cell falls back to rendering the raw `agentId`. For backfilled slug values ("claude", "codex") this is readable but unstyled; for any UUID-format agent IDs it shows the raw UUID. The `AutomationDetailSidebar` handles this correctly by falling back to `localHostId` — the same pattern should be applied here.
How can I resolve this? If you propose a fix, please make it concise. |
||
| hostId={automation.targetHostId ?? null} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Auto-targeted automations are resolved against the viewer’s local host when rendering Prompt for AI agents |
||
| /> | ||
| </span> | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hostAgentsreloadWhen the user switches the target host (via
DevicePicker),hostAgentsbriefly becomes[]while the new host's agents are fetched. During that window the guardhostAgents.some((option) => option.id === agent)fails (empty list),fallbackevaluates tonull, andsetAgent(null)fires — clearing the in-progress selection. The picker then shows empty and the Create button becomes disabled until the load settles. Using a stable "previous value" pattern (e.g. keeping the old agent until the new list loads and then applying the fallback only if the list is non-empty) would avoid the flash.Prompt To Fix With AI