diff --git a/ui/desktop/frontend/src/components/agents/AgentCard.tsx b/ui/desktop/frontend/src/components/agents/AgentCard.tsx index ac5033ee..a4cde688 100644 --- a/ui/desktop/frontend/src/components/agents/AgentCard.tsx +++ b/ui/desktop/frontend/src/components/agents/AgentCard.tsx @@ -14,9 +14,8 @@ export function AgentCard({ agent, onEdit, onDelete, onResummon }: AgentCardProp const { t } = useTranslation('agents') const displayName = agent.display_name || (UUID_RE.test(agent.agent_key) ? 'Unnamed Agent' : agent.agent_key) - const otherCfg = (agent.other_config ?? {}) as Record - const selfEvolve = agent.agent_type === 'predefined' && Boolean(otherCfg.self_evolve) - const emoji = typeof otherCfg.emoji === 'string' ? otherCfg.emoji : '' + const selfEvolve = agent.agent_type === 'predefined' && Boolean(agent.self_evolve ?? agent.other_config?.self_evolve) + const emoji = agent.emoji ?? (typeof agent.other_config?.emoji === 'string' ? agent.other_config.emoji : '') const showSubtitle = agent.display_name && !UUID_RE.test(agent.agent_key) const isSummoning = agent.status === 'summoning' const isFailed = agent.status === 'summon_failed' @@ -77,9 +76,9 @@ export function AgentCard({ agent, onEdit, onDelete, onResummon }: AgentCardProp ) : null} {/* Expertise/frontmatter */} - {(agent.frontmatter || otherCfg.description) ? ( + {(agent.frontmatter || agent.agent_description) ? (
- {String(agent.frontmatter || otherCfg.description || '')} + {String(agent.frontmatter || agent.agent_description || '')}
) : null} diff --git a/ui/desktop/frontend/src/components/agents/AgentFormDialog.tsx b/ui/desktop/frontend/src/components/agents/AgentFormDialog.tsx index f4760c72..6393a7ea 100644 --- a/ui/desktop/frontend/src/components/agents/AgentFormDialog.tsx +++ b/ui/desktop/frontend/src/components/agents/AgentFormDialog.tsx @@ -41,11 +41,11 @@ export function AgentFormDialog({ open, onOpenChange, agent, onSubmit }: AgentFo if (!open) return reset({ displayName: agent?.display_name ?? '', - emoji: (agent?.other_config?.emoji as string) ?? '🦊', + emoji: agent?.emoji ?? (agent?.other_config?.emoji as string) ?? '🦊', agentKey: agent?.agent_key ?? '', providerName: agent?.provider ?? '', model: agent?.model ?? '', - description: (agent?.other_config?.description as string) ?? '', + description: agent?.agent_description ?? (agent?.other_config?.description as string) ?? '', isDefault: agent?.is_default ?? false, }) setSubmitError('') @@ -96,9 +96,6 @@ export function AgentFormDialog({ open, onOpenChange, agent, onSubmit }: AgentFo const onValid = async (data: AgentFormData) => { setSubmitError('') try { - const otherConfig: Record = {} - if (data.description?.trim()) otherConfig.description = data.description.trim() - if (data.emoji?.trim()) otherConfig.emoji = data.emoji.trim() await onSubmit({ agent_key: data.agentKey, display_name: data.displayName.trim() || undefined, @@ -106,7 +103,9 @@ export function AgentFormDialog({ open, onOpenChange, agent, onSubmit }: AgentFo model: data.model.trim(), agent_type: isEditing ? agent!.agent_type : 'predefined', is_default: data.isDefault || undefined, - other_config: Object.keys(otherConfig).length > 0 ? otherConfig : undefined, + // Promoted fields at top level + emoji: data.emoji?.trim() || null, + agent_description: data.description?.trim() || null, }) onOpenChange(false) } catch (err) { diff --git a/ui/desktop/frontend/src/components/onboarding/AgentStep.tsx b/ui/desktop/frontend/src/components/onboarding/AgentStep.tsx index cfe58c47..08b74afb 100644 --- a/ui/desktop/frontend/src/components/onboarding/AgentStep.tsx +++ b/ui/desktop/frontend/src/components/onboarding/AgentStep.tsx @@ -82,10 +82,6 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp setLoading(true) setError('') try { - const otherConfig: Record = {} - if (description.trim()) otherConfig.description = description.trim() - if (selectedEmoji) otherConfig.emoji = selectedEmoji - const result = await getApiClient().post<{ id: string }>('/v1/agents', { agent_key: agentKey, display_name: displayName.trim() || undefined, @@ -93,7 +89,9 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp model: model || '', agent_type: 'predefined', is_default: true, - other_config: Object.keys(otherConfig).length > 0 ? otherConfig : undefined, + // Promoted fields at top level — agent_description triggers summoning on backend + agent_description: description.trim() || null, + emoji: selectedEmoji || null, }) setCreatedAgent({ id: result.id, name: displayName.trim() || agentKey }) } catch (err) { diff --git a/ui/desktop/frontend/src/hooks/use-agent-detail-state.ts b/ui/desktop/frontend/src/hooks/use-agent-detail-state.ts index 75a43cf6..bf561b2f 100644 --- a/ui/desktop/frontend/src/hooks/use-agent-detail-state.ts +++ b/ui/desktop/frontend/src/hooks/use-agent-detail-state.ts @@ -9,10 +9,10 @@ export function useAgentDetailState( onSave: (id: string, updates: Partial) => Promise, onClose: () => void, ) { - // --- Identity --- - const [emoji, setEmoji] = useState((agent.other_config?.emoji as string) ?? '🤖') + // --- Identity --- (promoted fields: read top-level, fallback to other_config for legacy data) + const [emoji, setEmoji] = useState(agent.emoji ?? (agent.other_config?.emoji as string) ?? '🤖') const [displayName, setDisplayName] = useState(agent.display_name ?? '') - const [description, setDescription] = useState((agent.other_config?.description as string) ?? '') + const [description, setDescription] = useState(agent.agent_description ?? (agent.other_config?.description as string) ?? '') const [status, setStatus] = useState(agent.status ?? 'active') const [isDefault, setIsDefault] = useState(agent.is_default ?? false) @@ -22,20 +22,22 @@ export function useAgentDetailState( const [contextWindow, setContextWindow] = useState(agent.context_window ?? 200000) const [maxToolIterations, setMaxToolIterations] = useState(agent.max_tool_iterations ?? 25) - // --- Evolution --- - const [selfEvolve, setSelfEvolve] = useState(!!(agent.other_config?.self_evolve)) - const [skillLearning, setSkillLearning] = useState(!!(agent.other_config?.skill_learning)) + // --- Evolution --- (promoted fields with fallback) + const [selfEvolve, setSelfEvolve] = useState(!!(agent.self_evolve ?? agent.other_config?.self_evolve)) + const [skillLearning, setSkillLearning] = useState(!!(agent.skill_evolve ?? agent.other_config?.skill_learning)) const [skillNudgeInterval, setSkillNudgeInterval] = useState( - (agent.other_config?.skill_nudge_interval as number) ?? 15, + agent.skill_nudge_interval ?? (agent.other_config?.skill_nudge_interval as number) ?? 15, ) // --- Prompt mode --- const [promptMode, setPromptMode] = useState((agent.other_config?.prompt_mode as string) || 'full') - // --- Thinking --- - const reasoning = (agent.other_config?.reasoning ?? {}) as AgentReasoningConfig + // --- Thinking --- (promoted: reasoning_config + thinking_level, fallback to other_config) + const reasoning = (agent.reasoning_config ?? agent.other_config?.reasoning ?? {}) as AgentReasoningConfig const [reasoningMode, setReasoningMode] = useState(reasoning.override_mode ?? 'inherit') - const [thinkingLevel, setThinkingLevel] = useState(reasoning.effort ?? 'off') + const [thinkingLevel, setThinkingLevel] = useState( + agent.thinking_level ?? reasoning.effort ?? 'off', + ) // --- Context pruning --- const [pruningEnabled, setPruningEnabled] = useState(agent.context_pruning != null) @@ -70,35 +72,24 @@ export function useAgentDetailState( setSaving(true) setSaveError('') try { + // Build other_config for non-promoted fields only const otherConfig: Record = { ...agent.other_config } - if (emoji) otherConfig.emoji = emoji - if (description.trim()) otherConfig.description = description.trim() - else delete otherConfig.description - otherConfig.self_evolve = selfEvolve + // Remove legacy keys that are now promoted to top-level + delete otherConfig.emoji + delete otherConfig.description + delete otherConfig.self_evolve + delete otherConfig.skill_learning + delete otherConfig.skill_nudge_interval + delete otherConfig.reasoning - // Skill learning - otherConfig.skill_learning = skillLearning - if (skillLearning && skillNudgeInterval > 0) { - otherConfig.skill_nudge_interval = skillNudgeInterval - } else { - delete otherConfig.skill_nudge_interval - } - - // Prompt mode + // Prompt mode (still in other_config) if (promptMode && promptMode !== 'full') { otherConfig.prompt_mode = promptMode } else { delete otherConfig.prompt_mode } - // Thinking / reasoning - if (reasoningMode === 'custom') { - otherConfig.reasoning = { override_mode: 'custom', effort: thinkingLevel } - } else { - delete otherConfig.reasoning - } - - // Pinned skills + // Pinned skills (still in other_config) if (pinnedSkills.length > 0) { otherConfig.pinned_skills = pinnedSkills } else { @@ -107,13 +98,23 @@ export function useAgentDetailState( await onSave(agent.id, { display_name: displayName.trim() || undefined, + // Promoted fields at top level + emoji: emoji || null, + agent_description: description.trim() || null, + self_evolve: selfEvolve, + skill_evolve: skillLearning, + skill_nudge_interval: skillLearning && skillNudgeInterval > 0 ? skillNudgeInterval : null, + thinking_level: reasoningMode === 'custom' ? thinkingLevel : null, + reasoning_config: reasoningMode === 'custom' + ? { override_mode: 'custom', effort: thinkingLevel } : null, + // Other fields provider, model, context_window: contextWindow, max_tool_iterations: maxToolIterations, is_default: isDefault, status, - other_config: otherConfig, + other_config: Object.keys(otherConfig).length > 0 ? otherConfig : null, context_pruning: pruningEnabled ? pruningConfig : null, compaction_config: compactionConfig, subagents_config: subEnabled ? subConfig : null, diff --git a/ui/desktop/frontend/src/hooks/use-agents.ts b/ui/desktop/frontend/src/hooks/use-agents.ts index 9a257e45..309936fc 100644 --- a/ui/desktop/frontend/src/hooks/use-agents.ts +++ b/ui/desktop/frontend/src/hooks/use-agents.ts @@ -14,17 +14,14 @@ export function useAgents() { try { const result = await agentService.listItems() - const mapped: Agent[] = (result.agents ?? []).map((a) => { - const otherCfg = a.other_config ?? {} - return { - id: a.id, - key: a.agent_key, - name: a.display_name || a.agent_key, - model: a.model ?? 'unknown', - status: 'online' as const, - emoji: typeof otherCfg.emoji === 'string' ? otherCfg.emoji : undefined, - } - }) + const mapped: Agent[] = (result.agents ?? []).map((a) => ({ + id: a.id, + key: a.agent_key, + name: a.display_name || a.agent_key, + model: a.model ?? 'unknown', + status: 'online' as const, + emoji: a.emoji ?? (typeof a.other_config?.emoji === 'string' ? a.other_config.emoji : undefined), + })) setAgents(mapped) return mapped diff --git a/ui/desktop/frontend/src/services/agent-service.ts b/ui/desktop/frontend/src/services/agent-service.ts index c5927586..7abc2805 100644 --- a/ui/desktop/frontend/src/services/agent-service.ts +++ b/ui/desktop/frontend/src/services/agent-service.ts @@ -9,6 +9,7 @@ export interface AgentListItem { display_name?: string model?: string provider?: string + emoji?: string | null other_config?: Record | null } diff --git a/ui/desktop/frontend/src/types/agent.ts b/ui/desktop/frontend/src/types/agent.ts index 88ccdaa6..6e3b9644 100644 --- a/ui/desktop/frontend/src/types/agent.ts +++ b/ui/desktop/frontend/src/types/agent.ts @@ -88,6 +88,15 @@ export interface AgentData { created_at?: string updated_at?: string + // Promoted fields (formerly in other_config, migration 000037 v3) + emoji?: string | null + agent_description?: string | null + thinking_level?: string | null + self_evolve?: boolean | null + skill_evolve?: boolean | null + skill_nudge_interval?: number | null + reasoning_config?: AgentReasoningConfig | null + // Per-agent JSONB configs (null/undefined = use global defaults) memory_config?: MemoryConfig | null compaction_config?: CompactionConfig | null @@ -108,6 +117,14 @@ export interface AgentInput { is_default?: boolean context_window?: number max_tool_iterations?: number + // Promoted fields + emoji?: string | null + agent_description?: string | null + self_evolve?: boolean + thinking_level?: string | null + reasoning_config?: AgentReasoningConfig | null + skill_evolve?: boolean | null + skill_nudge_interval?: number | null memory_config?: MemoryConfig | null other_config?: Record }