fix: butler audit critical fixes — pain detection, proposal trigger, URI + data flow
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
5 fixes from focused audit: - Connect analyze_for_pain_signals() to post_conversation_hook (pain points now auto-created) - Add "generate solution" button in InsightsSection for high-confidence pain points (>=0.7) - Fix Memory URI mismatch: viking://agents/ → viking://agent/ (singular) - Remove duplicate .then() chain in useButlerInsights (was destructuring undefined) - Update stale director.rs doc comment (multi-agent now enabled by default)
This commit is contained in:
@@ -6,10 +6,9 @@
|
|||||||
//! - Supporting multiple scheduling strategies
|
//! - Supporting multiple scheduling strategies
|
||||||
//! - Coordinating agent responses
|
//! - Coordinating agent responses
|
||||||
//!
|
//!
|
||||||
//! **Status**: This module is fully implemented but gated behind the `multi-agent` feature.
|
//! **Status**: This module is enabled by default via the `multi-agent` feature in the
|
||||||
//! The desktop build does not currently enable this feature. When multi-agent support
|
//! desktop build. The Director orchestrates butler delegation, task decomposition, and
|
||||||
//! is ready for production, add Tauri commands to create and interact with the Director,
|
//! expert agent assignment through `butler_delegate()`.
|
||||||
//! and enable the feature in `desktop/src-tauri/Cargo.toml`.
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|||||||
@@ -78,6 +78,37 @@ pub async fn post_conversation_hook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 1.6: Detect pain signals from user message
|
||||||
|
if !_user_message.is_empty() {
|
||||||
|
let messages = vec![zclaw_types::Message::user(_user_message)];
|
||||||
|
if let Some(analysis) = crate::intelligence::pain_aggregator::analyze_for_pain_signals(&messages) {
|
||||||
|
let severity_str = match analysis.severity {
|
||||||
|
crate::intelligence::pain_aggregator::PainSeverity::High => "high",
|
||||||
|
crate::intelligence::pain_aggregator::PainSeverity::Medium => "medium",
|
||||||
|
crate::intelligence::pain_aggregator::PainSeverity::Low => "low",
|
||||||
|
};
|
||||||
|
match crate::intelligence::pain_aggregator::butler_record_pain_point(
|
||||||
|
agent_id.to_string(),
|
||||||
|
"default_user".to_string(),
|
||||||
|
analysis.summary,
|
||||||
|
analysis.category,
|
||||||
|
severity_str.to_string(),
|
||||||
|
_user_message.to_string(),
|
||||||
|
analysis.evidence,
|
||||||
|
).await {
|
||||||
|
Ok(pain) => {
|
||||||
|
debug!(
|
||||||
|
"[intelligence_hooks] Pain point recorded: {} (confidence: {:.2}, count: {})",
|
||||||
|
pain.summary, pain.confidence, pain.occurrence_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("[intelligence_hooks] Failed to record pain point: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Step 2: Record conversation for reflection
|
// Step 2: Record conversation for reflection
|
||||||
let mut engine = reflection_state.lock().await;
|
let mut engine = reflection_state.lock().await;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ChevronDown, ChevronUp, AlertTriangle, TrendingUp, Info } from 'lucide-react';
|
import { ChevronDown, ChevronUp, AlertTriangle, TrendingUp, Info, Lightbulb, Loader2 } from 'lucide-react';
|
||||||
import type { ButlerPainPoint } from '../../lib/viking-client';
|
import type { ButlerPainPoint } from '../../lib/viking-client';
|
||||||
|
import { generateButlerSolution } from '../../lib/viking-client';
|
||||||
|
|
||||||
interface InsightsSectionProps {
|
interface InsightsSectionProps {
|
||||||
painPoints: ButlerPainPoint[];
|
painPoints: ButlerPainPoint[];
|
||||||
|
onGenerate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SeverityBadge({ severity }: { severity: ButlerPainPoint['severity'] }) {
|
function SeverityBadge({ severity }: { severity: ButlerPainPoint['severity'] }) {
|
||||||
@@ -28,8 +30,21 @@ function StatusBadge({ status }: { status: ButlerPainPoint['status'] }) {
|
|||||||
return <span className={`px-1.5 py-0.5 rounded text-xs ${c.bg} ${c.text}`}>{c.label}</span>;
|
return <span className={`px-1.5 py-0.5 rounded text-xs ${c.bg} ${c.text}`}>{c.label}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InsightsSection({ painPoints }: InsightsSectionProps) {
|
export function InsightsSection({ painPoints, onGenerate }: InsightsSectionProps) {
|
||||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||||
|
const [generating, setGenerating] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleGenerateSolution = async (painId: string) => {
|
||||||
|
setGenerating(painId);
|
||||||
|
try {
|
||||||
|
await generateButlerSolution(painId);
|
||||||
|
onGenerate?.();
|
||||||
|
} catch {
|
||||||
|
onGenerate?.();
|
||||||
|
} finally {
|
||||||
|
setGenerating(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (painPoints.length === 0) {
|
if (painPoints.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -112,6 +127,22 @@ export function InsightsSection({ painPoints }: InsightsSectionProps) {
|
|||||||
<span>|</span>
|
<span>|</span>
|
||||||
<span>最近: {new Date(pp.last_seen).toLocaleDateString()}</span>
|
<span>最近: {new Date(pp.last_seen).toLocaleDateString()}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{(pp.status === 'confirmed' || pp.status === 'detected') && pp.confidence >= 0.7 && (
|
||||||
|
<div className="pt-1">
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-xs font-medium bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50"
|
||||||
|
onClick={() => handleGenerateSolution(pp.id)}
|
||||||
|
disabled={generating === pp.id}
|
||||||
|
>
|
||||||
|
{generating === pp.id ? (
|
||||||
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Lightbulb className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
|
生成解决方案
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function MemorySection({ agentId }: MemorySectionProps) {
|
|||||||
if (!agentId) return;
|
if (!agentId) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
listVikingResources(`viking://agents/${agentId}/memories/`)
|
listVikingResources(`viking://agent/${agentId}/memories/`)
|
||||||
.then((entries) => {
|
.then((entries) => {
|
||||||
setMemories(entries as MemoryEntry[]);
|
setMemories(entries as MemoryEntry[]);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function ButlerPanel({ agentId }: ButlerPanelProps) {
|
|||||||
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||||
我最近在关注
|
我最近在关注
|
||||||
</h3>
|
</h3>
|
||||||
<InsightsSection painPoints={painPoints} />
|
<InsightsSection painPoints={painPoints} onGenerate={refresh} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Proposals section */}
|
{/* Proposals section */}
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ export function useButlerInsights(agentId: string | undefined): ButlerInsightsSt
|
|||||||
setError(errors.join('; '));
|
setError(errors.join('; '));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(([pains, props]) => {
|
|
||||||
setPainPoints(pains);
|
|
||||||
setProposals(props);
|
|
||||||
})
|
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, [agentId]);
|
}, [agentId]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user