feat(cortex): Epic 5 - Integration & Polish complete (MVP DONE)

Epic 5 completes the Cortex V2 MVP with end-to-end integration:

E5.S1 - Feature Flag Guards
- ActionChainCard wrapped with CORTEX_MULTI_INTENT flag
- ClarificationCard wrapped with CORTEX_V2_ENABLED flag
- NudgeToast wrapped with CORTEX_NUDGE flag
- V1 UI remains functional when flags disabled

E5.S2 - Chain Execution Flow
- handleConfirmAction routes actions to artifacts via routeIntentToArtifact()
- Nudge evaluation triggered after successful action completion
- Sequential chain execution with auto-advance useEffect
- Chain auto-completes when all actions done

E5.S3 - Integration Tests (21 tests)
- Scenario 1: Simple input → Reflex handles (4 tests)
- Scenario 2: Multi-intent → Orchestrator handles (4 tests)
- Scenario 3: Context-dependent → Pronoun resolution (4 tests)
- Scenario 4: Wondzorg → Nudge suggestion (4 tests)
- Scenario 5: Graceful fallback (3 tests)
- Scenario 6: Chain building (2 tests)

E5.S4 - Demo Script
- 5-minute demo flow with exact phrases
- 5 scenes: Reflex speed, Multi-intent, Pronoun, Nudge, Clarification
- Backup scenarios documented
- Test phrases reference included

Files changed:
- components/cortex/chat/chat-panel.tsx (feature flags + execution)
- components/cortex/command-center/command-center.tsx (NudgeToast flag)
- lib/cortex/__tests__/cortex-v2.test.ts (NEW - 21 integration tests)
- docs/intent/demo-script-cortex-v2.md (NEW - demo documentation)
- docs/intent/bouwplan-cortex-v2.md (v1.5 - MVP complete)

MVP Status: 26/26 stories, 48/48 SP - 100% COMPLETE

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
colinislit
2026-01-01 13:16:20 +01:00
parent 4b759c9b3d
commit 6add89dc51
5 changed files with 860 additions and 89 deletions

View File

@@ -20,7 +20,9 @@ import { ClarificationCard } from './clarification-card';
import { ProcessingIndicator } from './processing-indicator';
import { useCortexStore } from '@/stores/cortex-store';
import { sendChatMessage } from '@/lib/cortex/chat-api';
import { parseActionFromResponse, shouldOpenArtifact } from '@/lib/cortex/action-parser';
import { parseActionFromResponse, shouldOpenArtifact, routeIntentToArtifact } from '@/lib/cortex/action-parser';
import { evaluateNudge } from '@/lib/cortex/nudge';
import { isFeatureEnabled } from '@/lib/config/feature-flags';
import { cn } from '@/lib/utils';
export function ChatPanel() {
@@ -44,6 +46,10 @@ export function ChatPanel() {
const setPendingClarification = useCortexStore((s) => s.setPendingClarification);
const resolveClarification = useCortexStore((s) => s.resolveClarification);
// Artifact & Nudge state (E5.S2)
const openArtifact = useCortexStore((s) => s.openArtifact);
const addSuggestion = useCortexStore((s) => s.addSuggestion);
// Refs for scrolling
const scrollContainerRef = useRef<HTMLDivElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -103,16 +109,52 @@ export function ChatPanel() {
return () => window.removeEventListener('keydown', handleGlobalKeyDown);
}, []);
// V2 Chain action handlers
// V2 Chain action handlers (E5.S2)
const handleConfirmAction = useCallback((actionId: string) => {
console.log('[ChatPanel] Confirming action:', actionId);
// Find the action in the active chain
const action = activeChain?.actions.find((a) => a.id === actionId);
if (!action || !activeChain) {
console.error('[ChatPanel] Action not found:', actionId);
return;
}
console.log('[ChatPanel] Confirming action:', actionId, action.intent);
updateActionStatus(actionId, 'executing');
// TODO: E5.S2 - Execute the actual action via API
// For now, simulate success after a short delay
setTimeout(() => {
updateActionStatus(actionId, 'success');
}, 500);
}, [updateActionStatus]);
// Route to artifact (uses existing artifact system)
const artifact = routeIntentToArtifact(
action.intent,
action.entities,
action.confidence
);
if (artifact) {
console.log('[ChatPanel] Opening artifact:', artifact.type);
openArtifact({
type: artifact.type,
prefill: artifact.prefill,
title: artifact.title,
});
}
// Mark as success (artifact is now open for user to complete)
updateActionStatus(actionId, 'success');
// E5.S2: Trigger nudge evaluation after successful action
if (isFeatureEnabled('CORTEX_NUDGE')) {
const suggestions = evaluateNudge({
intent: action.intent,
actionId,
entities: action.entities,
content: action.entities.content,
});
if (suggestions.length > 0) {
console.log('[ChatPanel] Nudge suggestions:', suggestions.length);
suggestions.forEach((suggestion) => addSuggestion(suggestion));
}
}
}, [activeChain, updateActionStatus, openArtifact, addSuggestion]);
const handleSkipAction = useCallback((actionId: string) => {
console.log('[ChatPanel] Skipping action:', actionId);
@@ -144,6 +186,40 @@ export function ChatPanel() {
setPendingClarification(null);
}, [setPendingClarification]);
// E5.S2: Sequential chain execution - auto-advance to next action
useEffect(() => {
if (!activeChain) return;
const actions = activeChain.actions;
const completedStatuses = ['success', 'skipped', 'failed'];
// Count completed actions
const completedCount = actions.filter((a) =>
completedStatuses.includes(a.status)
).length;
// Find next pending action
const nextPending = actions.find((a) => a.status === 'pending');
// If there's a completed action and a pending one, auto-advance
if (completedCount > 0 && nextPending) {
// Small delay for UI feedback before advancing
const timer = setTimeout(() => {
updateActionStatus(nextPending.id, 'confirming');
}, 300);
return () => clearTimeout(timer);
}
// If all actions are complete, finish the chain
if (completedCount === actions.length && actions.length > 0) {
const timer = setTimeout(() => {
console.log('[ChatPanel] All actions complete, finishing chain');
completeChain();
}, 500);
return () => clearTimeout(timer);
}
}, [activeChain, updateActionStatus, completeChain]);
// Check if we should show multi-intent UI
const showActionChain = activeChain && activeChain.actions.length > 1;
@@ -168,33 +244,37 @@ export function ChatPanel() {
</div>
)}
{/* V2: Multi-intent action chain */}
<AnimatePresence mode="wait">
{showActionChain && (
<ActionChainCard
key={activeChain.id}
chain={activeChain}
onConfirmAction={handleConfirmAction}
onSkipAction={handleSkipAction}
onRetryAction={handleRetryAction}
onDismissChain={handleDismissChain}
/>
)}
</AnimatePresence>
{/* V2: Multi-intent action chain (feature flagged) */}
{isFeatureEnabled('CORTEX_MULTI_INTENT') && (
<AnimatePresence mode="wait">
{showActionChain && (
<ActionChainCard
key={activeChain.id}
chain={activeChain}
onConfirmAction={handleConfirmAction}
onSkipAction={handleSkipAction}
onRetryAction={handleRetryAction}
onDismissChain={handleDismissChain}
/>
)}
</AnimatePresence>
)}
{/* V2: Clarification card for ambiguous input */}
<AnimatePresence mode="wait">
{pendingClarification && (
<ClarificationCard
key="clarification"
question={pendingClarification.question}
options={pendingClarification.options}
originalInput={pendingClarification.originalInput}
onSelectOption={handleSelectClarification}
onDismiss={handleDismissClarification}
/>
)}
</AnimatePresence>
{/* V2: Clarification card for ambiguous input (feature flagged) */}
{isFeatureEnabled('CORTEX_V2_ENABLED') && (
<AnimatePresence mode="wait">
{pendingClarification && (
<ClarificationCard
key="clarification"
question={pendingClarification.question}
options={pendingClarification.options}
originalInput={pendingClarification.originalInput}
onSelectOption={handleSelectClarification}
onDismiss={handleDismissClarification}
/>
)}
</AnimatePresence>
)}
{/* Invisible element to scroll to */}
<div ref={messagesEndRef} />

