# FlowDrop Interrupt Module

Provides human-in-loop functionality for FlowDrop workflows, enabling workflows to pause execution and request user input before continuing.

PHP classes in this module are `@api` or `@internal`. See the [BC Policy](../../docs/reference/api/bc-policy.md) for details.

## Features

- **Persistent Storage**: Database-backed interrupt entity for reliable state management
- **Multiple Input Types**: Confirmation, Choice, Text, and Form interrupts
- **Cross-Orchestrator Support**: Full support across all orchestrators via `InterruptExceptionInterface`
- **API Endpoints**: RESTful API for managing interrupt requests
- **Playground Integration**: Seamless integration with FlowDrop playground

## Architecture

The interrupt system uses a unified exception-based approach that works across all orchestrators:

```
InterruptExceptionInterface (flowdrop_runtime)
├── InterruptRequiredException (flowdrop_interrupt)
│   - Used by: Confirmation, Choice, Text, Form nodes
└── ApprovalRequiredException (flowdrop_stategraph)
    - Used by: ApprovalGate node
```

All pipeline-based orchestrators handle `InterruptExceptionInterface` using a common `InterruptHandlingTrait`:

| Orchestrator | HIL Support | Notes |
|--------------|-------------|-------|
| SynchronousOrchestrator | **Not Supported** | Stateless - cannot resume after interrupt |
| SynchronousPipelineOrchestrator | ✅ Supported | Pipeline-based with job tracking |
| AsynchronousOrchestrator | ✅ Supported | Queue-based, emits `flowdrop.pipeline.interrupted` event |
| StateGraphOrchestrator | ✅ Supported | Checkpointing with approval gate support |

### Stateless Orchestrator Limitation

**The `SynchronousOrchestrator` does NOT support Human-in-the-Loop (HIL) workflows** because:

1. **No persistent state**: The orchestrator executes workflows in-memory without creating pipeline or job entities
2. **No pause/resume capability**: Execution context is lost when an interrupt occurs
3. **No job tracking**: Without job entities, there's no way to mark a job as "interrupted" and reset it on resume

If your workflow contains HIL nodes (ChoiceNode, ConfirmationNode, TextInputNode, FormInputNode), you **must use a pipeline-based orchestrator**:

- **SynchronousPipelineOrchestrator**: Best for interactive applications requiring immediate feedback
- **AsynchronousOrchestrator**: Best for long-running workflows that can be resumed later
- **StateGraphOrchestrator**: Best for complex workflows with conditional routing and checkpointing

**Future Enhancement**: Automatic orchestrator switching when HIL is encountered is planned but not yet implemented.

### Job Status Lifecycle for HIL

When an interrupt occurs, the job goes through the following status transitions:

```
┌─────────┐    ┌─────────┐    ┌─────────────┐
│  idle   │───>│ running │───>│ interrupted │
└─────────┘    └─────────┘    └──────┬──────┘
                                     │
                        User responds│
                                     ▼
                              ┌─────────┐    ┌───────────┐
                              │ pending │───>│ completed │
                              └─────────┘    └───────────┘
```

1. **Job starts execution** (`running`)
2. **HIL node throws `InterruptRequiredException`** → Job marked as `interrupted`
3. **Pipeline marked as `paused`**
4. **User submits response** → `flowdrop_interrupt.resolved` event dispatched
5. **InterruptResolvedSubscriber resets job** → Job marked as `pending`
6. **Pipeline resumes** → Job re-executes, finds resolved interrupt, completes normally

The "interrupted" status is distinct from "failed" to allow proper resume behavior:
- `failed` jobs are not re-executed (terminal state)
- `interrupted` jobs are reset to `pending` on resume and re-executed

### Capability Detection

Check if an orchestrator supports interrupts:

```php
$capabilities = $orchestrator->getCapabilities();
if ($capabilities["interrupt_support"] ?? FALSE) {
  // Orchestrator supports human-in-the-loop interrupts.
}
```

## Interrupt Types

### Confirmation
A simple Yes/No prompt for user decisions.

```php
#[FlowDropNodeProcessor(
  id: "confirmation",
  label: new TranslatableMarkup("Confirmation"),
  description: "Pauses workflow for user confirmation (Yes/No)",
)]
```

**Parameters:**
- `message`: The confirmation message to display
- `confirm_label`: Label for confirm button (default: "Yes")
- `cancel_label`: Label for cancel button (default: "No")

**Outputs:**
- `confirmed`: Boolean indicating user's choice
- `response_time`: Timestamp of the response
- `user_id`: ID of the responding user

