* feat: flexible intake — deferred variables + prepared sessions
Remove blocking intake form modal. Variables are now filled inline during
flow execution or pre-filled via prepared sessions. Adds PATCH /sessions/{id}/variables
endpoint, POST /sessions/prepare for session pre-staging, inline variable prompts
in StepDetail, editable Session Variables panel, and "Prepared for You" dashboard section.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass treeData directly to startSession to avoid stale state
setTree(treeData) hasn't committed when startSession runs immediately
after, so tree is still null and getStepsFromTree returns []. This
caused the step detail area to render empty on new session start.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: wire PrepareSessionModal entry point in Flow Library
Add "Prepare session" button (clipboard icon) to grid, list, and table
views for procedural/maintenance flows. Clicking fetches tree intake
fields and account members, then opens PrepareSessionModal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
4.9 KiB
Python
143 lines
4.9 KiB
Python
from datetime import datetime
|
|
from typing import Optional, Any, Literal
|
|
from uuid import UUID
|
|
from pydantic import BaseModel, Field, validator
|
|
|
|
SessionOutcome = Literal["resolved", "escalated", "workaround", "unresolved"]
|
|
|
|
|
|
class CustomStepSchema(BaseModel):
|
|
"""Enhanced custom step with source tracking.
|
|
|
|
Backward compatible: old sessions without new fields load with defaults.
|
|
"""
|
|
type: str # "decision" | "action" | "solution"
|
|
content: str
|
|
notes: Optional[str] = None
|
|
|
|
# Source tracking (new fields, optional for backward compatibility)
|
|
source: Literal["ad-hoc", "step-library", "forked-tree"] = "ad-hoc"
|
|
source_step_id: Optional[UUID] = None
|
|
inserted_at: Optional[datetime] = None
|
|
inserted_after_node_id: Optional[str] = None
|
|
|
|
|
|
class DecisionRecord(BaseModel):
|
|
node_id: str
|
|
question: Optional[str] = None
|
|
answer: Optional[str] = None
|
|
action_performed: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
command_output: Optional[str] = Field(None, max_length=10000)
|
|
automation_used: Optional[bool] = False
|
|
timestamp: datetime
|
|
entered_at: Optional[datetime] = None
|
|
exited_at: Optional[datetime] = None
|
|
duration_seconds: Optional[int] = Field(None, ge=0)
|
|
attachments: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class SessionCreate(BaseModel):
|
|
tree_id: UUID
|
|
ticket_number: Optional[str] = Field(None, max_length=100)
|
|
client_name: Optional[str] = Field(None, max_length=255)
|
|
session_variables: Optional[dict[str, str]] = Field(None, description="Intake form values for procedural flows")
|
|
|
|
|
|
class SessionUpdate(BaseModel):
|
|
path_taken: Optional[list[str]] = None
|
|
decisions: Optional[list[DecisionRecord]] = None
|
|
custom_steps: Optional[list[CustomStepSchema]] = None
|
|
ticket_number: Optional[str] = Field(None, max_length=100)
|
|
client_name: Optional[str] = Field(None, max_length=255)
|
|
scratchpad: Optional[str] = None
|
|
next_steps: Optional[str] = None
|
|
session_variables: Optional[dict[str, str]] = None
|
|
|
|
|
|
class PrepareSessionRequest(BaseModel):
|
|
"""Create a prepared session with pre-filled variables and optional assignee."""
|
|
tree_id: UUID
|
|
session_variables: Optional[dict[str, str]] = Field(None, description="Pre-filled intake form values")
|
|
assigned_to_id: Optional[UUID] = Field(None, description="User ID of the engineer to assign this session to")
|
|
ticket_number: Optional[str] = Field(None, max_length=100)
|
|
client_name: Optional[str] = Field(None, max_length=255)
|
|
|
|
|
|
class SessionResponse(BaseModel):
|
|
id: UUID
|
|
tree_id: UUID
|
|
user_id: UUID
|
|
tree_snapshot: dict[str, Any]
|
|
path_taken: list[str]
|
|
decisions: list[dict[str, Any]]
|
|
custom_steps: list[dict[str, Any]] = Field(default_factory=list)
|
|
started_at: Optional[datetime] = None
|
|
completed_at: Optional[datetime] = None
|
|
outcome: Optional[SessionOutcome] = None
|
|
outcome_notes: Optional[str] = None
|
|
next_steps: str = ""
|
|
ticket_number: Optional[str] = None
|
|
client_name: Optional[str] = None
|
|
exported: bool
|
|
scratchpad: str = ""
|
|
session_variables: dict[str, str] = Field(default_factory=dict)
|
|
|
|
# Prepared session fields
|
|
prepared_by_id: Optional[UUID] = None
|
|
assigned_to_id: Optional[UUID] = None
|
|
|
|
@validator('scratchpad', 'next_steps', pre=True, always=True)
|
|
def normalize_text_fields(cls, v):
|
|
return v or ""
|
|
|
|
batch_id: Optional[UUID] = None
|
|
target_label: Optional[str] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class SessionExport(BaseModel):
|
|
format: str = Field(default="markdown", pattern="^(text|markdown|html|psa)$")
|
|
include_timestamps: bool = True
|
|
include_tree_info: bool = True
|
|
# Phase A
|
|
include_outcome_notes: bool = True
|
|
include_next_steps: bool = True
|
|
max_step_index: Optional[int] = Field(None, ge=1, description="1-based inclusive step cutoff")
|
|
# Phase B
|
|
include_summary: bool = False
|
|
detail_level: Literal["standard", "full"] = "standard"
|
|
# Phase C
|
|
redaction_mode: Literal["none", "mask"] = "none"
|
|
|
|
|
|
class SessionComplete(BaseModel):
|
|
outcome: SessionOutcome
|
|
outcome_notes: Optional[str] = None
|
|
next_steps: Optional[str] = None
|
|
|
|
|
|
class SessionVariablesUpdate(BaseModel):
|
|
"""Partial update to session variables (dict merge)."""
|
|
variables: dict[str, str] = Field(..., description="Key-value pairs to merge into session_variables")
|
|
|
|
|
|
class ScratchpadUpdate(BaseModel):
|
|
scratchpad: str
|
|
|
|
|
|
class SaveAsTreeRequest(BaseModel):
|
|
"""Request to save a session as a tree."""
|
|
tree_name: Optional[str] = Field(None, max_length=255, description="Custom name for the saved tree (auto-generated if not provided)")
|
|
description: Optional[str] = Field(None, description="Description for the saved tree")
|
|
status: Literal["draft", "published"] = Field("draft", description="Status of the saved tree")
|
|
|
|
|
|
class SaveAsTreeResponse(BaseModel):
|
|
"""Response after saving a session as a tree."""
|
|
tree_id: UUID
|
|
tree_name: str
|
|
message: str
|