Adds network_diagram_ai_service.py with generate_diagram() function that
calls the AI provider to convert plain-English network descriptions into
structured DiagramNode/DiagramEdge data. Registers the action in
ACTION_MODEL_MAP as a standard-tier route.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Service layer (production code):
- branch_manager: set account_id on SessionBranch (root + fork) and ForkPoint
from session.account_id; load session in create_fork for this purpose
- handoff_manager: set account_id on SessionHandoff from session.account_id
- ai_suggestions endpoint: set account_id on AISuggestion from current_user
- steps endpoint (/feedback): set account_id on StepRating from current_user
- ratings endpoint: set account_id on StepRating from current_user
Test infrastructure:
- conftest.py: seed PLATFORM_ACCOUNT_ID (00000000-...-0001) account after
Base.metadata.create_all so global categories and gallery items have a valid FK
- test_rls_isolation: add _ensure_rls_schema fixture that runs
'alembic upgrade head' before module tests — previous function-scoped
test_db fixtures drop the schema, leaving the RLS tests with no tables
- test_branding: create Account before User in helper functions
- test_admin_gallery: set account_id=PLATFORM_ACCOUNT_ID on Tree/ScriptTemplate
- test_public_templates: set account_id=PLATFORM_ACCOUNT_ID on Tree,
ScriptTemplate, TreeCategory
- test_resolution_outputs: set account_id=session.account_id on
SessionResolutionOutput
- test_analytics_phase5: set account_id on PsaPostLog
- test_draft_trees: replace account_id=None with PLATFORM_ACCOUNT_ID in
migration default test (NOT NULL now enforced)
- test_maintenance_schedules: set account_id on other_tree
- test_save_session_as_tree: set account_id on all 5 Session() constructors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add tenant data isolation design spec
Complete architecture plan for multi-tenant data isolation across
all layers (PostgreSQL RLS, application-layer filtering, schema
migration, testing strategy, and phased rollout checklist).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add background job isolation policy to tenant isolation spec
Documents policy for all 5 existing background jobs:
- Knowledge Flywheel and PSA Retry flagged for account_id threading
- Chat Retention already follows correct pattern (model for others)
- Maintenance Schedule Firing needs account_id in queries + Session creation
- AI Conversation Expiry approved as cross-tenant with justification
Adds approved cross-tenant query registry and Phase 2 checklist items.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add tenant isolation Phase 0 implementation plan
8 tasks covering: CRITICAL copilot hotfix, tenant_filter() helper,
get_tenant_context dependency, analytics/category/AI session gap fixes,
full UUID endpoint audit, TargetList dead code audit, teams orphan
check, and CI grep check for missing tenant filters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: CRITICAL — scope copilot tree query to current account
A user who knew another account's tree UUID could start a copilot
conversation, causing the tree's full node structure, names, and
descriptions to be sent to the AI as part of the system prompt.
Fix: add account_id (or is_default / visibility='public') filter to
the tree SELECT in copilot_service.start_conversation(). Returns 404
for inaccessible trees. Test added in test_tenant_isolation_p0.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Three fixes from beta tester session feedback:
1. MCP error handling (backend/app/services/assistant_chat_service.py)
- The MCP Microsoft Learn integration was catching only BadRequestError.
Any other error type (APIStatusError, APIConnectionError, timeout) from
the external MCP server propagated as a 502, causing the generic error.
- Now catches all Exception types when MCP is active and retries without
MCP using the stable client.messages.create endpoint.
2. Frontend error UX (frontend/src/pages/AssistantChatPage.tsx)
- catch {} was silently swallowing all errors and inserting a generic
assistant message. Now: differentiates 429 (rate limit) vs 502/503
(AI unavailable), removes the optimistic user message on failure,
restores the failed message to the input so users can retry without
retyping, and logs errors to console for debugging.
3. Image attachments visible in chat (frontend/src/components/assistant/ChatMessage.tsx)
- Uploaded images were sent to the AI correctly but never shown in the
chat thread. Now captures preview URLs before clearing pendingUploads
and renders thumbnails above the user bubble, clickable to full size.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add persistent session header with title, status badge, Resolve,
Escalate, and Update Ticket/Share Update buttons — mirrors
FlowPilotSessionPage pattern exactly
- Update Ticket label when psa_ticket_id present, Share Update otherwise
- Full mobile support via ⋯ overflow menu (Resolve, Escalate, Update, Pause)
- Strip _(not yet completed)_ markers from stored conversation_messages
in unified_chat_service to prevent stale task lane items from prior
turns leaking into new sessions via the AI's re-include instruction
- Add currentChatRef guard to handleResumeNew (was missing unlike handleSend)
- Remove Update/Conclude from chatbar — toolbar is now input utilities only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import and call clearTaskState before updating questions/actions in
handleSend and handleTaskSubmit so new AI tasks always replace stale
sessionStorage cache instead of being overridden by it
- Include pending (not yet completed) tasks in the AI message on partial
submit so the AI knows which tasks were left unanswered
- Fix stale closure in TaskLane saveTaskLane useEffect — use refs for
questions/actions so the debounced backend save always uses current values
- Add responses field to pending_task_lane TypeScript type, removing the
unsafe double-cast in selectChat
- Instruct the AI to re-surface incomplete tasks unless ≥75% confident
the information is no longer needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reformat PSA resolution/escalation notes: clean single-line header,
steps with engineer responses inline, remove duplicate timing blocks,
remove AI confidence section, add follow-up recommendations
- Standardize time display to decimal hours (e.g. 0.25 hrs) across all
note formatters and status update context
- Add follow_up_recommendations to SessionDocumentation schema and
surface in SessionDocView; extracted from resolution suggestion steps
- Add _build_what_we_know() helper: uses session.evidence_items when
cockpit branch merges, falls back to deriving findings from steps
- Fix option label lookup in generate_status_update (was passing raw
machine values to AI instead of human-readable labels)
- Add 'What We Know' section to status update ticket notes prompt
- Improve _build_session_context in resolution_output_generator to
include intake text and full step details instead of truncated chat
- Add request_info audience type: client-facing information request
that skips the length step and generates a numbered question list
- Improve client_update and email_draft prompts with per-context
guidance (status/resolution/escalation) and fix escalation subject
line from 'Specialist Review' to 'Specialist Assistance'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- script_builder endpoint: pg_advisory_xact_lock on user_id before
session count check, preventing concurrent creates from both passing
the MAX_SESSIONS_PER_USER guard
- script_builder_service send_message: pg_advisory_xact_lock on session_id
before message count check, preventing concurrent sends from both
passing the MAX_MESSAGES_PER_SESSION guard
- script_builder_service save_to_library: replace check-then-insert slug
logic with IntegrityError retry loop (3 attempts with fresh UUID suffix);
add unique constraint on script_templates.slug (migration 070)
- ScriptBuilderPage: add creatingSessionRef to serialize concurrent
handleSend calls that would otherwise both call createSession() while
session is still null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The generate_status_update service inserted AISessionStep with
step_type='status_update' which violated the DB CHECK constraint,
causing a 500 error. Also fix incorrect field name confidence_score
(should be confidence_at_step) and remove nonexistent confidence_tier.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously save_to_library() hardcoded parameters_schema to empty and
always used session.latest_script. Now accepts optional overrides from
the frontend for parameterized script bodies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Task lane questions/actions are now saved to a pending_task_lane JSONB
column on ai_sessions, restoring them on session switch or page reload.
Partial submit no longer force-clears the lane — the AI response
controls what stays. Also removes redundant "New Session" button from
the sidebar (dashboard already provides this).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add DOCX MIME type to ALLOWED_DOCUMENT_TYPES in storage_service.py
- Add python-docx text extraction in _generate_ai_description
- Extract shared _store_document_content helper for PDF/DOCX
- Add python-docx>=1.1.0 to requirements.txt
- Add tests for docx upload acceptance and document fetch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PDF uploads were stored in S3 and had text extracted during upload, but
fetch_upload_images() filtered exclusively for image MIME types, so
document content never reached the AI.
- Add fetch_upload_documents() in storage_service.py to retrieve
extracted_content for PDFs and text files
- Update ai_sessions.py chat endpoint to call both fetch_upload_images
and fetch_upload_documents, injecting document text as context
- Add PDF text extraction in _generate_ai_description (pypdf)
- Add pypdf>=4.0.0 to requirements.txt
- Fix test_db teardown to avoid connection pool issues
- Add 5 tests for fetch_upload_documents
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
Adds ResolutionOutputGenerator service that generates PSA ticket notes,
knowledge base article draft, and client summary on session resolve, plus
integration tests for generate_all and edit_output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unified park/escalate handoff management with snapshot generation,
AI diagnostic assessment for escalations (via _call_ai), claim workflow
that reactivates sessions, PSA push via existing psa_documentation_service,
and team queue query. Dual-writes to ai_sessions.escalation_package for
backward compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add is_branching guard to unified_chat_service.send_chat_message() that
routes messages through BranchAwarePromptBuilder when a session has active
branching. Add branch_id to all AISessionStep constructor calls in
flowpilot_engine.py via optional branch_id param on _create_step_from_parsed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pure function that assembles system prompt, cross-branch context,
history, and images for _call_ai — no DB access, no LLM calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements branch lifecycle management for conversational branching:
create_root_branch, create_fork, switch_branch, mark_branch_status,
revive_branch, get_branch_tree, and build_cross_branch_context.
Five integration tests cover the full lifecycle from root creation
through forking, switching, dead-end marking, and tree retrieval.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The old /assistant/chats/* CRUD endpoints and assistant_chat_service
chat functions were unused — the frontend exclusively uses
/ai-sessions/{id}/chat (unified_chat_service) for all chat operations.
Removed:
- Chat CRUD endpoints (create, list, get, send, delete, conclude)
- assistant_chat_service: create_chat, send_message,
generate_conclusion_summary, CONCLUSION_SYSTEM_PROMPT
- Frontend: assistantChatApi chat methods, dead types
(AssistantChat, AssistantChatMessage, ConcludeChatRequest, etc.)
Kept:
- /assistant/retention endpoints (used by ChatRetentionSettingsPage)
- Shared AI infrastructure (_call_ai, _call_anthropic_cached,
ASSISTANT_SYSTEM_PROMPT, _auto_title) — imported by unified_chat_service
Moved:
- fetch_upload_images + resize_image_for_vision → storage_service.py
(shared location, not tied to dead endpoint)
Also added "Image Analysis" section to system prompt so Claude knows
to describe attached screenshots.
-650 lines of dead code removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The frontend calls /ai-sessions/{id}/chat (unified_chat_service), not
/assistant/chats/{id}/messages (assistant_chat_service). The previous
commit wired images into the wrong backend. This fixes it:
- ai_session.py schema: add upload_ids to ChatMessageRequest
- ai_sessions.py endpoint: fetch images via _fetch_upload_images
- unified_chat_service: accept and forward images to _call_ai
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backend: ChatMessageRequest accepts upload_ids, endpoint fetches
images from S3, base64-encodes them, passes to Claude as multimodal
content blocks (vision API)
- Backend: add download_file() to storage_service for fetching from S3
- Frontend: handleSend collects completed upload IDs from pendingUploads
and includes them in the sendChatMessage API call
- Frontend: prefill handler passes upload IDs from dashboard nav state
- Enables paste-screenshot → AI-sees-it flow end-to-end
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add session_type ('guided'|'chat') and title columns to ai_sessions,
enabling both FlowPilot guided sessions and assistant chat sessions to
live in a single table. This is the foundation for a unified session
history and consistent UX across both interaction modes.
Backend:
- Migration 066: session_type + title columns
- unified_chat_service: chat sessions on ai_sessions with same AI/RAG
- POST /ai-sessions supports session_type='chat' creation
- POST /ai-sessions/{id}/chat for chat messages
- DELETE /ai-sessions/{id} for session deletion
- session_type filter on GET /ai-sessions
Frontend:
- AssistantChatPage rewired to aiSessionsApi (no more assistantChatApi)
- /assistant/:sessionId route for deep-linking
- Session history: type filter pills (All/Guided/Chat), type icons
- Dashboard: both types shown with correct routing and icons
- Fixed glass-border → border-default in dashboard components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Engineers can now generate AI-powered status updates during active FlowPilot
sessions and after resolve/escalate. Three audiences (Ticket Notes, Client
Update, Email Draft) with Quick/Detailed length options. Copy to clipboard
with one click. Client names auto-inserted from intake/PSA context.
Backend: new endpoint POST /ai-sessions/{id}/status-update with audience-aware
system prompts. Frontend: StatusUpdateModal with 2-step selection flow,
Share Update button in action bar, Share Resolution/Escalation on completed
sessions. Also updates Solutions Library spec with Community tier design.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract JSONB messages array from script_builder_sessions into a proper
script_builder_messages table with individual columns for role, content,
script, tokens, etc. Migration handles data migration from JSONB to rows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Message bar now fixed-positioned above action bar with full-width
layout (respects both app sidebar and session sidebar)
- Added abandon_session endpoint (POST /ai-sessions/{id}/abandon)
- Added "Close" button to FlowPilot action bar with confirmation dialog
- Session can now be closed without resolving or escalating
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FlowPilot was jumping straight to script generation without asking if
the user preferred GUI guidance. Now it asks "GUI or script?" first
when a task can be done either way.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Also fix bug in save_to_library: remove invalid 'language' kwarg
passed to ScriptTemplate constructor (column doesn't exist on model).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Script Builder service with language-specific system prompts (PowerShell, Bash, Python)
- AI-powered script generation with code block extraction and filename detection
- Context window management (last 20 messages) and session message limits
- REST API: CRUD sessions, send messages, save to Script Library
- Rate limiting on message endpoint (10/min), max 5 concurrent sessions per user
- Registered script_build action in AI model tier routing (standard tier)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User model has 'name', not 'display_name'. Fixed in flowpilot_engine
(escalate notify + pickup briefing) and psa_documentation_service
(engineer name in exported docs).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: embedding generation could break the DB transaction via a failed
SQL statement. The except block caught the Python error but left the transaction
in a failed state. Subsequent queries (_record_usage → subscription lookup)
then failed with InFailedSQLTransactionError.
Fixes:
- session_embedding_service: use begin_nested() savepoint so failures don't
poison the parent transaction
- ai_sessions.py: add db.rollback() before _record_usage in all 3 error
handlers (create, respond, pickup) to recover from broken transactions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds vector-based similar session discovery using the existing Voyage AI
embedding infrastructure and pgvector cosine similarity search.
- New AISessionEmbedding model with vector(1024) column
- session_embedding_service: generate + upsert embeddings, find similar sessions
- Embeddings generated on session create (from problem_summary/domain) and
updated on resolve (adds resolution_summary)
- GET /ai-sessions/{id}/similar endpoint returns top-N similar sessions
- Migration a7c9e3b1f402 creates ai_session_embeddings table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- In `start_session`: increment `flow.usage_count` and set `flow.last_matched_at`
when a flow is matched to a new session; errors are caught without blocking
- In `resolve_session`: recalculate `flow.success_rate` as (resolved / total)
across all sessions ever matched to the flow after each resolution; errors
are caught without blocking the session close
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Log `note_posted` and `time_entry_posted` activities to `psa_activity_logs`
after each successful PSA push in `psa_documentation_service.py`; errors
are caught and logged without blocking the main push flow
- Add `PsaFunnel`, `PsaDailyTrend`, and `EnhancedPsaMetrics` Pydantic schemas
- Add `GET /analytics/flowpilot/psa-metrics?period=30d` endpoint (team_admin,
rate-limited 15/min) returning time entry totals, push funnel
(sessions → linked → doc pushed → time entry logged), and daily trend
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add sso_enabled, sso_provider, sso_config (JSONB) columns to Account model
(migration included in branding commit 58e3f27f3e8f)
- Create sso_service.py stub with initiate_sso_login, process_sso_callback,
validate_sso_config — all raise NotImplementedError (Phase 5)
- Add GET /accounts/me/sso endpoint returning enabled status and provider
- Add SSO section in AccountSettingsPage with Enterprise badge and Contact Us link
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "Generated with ResolutionFlow — https://resolutionflow.com" footer
to markdown, text, HTML, PSA formats (both troubleshooting and procedural
variants — 8 generators total)
- Fix PDF @page CSS: "Powered by ResolutionFlow" now appears on every PDF,
not just for users with a custom logo (removed the has_custom_logo conditional)
- Add Loader2 spinner icon to PDF download button in ExportPreviewModal
when pdfLoading is true, replacing the static Download icon
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements Phase 1 of the FlowPilot-First pivot — the core AI session
experience where engineers describe a problem and FlowPilot guides them
through structured diagnosis with selectable options, free-text escape
hatches, and auto-generated documentation on resolution.
Backend: AISession + AISessionStep models, FlowPilot Engine (LLM
orchestration with structured JSON output), Flow Matching Engine v1
(semantic + keyword + recency scoring), 8 API endpoints with auth,
rate limiting, and AI quota enforcement.
Frontend: Intake screen, conversational session view with sidebar,
step cards with options/actions/resolution suggestions, resolve/escalate
modals, documentation view with rating, session history integration,
and /pilot route with sidebar navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Query supporting data in the export endpoint and pass to markdown, text,
HTML, and PSA export generators. Each format renders text snippets and
screenshot placeholders in its native style.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>