mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 10:15:12 +00:00
fix(ui/desktop): send promoted agent fields at top level to fix summoning stuck
Backend v3 (migration 000037) promoted emoji, agent_description, self_evolve, thinking_level, reasoning_config from other_config JSONB to top-level columns. Desktop app was still nesting these inside other_config, so backend saw empty agent_description and never triggered the summoning goroutine. SummoningModal then waited forever for agent.summoning WS events that were never emitted. - types/agent.ts: add promoted fields to AgentData and AgentInput - services/agent-service.ts: add emoji to AgentListItem - hooks/use-agent-detail-state.ts: read/write promoted fields at top-level, keep fallback to other_config for legacy agents, drop legacy keys on save - hooks/use-agents.ts: read emoji from top-level with fallback - components/agents/AgentFormDialog.tsx: send agent_description and emoji at top-level; read with fallback - components/agents/AgentCard.tsx: read self_evolve, emoji, agent_description from top-level with fallback to other_config - components/onboarding/AgentStep.tsx: critical fix - send agent_description at top level so backend triggers summoning
This commit is contained in:
1 parent
1296cdbf02
commit
3105a7c811
7 files changed
+71
-59
No files matched your search
@@ -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<string, unknown>
|
||||
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) ? (
|
||||
<div className="line-clamp-3 text-xs text-text-muted/70">
|
||||
{String(agent.frontmatter || otherCfg.description || '')}
|
||||
{String(agent.frontmatter || agent.agent_description || '')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -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<string, unknown> = {}
|
||||
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) {
|
||||
|
||||
@@ -82,10 +82,6 @@ export function AgentStep({ provider, model, onBack, onComplete }: AgentStepProp
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
const otherConfig: Record<string, unknown> = {}
|
||||
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) {
|
||||
|
||||
@@ -9,10 +9,10 @@ export function useAgentDetailState(
|
||||
onSave: (id: string, updates: Partial<AgentData>) => Promise<void>,
|
||||
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<ReasoningOverrideMode>(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<string, unknown> = { ...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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface AgentListItem {
|
||||
display_name?: string
|
||||
model?: string
|
||||
provider?: string
|
||||
emoji?: string | null
|
||||
other_config?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, unknown>
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user