### Choice Input
Allows users to select from a list of options.

```php
#[FlowDropNodeProcessor(
  id: "choice_input",
  label: new TranslatableMarkup("Choice Input"),
  description: "Pauses workflow for user to select from options",
)]
```

**Parameters:**
- `message`: The prompt message
- `options`: Array of options with `value`, `label`, and optional `description`
- `multiple`: Whether to allow multiple selections
- `min_selections`: Minimum selections required
- `max_selections`: Maximum selections allowed

**Outputs:**
- `selected`: The selected value(s)
- `selection_count`: Number of options selected
- `response_time`: Timestamp of the response
- `user_id`: ID of the responding user

### Text Input (Interrupt)
Collects free-form text input from users.

```php
#[FlowDropNodeProcessor(
  id: "text_input_interrupt",
  label: new TranslatableMarkup("Text Input (Interrupt)"),
  description: "Pauses workflow for user to enter text",
)]
```

**Parameters:**
- `message`: The prompt message
- `placeholder`: Placeholder text for the input
- `multiline`: Whether to show a multiline text area
- `min_length`: Minimum text length
- `max_length`: Maximum text length
- `default_value`: Default text to pre-fill

**Outputs:**
- `text`: The entered text
- `length`: Character count
- `response_time`: Timestamp of the response
- `user_id`: ID of the responding user

### Form Input
Presents a JSON Schema-based form to users.

```php
#[FlowDropNodeProcessor(
  id: "form_input",
  label: new TranslatableMarkup("Form Input"),
  description: "Pauses workflow for user to fill a JSON Schema form",
)]
```

**Parameters:**
- `message`: The prompt message
- `schema`: JSON Schema defining the form fields
- `default_values`: Default values for form fields

**Outputs:**
- `data`: The form data as an object
- `response_time`: Timestamp of the response
- `user_id`: ID of the responding user

## API Endpoints

### List Session Interrupts
```
GET /api/flowdrop/playground/sessions/{sessionId}/interrupts
```
Returns pending interrupts for a playground session.

### Get Interrupt
```
GET /api/flowdrop/interrupts/{interruptId}
```
Returns details of a specific interrupt.

### Resolve Interrupt
```
POST /api/flowdrop/interrupts/{interruptId}
Content-Type: application/json

{
  "value": <response_data>
}
```
Resolves an interrupt with user input.

### Cancel Interrupt
```
POST /api/flowdrop/interrupts/{interruptId}/cancel
```
Cancels a pending interrupt.

### List Pipeline Interrupts
```
GET /api/flowdrop/pipelines/{pipelineId}/interrupts
```
Returns pending interrupts for a pipeline.

## Permissions

- `administer flowdrop interrupts`: Full administrative access
- `view flowdrop interrupts`: View interrupt requests
- `resolve flowdrop interrupts`: Respond to interrupt requests
- `cancel flowdrop interrupts`: Cancel pending interrupts

## FlowDrop JS Library Integration

The interrupt UI rendering is handled by the FlowDrop JS library (`@flowdrop/flowdrop`). The following components need to be implemented in the JS library:

### Required Components

1. **InterruptOverlay**: Modal overlay for displaying interrupt prompts
2. **ConfirmationPrompt**: Yes/No confirmation UI
3. **ChoicePrompt**: Option selection UI (single/multiple)
4. **TextInputPrompt**: Text input UI
5. **FormPrompt**: JSON Schema form renderer

### Endpoint Configuration

The playground includes interrupt endpoints in its configuration:

```javascript
endpoints: {
  interrupts: {
    list: "/playground/sessions/{sessionId}/interrupts",
    get: "/interrupts/{interruptId}",
    resolve: "/interrupts/{interruptId}",
    cancel: "/interrupts/{interruptId}/cancel"
  }
}
```

### Message Metadata

When an interrupt is triggered, a system message is created with the following metadata:

```json
{
  "type": "interrupt_request",
  "interrupt_id": "uuid-here",
  "interrupt_type": "confirmation|choice|text|form",
  "schema": {...},
  "options": [...],
  "default_value": null,
  "node_id": "node_id_here",
  "execution_id": "exec_id_here"
}
```

The JS library should detect messages with `type: "interrupt_request"` and display the appropriate UI component.

## Installation

```bash
ddev drush en flowdrop_interrupt -y
ddev drush cr
```

## Dependencies

- flowdrop
- flowdrop_runtime
- flowdrop_playground
- drupal:user

