# 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

1. **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.

2. **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.

3. **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_text` flag)
- Clicking the link reveals a `RichTextInput` textarea 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_input` response 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-border` styling, 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

1. User clicks "Script Builder" in the sidebar nav
2. Selects a language (PowerShell, Bash, Python) via pill-style selector at the top
3. 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"*
4. AI responds in the conversation with an explanation and a collapsed code preview (first few lines + line count)
5. Inline action buttons on each code block: **"View Full Script"**, **"Copy"**, **"Save to Library"**
6. **"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
7. User can type follow-up messages to refine: *"Add CSV export"*, *"Handle multiple domains"* — AI responds with an updated script
8. **"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`, or `Terminal`)
- **Mobile nav:** Also add to `mobileNavItems` array in `AppLayout.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 existing `ParameterCandidate` analysis 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 with `try/catch/finally`, `Write-Verbose`/`Write-Error`, pipeline support, `.SYNOPSIS`/`.DESCRIPTION` comment-based help
- **Bash:** Shebang lines, `set -euo pipefail`, argument parsing with `getopts` or 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`

```typescript
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):

1. FlowPilot responds with a step: *"It sounds like you need a custom script for this. I can help you build one."*
2. The step includes an action button: **"Open Script Builder"**
3. Clicking stores context in `sessionStorage` (`{ from_session, prompt, language }`) and opens `/script-builder?from=flowpilot` in a **new browser tab**. The Script Builder reads and clears `sessionStorage` on mount. This avoids URL length limits for long prompts.
4. The `ScriptBuilderSession` stores the `ai_session_id` for traceability
5. 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:

- **`language` column** (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_id` column is `NOT NULL` with a `RESTRICT` FK. 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" filter
- `team_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 by `created_by`)
- **Team Scripts tab:** `GET /scripts/templates?shared=true` (existing behavior, filters by `team_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:

1. Creates a `ScriptTemplate` with the generated script as `script_body` (pulled from session's `latest_script`)
2. Sets `language` from the builder session's language
3. Auto-detects parameters from the script to populate `parameters_schema`
4. Sets `created_by` to current user
5. Sets `category_id` to selected category or default "AI Generated" category
6. If "Share with team" is on, sets `team_id` to user's team

---

## Implementation Phases

### Phase 1: Always-Visible Message Bar

- Remove free text escape hatch from `FlowPilotStepCard`
- Create `FlowPilotMessageBar` component
- Add to `FlowPilotSession` layout
- No backend changes

### Phase 2: Script Builder Core

- New `ScriptBuilderSession` model + migration (includes `language` column on `ScriptTemplate` + "AI Generated" seed category)
- `script_builder_service.py` with language-specific prompts
- API endpoints for sessions and messages
- Frontend: `ScriptBuilderPage`, chat components, code block with syntax highlighting via `react-syntax-highlighter`
- Sidebar nav entry (desktop + mobile)
- Route setup

### Phase 3: Script Preview Modal & Save Flow

- `ScriptPreviewModal` fullscreen overlay
- `SaveToLibraryDialog` with auto-parameter detection
- Backend save endpoint (creates `ScriptTemplate` via `SaveToLibraryRequest` schema)

### Phase 4: Library Reorganization

- My Scripts / Team Scripts tabs
- "Build a New Script" button routing to Script Builder
- Backend `mine` filter 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_id` linking

---

## 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 when `DEBUG=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_script` field 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_at` descending
- 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)
