After migration 174f442795b7 enforces NOT NULL on account_id, all
platform/global content must use the sentinel platform account instead
of NULL. Three categories of fixes:
1. trees.py: is_default trees now get PLATFORM_ACCOUNT_ID (not None)
2. admin_categories.py: global category CRUD now uses PLATFORM_ACCOUNT_ID
3. categories.py, tags.py, step_categories.py: creation endpoints coerce
None → PLATFORM_ACCOUNT_ID; IS NULL filter queries updated to
== PLATFORM_ACCOUNT_ID (IS NULL queries returned empty after migration
backfilled all global rows to the platform account)
Defines PLATFORM_ACCOUNT_ID constant in app/core/service_account.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All previously-nullable account_id columns are now NOT NULL.
tree_embeddings and feedback backfilled before constraint applied.
Global content assigned to platform sentinel account (00000000-...-0001)
in preceding migration.
Tables updated: users, trees, tree_categories, tree_tags,
step_categories, step_library, tree_embeddings, feedback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates template_trees and platform_steps (no account_id, no RLS).
Migrates is_default=TRUE trees and public steps into them.
Creates sentinel platform account (00000000-...-0001) for global
tree_categories, tree_tags, step_categories, step_library, and
is_default trees — clearing all NULL account_id rows in those tables
as prerequisite for Group 9 SET NOT NULL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zero rows in production — this is a schema-only migration in practice.
team_id kept for app code compatibility. Drop deferred to later cleanup.
Backfill: team_id → team admin user → account_id; fallback: created_by.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
team_id is kept in all three tables — drop deferred until app code
is fully migrated off team_id references.
Tables: script_builder_sessions, script_templates, script_generations
Backfill: user_id/created_by → users.account_id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
psa_post_log: backfill via psa_connection, fallback to posted_by user
psa_member_mappings: backfill via psa_connection
notification_logs: backfill via notification_config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backfill from rater/user's account_id (not the step's account_id).
This is an explicit design decision — step rating data is attributed
to the account that performed the rating.
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>
_build_session_detail was omitting pending_task_lane, is_branching, and
active_branch_id from the GET /ai-sessions/{id} response. The fields
existed on the schema and model but were never passed in the manual
constructor, so task lane state could never be restored on navigation.
Also adds console logging to AssistantChatPage selectChat flow to
diagnose message restoration, and fixes ScriptTemplateEditor stepper
dismiss firing during programmatic script_body updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PUT /ai-sessions/{id}/task-lane endpoint that saves the full task
lane state (AI questions/actions + user's in-progress responses) to
the pending_task_lane JSONB column. TaskLane debounce-saves to the
backend every 2s after changes. On session load, user responses are
restored from the backend into sessionStorage so TaskLane picks them
up on mount. Users can now close the browser, come back later, and
find their task lane exactly where they left it.
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>
When user.team_id is None, `WHERE team_id == None` matches all
personal templates. Return empty set instead when user has no team.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- script_template.py: add server_default to ALL NOT NULL columns so
Base.metadata.create_all matches Alembic behavior for raw SQL INSERTs
- test_session_branches_api.py: fork_reason needs 5+ chars ("test" → "testing fork")
- test_scripts.py: engineers CAN create templates (assert 201, not 403)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- script_template.py: add server_default for requires_elevation,
is_gallery_featured, gallery_sort_order so Base.metadata.create_all
emits proper SQL DEFAULTs (test fixtures use raw SQL INSERT)
- session_branches.py: refresh fork_point after commit so JSONB options
field is loaded before Pydantic serialization
- test_session_branches_api.py: add status assertion on fork response
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SQLAlchemy SAWarning about overlapping relationships was promoted to
an error by pytest filterwarnings=error, crashing mapper initialization
and causing 500s on every request — cascading to 423+ test failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add back_populates to SessionBranch.session ↔ AISession.branches
to fix SQLAlchemy InvalidRequestError that broke all backend tests
- Remove unused 'tree' variable in command-palette.spec.ts
- Suppress setState-in-effect lint rule in TaskLane (intentional sync)
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>
SQLAlchemy can't resolve ambiguous FK paths when tables have
multiple cross-references. All relationships now specify foreign_keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrates ResolutionOutputGenerator into the resolve endpoint so that
structured outputs (ticket note, KB article, etc.) are auto-generated
after every successful session resolution. Non-blocking — resolve still
succeeds if output generation fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds _generate_ai_description background task that fires after a
successful upload: images get a one-sentence vision description via
Claude, text/log/config files get extracted_content + AI summary when
>2000 chars. Runs as asyncio.create_task so it never blocks the upload
response. Errors are logged and swallowed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds GET /outputs, PATCH /outputs/{id}, and POST /outputs/{id}/push
endpoints under /ai-sessions/{session_id}/, plus integration tests.
Router registered before ai_sessions to avoid path conflict.
Co-Authored-By: Claude Opus 4.6 (1M context) <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>
Four endpoints: create handoff (park/escalate), list handoff history,
claim session, and team queue. Two routers: session-scoped router with
prefix /ai-sessions/{session_id} and queue_router with prefix /ai-sessions.
queue_router registered before ai_sessions.router to avoid /{session_id}
path conflict on GET /ai-sessions/queue.
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>