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:
@@ -27,6 +27,8 @@ export interface UseFlowPilotSession {
|
||||
respondToStep: (response: StepResponseRequest) => Promise<void>
|
||||
resolveSession: (data: ResolveSessionRequest) => Promise<SessionDocumentation>
|
||||
escalateSession: (data: EscalateSessionRequest) => Promise<SessionDocumentation>
|
||||
pauseSession: () => Promise<void>
|
||||
resumeOwnSession: () => Promise<void>
|
||||
rateSession: (rating: number, feedback?: string) => Promise<void>
|
||||
loadSession: (sessionId: string) => Promise<void>
|
||||
|
||||
@@ -37,6 +39,9 @@ export interface UseFlowPilotSession {
|
||||
|
||||
// Post-close
|
||||
documentation: SessionDocumentation | null
|
||||
psaPushStatus: string | null
|
||||
psaPushError: string | null
|
||||
memberMappingWarning: string | null
|
||||
}
|
||||
|
||||
export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
@@ -47,6 +52,9 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [documentation, setDocumentation] = useState<SessionDocumentation | null>(null)
|
||||
const [psaPushStatus, setPsaPushStatus] = useState<string | null>(null)
|
||||
const [psaPushError, setPsaPushError] = useState<string | null>(null)
|
||||
const [memberMappingWarning, setMemberMappingWarning] = useState<string | null>(null)
|
||||
|
||||
const startSession = useCallback(async (intake: AISessionCreateRequest) => {
|
||||
setIsLoading(true)
|
||||
@@ -73,6 +81,9 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
resolution_action: null,
|
||||
escalation_reason: null,
|
||||
session_feedback: null,
|
||||
psa_ticket_id: intake.psa_ticket_id ?? null,
|
||||
psa_connection_id: intake.psa_connection_id ?? null,
|
||||
ticket_data: null,
|
||||
steps: [firstStep],
|
||||
})
|
||||
setAllSteps([firstStep])
|
||||
@@ -120,6 +131,9 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
const result = await aiSessionsApi.resolveSession(session.id, data)
|
||||
setSession(prev => prev ? { ...prev, status: 'resolved' } : null)
|
||||
setDocumentation(result.documentation)
|
||||
setPsaPushStatus(result.psa_push_status)
|
||||
setPsaPushError(result.psa_push_error)
|
||||
setMemberMappingWarning(result.member_mapping_warning)
|
||||
setCurrentStep(null)
|
||||
toast.success('Session resolved')
|
||||
return result.documentation
|
||||
@@ -139,6 +153,9 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
const result = await aiSessionsApi.escalateSession(session.id, data)
|
||||
setSession(prev => prev ? { ...prev, status: 'escalated' } : null)
|
||||
setDocumentation(result.documentation)
|
||||
setPsaPushStatus(result.psa_push_status)
|
||||
setPsaPushError(result.psa_push_error)
|
||||
setMemberMappingWarning(result.member_mapping_warning)
|
||||
setCurrentStep(null)
|
||||
toast.success('Session escalated')
|
||||
return result.documentation
|
||||
@@ -151,6 +168,28 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const pauseSession = useCallback(async () => {
|
||||
if (!session) return
|
||||
try {
|
||||
await aiSessionsApi.pauseSession(session.id)
|
||||
setSession(prev => prev ? { ...prev, status: 'paused' } : null)
|
||||
toast.success('Session paused')
|
||||
} catch {
|
||||
toast.error('Failed to pause session')
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const resumeOwnSession = useCallback(async () => {
|
||||
if (!session) return
|
||||
try {
|
||||
await aiSessionsApi.resumeSession(session.id)
|
||||
setSession(prev => prev ? { ...prev, status: 'active' } : null)
|
||||
toast.success('Session resumed')
|
||||
} catch {
|
||||
toast.error('Failed to resume session')
|
||||
}
|
||||
}, [session])
|
||||
|
||||
const rateSession = useCallback(async (rating: number, feedback?: string) => {
|
||||
if (!session) return
|
||||
try {
|
||||
@@ -169,7 +208,12 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
const detail = await aiSessionsApi.getSession(sessionId)
|
||||
setSession(detail)
|
||||
setAllSteps(detail.steps)
|
||||
setCurrentStep(detail.status === 'active' ? detail.steps[detail.steps.length - 1] ?? null : null)
|
||||
// Set current step for active and paused sessions (paused can be resumed)
|
||||
setCurrentStep(
|
||||
detail.status === 'active' || detail.status === 'paused'
|
||||
? detail.steps[detail.steps.length - 1] ?? null
|
||||
: null
|
||||
)
|
||||
|
||||
if (detail.status === 'resolved' || detail.status === 'escalated') {
|
||||
const doc = await aiSessionsApi.getDocumentation(sessionId)
|
||||
@@ -199,11 +243,16 @@ export function useFlowPilotSession(): UseFlowPilotSession {
|
||||
respondToStep,
|
||||
resolveSession,
|
||||
escalateSession,
|
||||
pauseSession,
|
||||
resumeOwnSession,
|
||||
rateSession,
|
||||
loadSession,
|
||||
isActive,
|
||||
canResolve,
|
||||
canEscalate,
|
||||
documentation,
|
||||
psaPushStatus,
|
||||
psaPushError,
|
||||
memberMappingWarning,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user