feat: AI marker system prompt fixes, TaskLane activation, and FlowPilot updates

- Fix system prompt to ensure [QUESTIONS]/[ACTIONS] markers in AI responses
- Add format reminder injection to user messages for marker compliance
- Wire TaskLane activation in prefill and resume paths
- Add ActionCardGroup component for structured question/action rendering
- Update FlowPilot session and step card components
- Update ai-session schemas and types for marker data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-26 19:57:39 +00:00
parent 37d217b12a
commit 3c0a29115c
14 changed files with 913 additions and 42 deletions

View File

@@ -2,9 +2,12 @@ import { useEffect, useRef, useState } from 'react'
import { useParams, useSearchParams, useLocation, useBlocker, useNavigate } from 'react-router-dom'
import { Sparkles, Loader2, AlertTriangle, CheckCircle2, ArrowUpRight, FileText, MoreHorizontal, Pause, X } from 'lucide-react'
import { useFlowPilotSession } from '@/hooks/useFlowPilotSession'
import { useBranching } from '@/hooks/useBranching'
import { FlowPilotIntake, FlowPilotSession, SessionBriefing } from '@/components/flowpilot'
import { EscalateModal } from '@/components/flowpilot/EscalateModal'
import { StatusUpdateModal } from '@/components/flowpilot/StatusUpdateModal'
import { HandoffModal } from '@/components/session/HandoffModal'
import { handoffsApi } from '@/api/handoffs'
import { aiSessionsApi } from '@/api'
import { toast } from '@/lib/toast'
@@ -16,12 +19,14 @@ export default function FlowPilotSessionPage() {
const prefill = (location.state as { prefill?: string } | null)?.prefill || ''
const isPickup = searchParams.get('pickup') === 'true'
const fp = useFlowPilotSession()
const branching = useBranching()
const prefillHandledRef = useRef(false)
const [showOverflow, setShowOverflow] = useState(false)
const [showResolve, setShowResolve] = useState(false)
const [showEscalate, setShowEscalate] = useState(false)
const [showAbandon, setShowAbandon] = useState(false)
const [showStatusUpdate, setShowStatusUpdate] = useState(false)
const [showHandoff, setShowHandoff] = useState(false)
const [resolutionSummary, setResolutionSummary] = useState('')
const [submitting, setSubmitting] = useState(false)
@@ -48,6 +53,13 @@ export default function FlowPilotSessionPage() {
}
}, [sessionId]) // eslint-disable-line react-hooks/exhaustive-deps
// Load branches when session is branching
useEffect(() => {
if (fp.session?.is_branching && fp.session.id) {
branching.loadBranches(fp.session.id)
}
}, [fp.session?.is_branching, fp.session?.id]) // eslint-disable-line react-hooks/exhaustive-deps
const handlePickupContinue = async () => {
if (!sessionId) return
setPickingUp(true)
@@ -64,6 +76,15 @@ export default function FlowPilotSessionPage() {
}
}
const handleBranchSwitch = async (branchId: string) => {
if (!fp.session) return
const result = await branching.switchBranch(fp.session.id, branchId)
if (result) {
// Reload session to get updated steps for the switched branch
await fp.loadSession(fp.session.id)
}
}
const handlePickupFresh = async (context: string) => {
if (!sessionId) return
setPickingUp(true)
@@ -375,6 +396,9 @@ export default function FlowPilotSessionPage() {
onRate={fp.rateSession}
onReloadSession={() => fp.loadSession(fp.session!.id)}
onGenerateStatusUpdate={(audience, length, context) => fp.generateStatusUpdate({ audience, length, context })}
branches={branching.branches}
activeBranchId={branching.activeBranchId}
onBranchSwitch={handleBranchSwitch}
/>
</div>
@@ -476,6 +500,18 @@ export default function FlowPilotSessionPage() {
context="status"
hasPsaTicket={!!fp.session.psa_ticket_id}
/>
{/* Handoff modal (branching sessions) */}
{fp.session.is_branching && showHandoff && (
<HandoffModal
onClose={() => setShowHandoff(false)}
onSubmit={async (data) => {
await handoffsApi.createHandoff(fp.session!.id, data)
setShowHandoff(false)
toast.success('Handoff created')
}}
/>
)}
</div>
)
}