18 KiB
FlowPilot Message Bar & AI Script Builder
Date: 2026-03-21 Status: Approved Scope: Three interconnected features — FlowPilot always-visible message bar, standalone AI Script Builder, and Script Library reorganization
Problem
-
FlowPilot sessions lack a visible text input. When FlowPilot asks the user to provide detailed information (e.g., AD user details for script building), the only way to type a response is a tiny "None of these — let me describe" link that expands into a textarea. Users don't notice it and feel stuck.
-
No AI-powered script generation from natural language. The existing script system is template-based — users pick a pre-built template and fill in parameters. There's no way to say "build me a script that does X" and get a custom script generated.
-
Script Library lacks personal/team separation. Scripts are either personal or team-shared via a toggle, but the UI doesn't clearly separate "My Scripts" from "Team Scripts." There's also no prominent entry point to create new scripts.
Feature 1: Always-Visible Message Bar (FlowPilot Sessions)
What Changes
Replace the hidden "None of these — let me describe" escape hatch with a persistent chat-style input bar pinned to the bottom of the FlowPilot session view.
Current Flow
- Option buttons render inside the step card
- A small "None of these — let me describe" text link appears below options (controlled by
allow_free_textflag) - Clicking the link reveals a
RichTextInputtextarea with Submit/Cancel buttons - If the user doesn't notice the link, they have no way to respond with free text
New Flow
- Option buttons still render inside the step card (unchanged)
- A persistent message input bar is pinned to the bottom of the session view, outside step cards
- Always visible, always ready — styled like Claude Desktop's input bar
- Typing and submitting sends a
free_text_inputresponse to the current step (same backend flow, no backend changes needed) - The "None of these — let me describe" link is removed entirely from
FlowPilotStepCard - The message bar is disabled when: FlowPilot is processing, the session is completed/resolved/escalated, or no current step exists
Component Changes
| File | Change |
|---|---|
FlowPilotStepCard.tsx |
Remove the free text escape hatch block (the {!isResolutionSuggestion && step.allow_free_text && ...} conditional and its children) |
FlowPilotSession.tsx |
Add FlowPilotMessageBar component, pinned to bottom of session area, above the Resolve/Escalate action bar |
New: FlowPilotMessageBar.tsx |
Persistent input bar using existing RichTextInput. Rounded input with placeholder "Type a message...", send button. Calls onRespond({ free_text_input }) |
Visual Design
- Rounded input field with
bg-card border-borderstyling, consistent with the app's glass design system - Send button with
bg-gradient-brand(cyan gradient) - Placeholder text: "Type a message..."
- Pinned to bottom of session content area, above Resolve/Escalate bar
- Disabled state (dimmed, no interaction) when processing or session is closed
Step Targeting
The message bar always submits to the current step at the moment of submission (same behavior as the existing free text mechanism). If the step changes between when the user starts typing and when they click Send, the submission targets the new current step. This is acceptable because FlowPilot won't advance steps without a user response.
Respecting allow_free_text: false
Certain step types explicitly set allow_free_text: false — specifically resolution suggestions and escalation briefings, where the AI expects a structured response (accept/reject). The message bar must respect this flag: when allow_free_text is false on the current step, the message bar is disabled (visually dimmed, input blocked) rather than hidden. This preserves visibility while preventing engineers from bypassing structured response constraints.
Backend Changes
None. The free_text_input field in StepResponseRequest already handles this. The allow_free_text flag on steps continues to control whether the message bar is enabled/disabled for a given step.
Feature 2: AI Script Builder (Standalone)
Overview
A new standalone page accessible from the sidebar that lets users describe what they need in natural language, and an AI generates a custom script. Users can iterate on the script through multi-turn conversation, then save it to the Script Library.
User Experience
- User clicks "Script Builder" in the sidebar nav
- Selects a language (PowerShell, Bash, Python) via pill-style selector at the top
- Types a description of what they need in a chat-style input: "I need a script that pulls all GPOs linked to a domain, showing name, link location, and enforcement status"
- AI responds in the conversation with an explanation and a collapsed code preview (first few lines + line count)
- Inline action buttons on each code block: "View Full Script", "Copy", "Save to Library"
- "View Full Script" opens a fullscreen modal overlay with the complete syntax-highlighted script, Copy/Save buttons in the header, line count and metadata in the footer, and a "Close & Return to Chat" button
- User can type follow-up messages to refine: "Add CSV export", "Handle multiple domains" — AI responds with an updated script
- "Save to Library" opens a dialog for: name, description (optional), category, and whether to share with team
Sidebar Navigation
- Label: "Script Builder"
- Section: "Knowledge" group (alongside Flows, Step Library, Scripts)
- Icon: TBD (a Lucide icon that conveys "build/generate" — e.g.,
Wand2,Sparkles, orTerminal) - Mobile nav: Also add to
mobileNavItemsarray inAppLayout.tsx
Frontend Architecture
| Component | Purpose |
|---|---|
ScriptBuilderPage.tsx |
New page at /script-builder. Chat-style layout with language selector |
ScriptBuilderChat.tsx |
Chat message list — user messages (right-aligned, cyan tint) and AI responses (left-aligned, glass card) |
ScriptBuilderInput.tsx |
Chat input bar at bottom with send button |
ScriptCodeBlock.tsx |
Collapsed code preview in AI messages with View/Copy/Save buttons |
ScriptPreviewModal.tsx |
Fullscreen modal overlay for viewing complete script with syntax highlighting |
SaveToLibraryDialog.tsx |
Dialog for naming and categorizing a script before saving |
Route in router.tsx |
/script-builder inside ProtectedRoute/AppLayout children |
Backend Architecture
New Service: script_builder_service.py
Location: backend/app/services/script_builder_service.py
Responsibilities:
- Manages AI conversation for script generation
- Language-specific system prompts with syntax knowledge, best practices, error handling patterns, security considerations
- Multi-turn conversation support — maintains message history per session
- Extracts generated script from AI response (parses code fences)
- On save: auto-detects parameters from the generated script to populate
parameters_schema(reuses existingParameterCandidateanalysis pattern)
Uses the existing AnthropicProvider with standard model tier (Sonnet) via settings.get_model_for_action().
Syntax Highlighting
Use react-syntax-highlighter with the oneDark theme (already a common choice in React ecosystems, lightweight). Supports PowerShell, Bash, and Python out of the box. Used for both the inline collapsed code preview (first 5 lines shown) and the fullscreen modal.
Inline Code Preview
The collapsed code block in AI chat messages shows:
- Filename (auto-generated by AI, e.g.,
Get-LinkedGPOs.ps1) - First 5 lines of the script with syntax highlighting
- Total line count (e.g., "42 lines")
- Clicking anywhere on the code block opens the fullscreen modal (same as "View Full Script" button)
New Model: ScriptBuilderSession
Location: backend/app/models/script_builder_session.py
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
user_id |
UUID FK | Creator |
team_id |
UUID FK (nullable) | Team context |
language |
String(30) | Selected language (powershell, bash, python) |
title |
String(200, nullable) | Auto-generated from first message |
messages |
JSONB | Array of {role, content, timestamp} |
latest_script |
Text (nullable) | Most recent generated script (for quick access) |
ai_session_id |
UUID FK → ai_sessions.id (nullable) |
Link to FlowPilot session if launched from there |
created_at |
DateTime(tz) | |
updated_at |
DateTime(tz) |
New Endpoints: /scripts/builder/
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/scripts/builder/sessions |
Start a new builder session. Body: { language }. Returns session with ID |
POST |
/scripts/builder/sessions/{id}/messages |
Send user message, get AI response with generated script. Body: { content }. Returns { message, script, script_filename, line_count } |
GET |
/scripts/builder/sessions |
List user's recent builder sessions (paginated). Returns lightweight schema without messages array — only id, title, language, created_at, updated_at |
GET |
/scripts/builder/sessions/{id} |
Get full session with message history (for resuming). Returns complete messages JSONB |
POST |
/scripts/builder/sessions/{id}/save |
Save latest script to library. Body: SaveToLibraryRequest { name, description?, category_id?, share_with_team? }. Service layer pulls script_body from session's latest_script and combines with user-provided metadata to create ScriptTemplate. Uses a dedicated SaveToLibraryRequest schema (not ScriptTemplateCreate) |
DELETE |
/scripts/builder/sessions/{id} |
Delete a builder session. Owner only. |
Language-Specific System Prompts
Each language gets a tailored system prompt injected into the AI conversation:
- PowerShell: Advanced function patterns,
param()blocks,CmdletBinding, module imports, error handling withtry/catch/finally,Write-Verbose/Write-Error, pipeline support,.SYNOPSIS/.DESCRIPTIONcomment-based help - Bash: Shebang lines,
set -euo pipefail, argument parsing withgetoptsor positional params, error handling, shellcheck-clean output, POSIX compatibility notes - Python: Type hints, argparse for CLI scripts,
if __name__ == "__main__"guard, logging module, docstrings, virtual environment considerations
Frontend API Client
New module: frontend/src/api/scriptBuilder.ts
export const scriptBuilderApi = {
createSession(language: string): Promise<ScriptBuilderSession>,
sendMessage(sessionId: string, content: string): Promise<ScriptBuilderMessage>,
listSessions(params?: PaginationParams): Promise<PaginatedResponse<ScriptBuilderSession>>,
getSession(sessionId: string): Promise<ScriptBuilderSessionDetail>,
saveToLibrary(sessionId: string, data: SaveToLibraryRequest): Promise<ScriptTemplateDetail>,
}
FlowPilot Integration
When FlowPilot detects the user needs a custom script (no matching template):
- FlowPilot responds with a step: "It sounds like you need a custom script for this. I can help you build one."
- The step includes an action button: "Open Script Builder"
- Clicking stores context in
sessionStorage({ from_session, prompt, language }) and opens/script-builder?from=flowpilotin a new browser tab. The Script Builder reads and clearssessionStorageon mount. This avoids URL length limits for long prompts. - The
ScriptBuilderSessionstores theai_session_idfor traceability - The FlowPilot session continues independently
Existing template-based flow (InSessionScriptGenerator) remains unchanged for when FlowPilot matches an existing template.
Feature 3: Script Library Reorganization
Tab Structure
Two tabs at the top of the Script Library page (/scripts):
- My Scripts — scripts where
created_by == current_user. Includes both AI-generated saves and manually created templates. - Team Scripts — scripts where the template is shared to the team (
team_id == current_team, shared by any team member).
"Build a New Script" Button
A prominent action button at the top of the library page (next to the tab bar or in the header area). Styled with bg-gradient-brand (primary CTA). Clicking routes to /script-builder.
Data Model Changes
The existing ScriptTemplate model needs two additions:
languagecolumn (String(30), nullable, default'powershell'): Stores the script language. Without this, saved scripts lose their language metadata and can't be filtered by language in the library. Added via migration.- Default "AI Generated" category: The existing
category_idcolumn isNOT NULLwith aRESTRICTFK. Rather than making it nullable (which would break existing queries), create a default "AI Generated" category via migration seed data. The save dialog auto-selects this category when no other is chosen.
Existing fields used for library filtering:
created_by(user ID) — used for "My Scripts" filterteam_id(team ID, nullable) — used for "Team Scripts" filter- Team sharing toggle already exists
The frontend filtering changes:
- My Scripts tab:
GET /scripts/templates?mine=true(new query param, filters bycreated_by) - Team Scripts tab:
GET /scripts/templates?shared=true(existing behavior, filters byteam_id)
Library Backend Changes
Add mine query parameter to GET /scripts/templates endpoint. When mine=true, filter by created_by == current_user.id regardless of team sharing status.
Save to Library Dialog
When triggered from the Script Builder's "Save to Library" button:
| Field | Type | Required | Notes |
|---|---|---|---|
| Name | Text input | Yes | Auto-suggested from AI-generated filename |
| Description | Textarea | No | Auto-suggested from AI's explanation |
| Category | Select dropdown | No | Defaults to "AI Generated" category if not selected |
| Share with team | Toggle | No | Default off. Shares to user's team |
On save, the backend:
- Creates a
ScriptTemplatewith the generated script asscript_body(pulled from session'slatest_script) - Sets
languagefrom the builder session's language - Auto-detects parameters from the script to populate
parameters_schema - Sets
created_byto current user - Sets
category_idto selected category or default "AI Generated" category - If "Share with team" is on, sets
team_idto user's team
Implementation Phases
Phase 1: Always-Visible Message Bar
- Remove free text escape hatch from
FlowPilotStepCard - Create
FlowPilotMessageBarcomponent - Add to
FlowPilotSessionlayout - No backend changes
Phase 2: Script Builder Core
- New
ScriptBuilderSessionmodel + migration (includeslanguagecolumn onScriptTemplate+ "AI Generated" seed category) script_builder_service.pywith language-specific prompts- API endpoints for sessions and messages
- Frontend:
ScriptBuilderPage, chat components, code block with syntax highlighting viareact-syntax-highlighter - Sidebar nav entry (desktop + mobile)
- Route setup
Phase 3: Script Preview Modal & Save Flow
ScriptPreviewModalfullscreen overlaySaveToLibraryDialogwith auto-parameter detection- Backend save endpoint (creates
ScriptTemplateviaSaveToLibraryRequestschema)
Phase 4: Library Reorganization
- My Scripts / Team Scripts tabs
- "Build a New Script" button routing to Script Builder
- Backend
minefilter on templates endpoint
Phase 5: FlowPilot Integration
- FlowPilot prompt updates to detect custom script needs
- "Open Script Builder" action button in step cards
- Context pre-fill via
sessionStorage ai_session_idlinking
Operational Constraints
Rate Limiting & Token Budget
- Max messages per session: 30 (same safety limit as FlowPilot sessions)
- Max concurrent builder sessions per user: 5 active sessions
- Rate limit: 10 messages per minute per user (uses existing
@limiter.limit()decorator, disabled whenDEBUG=True) - Context window management: When conversation history exceeds ~80% of the model's context window, the service truncates older messages (keeping the system prompt and last 10 exchanges). The
latest_scriptfield ensures the most recent script is always accessible even if early messages are truncated.
Session Lifecycle
- Builder sessions have no explicit expiration — they persist until the user deletes them
- The list endpoint returns sessions ordered by
updated_atdescending - A "New Conversation" button on the Script Builder page starts a fresh session
- Recent sessions are accessible via a collapsible sidebar/drawer on the Script Builder page (not a separate page)
- Session title is auto-generated after the first AI response (AI summarizes the intent)
Error Handling
AI Failures
| Scenario | Handling |
|---|---|
| AI response has no code block | Display the AI's text response normally. Show a subtle hint: "No script was generated. Try being more specific about what you need." |
| AI request times out (120s) | Show error toast: "Script generation timed out. Please try again." Keep conversation intact so user can retry. |
| AI returns malformed/unparseable response | Log the raw response for debugging. Show generic error: "Something went wrong generating your script. Please try again." |
| Context window exceeded | Auto-truncate older messages and retry once. If still fails, prompt user to start a new session. |
AnthropicProvider rate limited (429) |
Show error with retry-after hint: "AI service is busy. Please wait a moment and try again." |
Save Failures
| Scenario | Handling |
|---|---|
No latest_script on session |
Disable "Save to Library" button. Show tooltip: "Generate a script first." |
category_id constraint violation |
Should not occur (default "AI Generated" category). If it does, return 400 with clear message. |
| Duplicate template name | Return 409 conflict. Dialog shows inline error: "A script with this name already exists." |
Out of Scope
- Script execution/testing within ResolutionFlow (scripts are generated and copied, not run)
- Script version history (save creates a new template, no versioning on iterations)
- Marketplace/community script sharing beyond team level
- Languages beyond PowerShell, Bash, Python (extensible later)
- Script Builder conversation branching (linear conversation only)