fix(flowpilot): widen message bar, add close/abandon session support

- 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>
This commit is contained in:
Michael Chihlas
2026-03-21 18:20:34 -04:00
parent 74bc5a532d
commit 6ecb5a9bbd
8 changed files with 170 additions and 43 deletions

View File

@@ -396,6 +396,34 @@ async def resume_session(
await db.commit()
# ── Abandon / Close ──
@router.post("/{session_id}/abandon", status_code=204)
@limiter.limit("15/minute")
async def abandon_session(
request: Request,
session_id: UUID,
current_user: Annotated[User, Depends(get_current_active_user)],
db: Annotated[AsyncSession, Depends(get_db)],
_: None = Depends(require_engineer_or_admin),
reason: str | None = None,
):
"""Close a session without resolving or escalating."""
try:
await flowpilot_engine.abandon_session(
session_id=session_id,
user_id=current_user.id,
reason=reason,
db=db,
)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
except PermissionError as e:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e))
await db.commit()
# ── Escalation Queue ──
@router.get("/escalation-queue", response_model=list[AISessionSummary])

View File

@@ -793,6 +793,29 @@ async def resume_session(
await db.flush()
async def abandon_session(
session_id: UUID,
user_id: UUID,
reason: Optional[str],
db: AsyncSession,
) -> None:
"""Close a session without resolving or escalating.
Used when the engineer no longer needs help, figured it out on their own,
or the session is no longer relevant.
"""
session = await _load_session(session_id, user_id, db)
if session.status not in ("active", "paused"):
raise ValueError(f"Cannot close session in status: {session.status}")
session.status = "abandoned"
session.resolved_at = datetime.now(timezone.utc)
if reason:
session.resolution_notes = reason
await db.flush()
async def rate_session(
session_id: UUID,
rating: int,