diff --git a/crates/zclaw-kernel/src/kernel/messaging.rs b/crates/zclaw-kernel/src/kernel/messaging.rs index 35df8e5..b529ebf 100644 --- a/crates/zclaw-kernel/src/kernel/messaging.rs +++ b/crates/zclaw-kernel/src/kernel/messaging.rs @@ -426,6 +426,7 @@ impl Kernel { prompt.push_str("- Provide clear options when possible\n"); prompt.push_str("- Include brief context about why you're asking\n"); prompt.push_str("- After receiving clarification, proceed immediately\n"); + prompt.push_str("- CRITICAL: When calling ask_clarification, do NOT repeat the options in your text response. The options will be shown in a dedicated card above your reply. Simply greet the user and briefly explain why you need clarification — avoid phrases like \"以下信息\" or \"the following options\" that imply a list follows in your text\n"); prompt } diff --git a/desktop/src/components/ChatArea.tsx b/desktop/src/components/ChatArea.tsx index 0b7fe69..6536b7e 100644 --- a/desktop/src/components/ChatArea.tsx +++ b/desktop/src/components/ChatArea.tsx @@ -665,6 +665,28 @@ function stripToolNarration(content: string): string { return result || content; } +/** + * Strip dangling clarification references from text when ask_clarification tool was called. + * When the LLM calls ask_clarification, it often ends its text with phrases like + * "比如:" / "以下信息" / "以下选项" that reference the tool output — but the tool output + * is rendered in a separate ClarificationCard, so these become confusing dead-end sentences. + */ +function stripDanglingClarificationRef(text: string, hasClarificationTool: boolean): string { + if (!hasClarificationTool || !text) return text; + // Match trailing dangling references in Chinese and English + const patterns = [ + /[,,]\s*可以(?:提供以下|告诉我更多细节,)?(?:信息|选项|方向|细节|分类|类型)[::]\s*$/, + /[,,]\s*比如[::]\s*$/, + /[,,]\s*(?:例如|譬如|如以下)[::]\s*$/, + /,\s*(?:for example|such as|like|the following)[::]?\s*$/i, + ]; + for (const pat of patterns) { + const stripped = text.replace(pat, ''); + if (stripped !== text) return stripped; + } + return text; +} + function MessageBubble({ message, onRetry }: { message: Message; setInput?: (text: string) => void; onRetry?: () => void }) { if (message.role === 'tool') { return null; @@ -749,7 +771,10 @@ function MessageBubble({ message, onRetry }: { message: Message; setInput?: (tex ? (isUser ? message.content : s.toolName === 'ask_clarification') ?? false, + )} isStreaming={!!message.streaming} className="text-gray-700 dark:text-gray-200" /> diff --git a/desktop/src/components/ai/ToolCallChain.tsx b/desktop/src/components/ai/ToolCallChain.tsx index 446a6b1..9b23f67 100644 --- a/desktop/src/components/ai/ToolCallChain.tsx +++ b/desktop/src/components/ai/ToolCallChain.tsx @@ -166,7 +166,8 @@ interface ToolStepRowProps { } function ToolStepRow({ step, isActive, showConnector }: ToolStepRowProps) { - const [expanded, setExpanded] = useState(false); + // Clarification cards default to expanded so users see options immediately + const [expanded, setExpanded] = useState(step.toolName === 'ask_clarification'); const Icon = getToolIcon(step.toolName); const label = getToolLabel(step.toolName); const isRunning = step.status === 'running';