View File

@@ -26,6 +26,7 @@ import { ChatPanel } from '../chat/chat-panel';
import { ArtifactArea } from '../artifacts/artifact-area';
import { getArtifactTitle } from '../artifacts/artifact-container';
import { routeIntentToArtifact } from '@/lib/cortex/action-parser';
import { isFeatureEnabled } from '@/lib/config/feature-flags';
export function CommandCenter() {
const {
@@ -131,42 +132,44 @@ export function CommandCenter() {
</div>
</div>
{/* Nudge Toast - E4 (fixed position, bottom-left above chat) */}
<AnimatePresence mode="wait">
{suggestions.length > 0 && (
<div className="fixed bottom-20 left-4 right-4 lg:left-4 lg:right-auto lg:w-[38%] z-50">
<NudgeToast
key={suggestions[0].id}
suggestion={suggestions[0]}
onAccept={(id) => {
const suggestion = suggestions.find((s) => s.id === id);
if (suggestion) {
// Route the suggested intent to an artifact
const artifact = routeIntentToArtifact(
suggestion.suggestion.intent,
suggestion.suggestion.entities,
1.0 // High confidence since user explicitly accepted
);
{/* Nudge Toast - E4 (fixed position, bottom-left above chat, feature flagged) */}
{isFeatureEnabled('CORTEX_NUDGE') && (
<AnimatePresence mode="wait">
{suggestions.length > 0 && (
<div className="fixed bottom-20 left-4 right-4 lg:left-4 lg:right-auto lg:w-[38%] z-50">
<NudgeToast
key={suggestions[0].id}
suggestion={suggestions[0]}
onAccept={(id) => {
const suggestion = suggestions.find((s) => s.id === id);
if (suggestion) {
// Route the suggested intent to an artifact
const artifact = routeIntentToArtifact(
suggestion.suggestion.intent,
suggestion.suggestion.entities,
1.0 // High confidence since user explicitly accepted
);
if (artifact) {
console.log('[CommandCenter] Opening artifact from nudge:', artifact.type);
openArtifact({
type: artifact.type,
prefill: artifact.prefill,
title: artifact.title,
});
if (artifact) {
console.log('[CommandCenter] Opening artifact from nudge:', artifact.type);
openArtifact({
type: artifact.type,
prefill: artifact.prefill,
title: artifact.title,
});
}
}
}
acceptSuggestion(id);
}}
onDismiss={(id) => {
dismissSuggestion(id);
console.log('[CommandCenter] Nudge dismissed:', id);
}}
/>
</div>
)}
</AnimatePresence>
acceptSuggestion(id);
}}
onDismiss={(id) => {
dismissSuggestion(id);
console.log('[CommandCenter] Nudge dismissed:', id);
}}
/>
</div>
)}
</AnimatePresence>
)}
</div>
);
}