feat(ai-session): add Phase 2 PSA integration, escalation handoff, and session management
Phase 2 of the FlowPilot-First Pivot connecting AI sessions to ConnectWise PSA: Slice 1 — PSA Ticket Intake: - FlowPilotEngine accepts psa_ticket intake with graceful CW API fallback - Ticket picker on intake screen (refactored TicketPickerModal for dual-mode) - Ticket context card in session sidebar Slice 2 — Auto Documentation Push: - PSA documentation service with resolution/escalation note formatting - Time entry creation via new ConnectWise provider method - Automatic retry scheduler (APScheduler, 5min interval, 3 retries) - PSA push status indicators in frontend with manual retry button - Member mapping warning when CW member not mapped Slice 3 — Session Pause/Resume & Escalation Handoff: - Pause/resume endpoints for same-engineer session bookmarking - Escalation flow: requesting_escalation status, self-escalation blocked - Enhanced escalation package with LLM-generated hypotheses/suggestions - Pickup endpoint with continue/fresh resume modes and briefing step - Escalation queue (sidebar nav + dedicated page) - SessionBriefing component with continue/fresh choice UI - EscalateModal with PSA-aware button text Slice 4 — Mid-Session Ticket Linking: - Link ticket retroactively with context injection into system prompt - Link Ticket button in session sidebar Slice 5 — FlowPilot PSA Settings: - Settings tab on IntegrationsPage with 7 configurable options - Stored as flowpilot_settings JSONB on PsaConnection Database: 2 migrations (flowpilot_settings, psa_post_log changes, status constraint) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,11 +12,14 @@ type Mode = 'search' | 'manual'
|
||||
interface Props {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
sessionId: string
|
||||
onLinked: (ticketId: string, ticket: PSATicketInfo) => void
|
||||
/** Legacy session linking mode — pass sessionId + onLinked */
|
||||
sessionId?: string
|
||||
onLinked?: (ticketId: string, ticket: PSATicketInfo) => void
|
||||
/** Selection-only mode — pass onSelect instead. Returns selected ticket without linking. */
|
||||
onSelect?: (ticketId: string, ticket: PSATicketInfo) => void
|
||||
}
|
||||
|
||||
export function TicketPickerModal({ open, onClose, sessionId, onLinked }: Props) {
|
||||
export function TicketPickerModal({ open, onClose, sessionId, onLinked, onSelect }: Props) {
|
||||
const [mode, setMode] = useState<Mode>('search')
|
||||
|
||||
// Search mode state
|
||||
@@ -138,11 +141,22 @@ export function TicketPickerModal({ open, onClose, sessionId, onLinked }: Props)
|
||||
const handleLink = async () => {
|
||||
if (!selectedTicket || !selectedTicketId) return
|
||||
|
||||
// Selection-only mode — return ticket data without linking
|
||||
if (onSelect) {
|
||||
onSelect(selectedTicketId, selectedTicket)
|
||||
handleReset()
|
||||
onClose()
|
||||
return
|
||||
}
|
||||
|
||||
// Legacy session linking mode
|
||||
if (!sessionId) return
|
||||
|
||||
setIsLinking(true)
|
||||
setError(null)
|
||||
try {
|
||||
await sessionPsaApi.linkTicket(sessionId, selectedTicketId)
|
||||
onLinked(selectedTicketId, selectedTicket)
|
||||
onLinked?.(selectedTicketId, selectedTicket)
|
||||
handleReset()
|
||||
} catch (err: unknown) {
|
||||
const message =
|
||||
@@ -195,7 +209,7 @@ export function TicketPickerModal({ open, onClose, sessionId, onLinked }: Props)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={open} onClose={handleClose} title="Link ConnectWise Ticket" size="sm">
|
||||
<Modal isOpen={open} onClose={handleClose} title={onSelect ? 'Select ConnectWise Ticket' : 'Link ConnectWise Ticket'} size="sm">
|
||||
<div className="space-y-4">
|
||||
{/* Mode tabs */}
|
||||
<div className="flex gap-1 rounded-lg bg-white/[0.03] p-1">
|
||||
@@ -411,7 +425,7 @@ export function TicketPickerModal({ open, onClose, sessionId, onLinked }: Props)
|
||||
loading={isLinking}
|
||||
>
|
||||
<Ticket className="h-4 w-4" />
|
||||
Link This Ticket
|
||||
{onSelect ? 'Select This Ticket' : 'Link This Ticket'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user