Changeset 0.35.0 (#209)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
@@ -115,6 +115,7 @@ how data is collected.
|
||||
| `chat_only` | Default. Persona drives the conversation. |
|
||||
| `form_only` | UI-rendered form, no LLM. Visitor fills out fields. |
|
||||
| `form_chat` | Both form and chat available. Persona assists alongside the form. |
|
||||
| `review` | Human review queue. Shows structured stage_data with approve/reject. |
|
||||
|
||||
### List Stages
|
||||
|
||||
@@ -138,11 +139,12 @@ POST /workflows/:id/stages
|
||||
"ordinal": 0,
|
||||
"persona_id": "uuid|null",
|
||||
"assignment_team_id": "uuid|null",
|
||||
"stage_mode": "chat_only|form_only|form_chat",
|
||||
"stage_mode": "chat_only|form_only|form_chat|review",
|
||||
"form_template": { "fields": [...] },
|
||||
"history_mode": "full|summary|fresh",
|
||||
"auto_transition": false,
|
||||
"transition_rules": { "auto_assign": "round_robin" }
|
||||
"transition_rules": { "auto_assign": "round_robin", "conditions": [...] },
|
||||
"sla_seconds": 3600
|
||||
}
|
||||
```
|
||||
|
||||
@@ -159,6 +161,12 @@ freeform templates are still supported as guidance for the LLM.
|
||||
`transition_rules.auto_assign`: `round_robin` assigns to team members
|
||||
in rotation. Null = unassigned (manual claim).
|
||||
|
||||
`transition_rules.conditions`: array of routing conditions (v0.35.0).
|
||||
See [Conditional Routing](#conditional-routing-v035).
|
||||
|
||||
`sla_seconds`: optional SLA budget in seconds for this stage. Used by
|
||||
the monitoring dashboard for deadline tracking. Null = no SLA.
|
||||
|
||||
**Auth:** `workflow.create` permission required.
|
||||
|
||||
### Typed Form Template (v0.29.3)
|
||||
@@ -172,7 +180,8 @@ in rotation. Null = unassigned (manual claim).
|
||||
{ "value": "eng", "label": "Engineering" },
|
||||
{ "value": "sales", "label": "Sales" }
|
||||
]},
|
||||
{ "key": "notes", "type": "textarea", "label": "Additional Notes" }
|
||||
{ "key": "notes", "type": "textarea", "label": "Additional Notes",
|
||||
"condition": { "when": "dept", "op": "eq", "value": "eng" } }
|
||||
],
|
||||
"hooks": {
|
||||
"package_id": "uuid",
|
||||
@@ -190,6 +199,41 @@ in rotation. Null = unassigned (manual claim).
|
||||
- `min`, `max` — numeric bounds for number fields
|
||||
- `min_date`, `max_date` — date range bounds (YYYY-MM-DD format)
|
||||
|
||||
**Conditional fields (v0.35.0):** A field can have a `condition` object
|
||||
that controls visibility. When the condition is not met, the field is
|
||||
hidden on the client and skipped during server-side validation.
|
||||
|
||||
```json
|
||||
{ "when": "field_key", "op": "eq|neq|in|exists", "value": "..." }
|
||||
```
|
||||
|
||||
- `eq` (default): field is shown when the referenced field equals `value`
|
||||
- `neq`: shown when not equal
|
||||
- `in`: shown when the referenced field's value is in the `value` array
|
||||
- `exists`: shown when the referenced field has any non-empty value
|
||||
|
||||
**Progressive forms / fieldsets (v0.35.0):** For multi-step forms within
|
||||
a single stage, use `fieldsets` instead of (or alongside) `fields`:
|
||||
|
||||
```json
|
||||
{
|
||||
"fieldsets": [
|
||||
{ "label": "Contact Info", "fields": [
|
||||
{ "key": "name", "type": "text", "label": "Name", "required": true },
|
||||
{ "key": "email", "type": "email", "label": "Email", "required": true }
|
||||
]},
|
||||
{ "label": "Details", "fields": [
|
||||
{ "key": "description", "type": "textarea", "label": "Description" }
|
||||
]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When `fieldsets` is present, the visitor sees one fieldset at a time with
|
||||
next/back navigation. All fields from all fieldsets are submitted together.
|
||||
On the server, `ParseTypedFormTemplate` flattens fieldsets into the
|
||||
`fields` array for validation — backward compatible with existing logic.
|
||||
|
||||
**Hooks:** optional Starlark hooks via extension packages.
|
||||
- `validate`: called before form submission is accepted; can return field errors
|
||||
- `on_submit`: fire-and-forget hook after successful submission
|
||||
@@ -243,6 +287,8 @@ Sets ordinals based on array position.
|
||||
"history_mode": "full",
|
||||
"auto_transition": false,
|
||||
"transition_rules": {},
|
||||
"surface_pkg_id": "uuid|null",
|
||||
"sla_seconds": 3600,
|
||||
"created_at": "..."
|
||||
}
|
||||
```
|
||||
@@ -315,7 +361,8 @@ GET /channels/:id/workflow/status
|
||||
"current_stage": 1,
|
||||
"stage_data": { "name": "Jane", "email": "jane@co.com" },
|
||||
"status": "active|completed|stale",
|
||||
"last_activity_at": "..."
|
||||
"last_activity_at": "...",
|
||||
"stage_entered_at": "..."
|
||||
}
|
||||
```
|
||||
|
||||
@@ -331,12 +378,17 @@ POST /channels/:id/workflow/advance
|
||||
{ "data": { "name": "Jane", "email": "jane@co.com" } }
|
||||
```
|
||||
|
||||
Merges `data` into the channel's accumulated stage data and moves to
|
||||
the next stage. If the current stage is the last, marks the workflow
|
||||
as `completed` and sets `ai_mode=off`.
|
||||
Merges `data` into the channel's accumulated stage data. The next stage
|
||||
is determined by the **conditional routing engine** (v0.35.0):
|
||||
`transition_rules.conditions` are evaluated against the merged stage
|
||||
data. First matching condition selects the target stage; if no conditions
|
||||
match or none are defined, advances to `ordinal + 1`. If the target
|
||||
is beyond the last stage, marks the workflow as `completed` and sets
|
||||
`ai_mode=off`.
|
||||
|
||||
On completion, triggers `on_complete` chaining and webhook delivery
|
||||
if configured.
|
||||
After a successful transition, the `on_advance` hook fires if configured
|
||||
(see below). On completion, triggers `on_complete` chaining and webhook
|
||||
delivery if configured.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
@@ -412,6 +464,31 @@ Valid values: `unassigned`, `claimed`, `completed`.
|
||||
|
||||
**Auth:** Authenticated (team member).
|
||||
|
||||
### Get Assignment (v0.35.0)
|
||||
|
||||
```
|
||||
GET /workflow-assignments/:id
|
||||
```
|
||||
|
||||
Returns the full assignment including review comments.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Add Review Comment (v0.35.0)
|
||||
|
||||
```
|
||||
POST /workflow-assignments/:id/comment
|
||||
```
|
||||
|
||||
```json
|
||||
{ "text": "Looks good, approved with minor note about address." }
|
||||
```
|
||||
|
||||
Appends a comment to the assignment's `review_comments` array.
|
||||
Comments are visible to the next stage when `history_mode=full`.
|
||||
|
||||
**Auth:** Authenticated.
|
||||
|
||||
### Assignment Object
|
||||
|
||||
```json
|
||||
@@ -422,6 +499,9 @@ Valid values: `unassigned`, `claimed`, `completed`.
|
||||
"team_id": "uuid",
|
||||
"assigned_to": "uuid|null",
|
||||
"status": "unassigned|claimed|completed",
|
||||
"review_comments": [
|
||||
{ "text": "...", "user_id": "uuid", "created_at": "..." }
|
||||
],
|
||||
"created_at": "...",
|
||||
"claimed_at": "...|null",
|
||||
"completed_at": "...|null"
|
||||
@@ -569,6 +649,161 @@ The system starts a new instance of the target workflow, passing mapped
|
||||
stage data as initial context. Webhook delivery (if configured) fires
|
||||
before chaining.
|
||||
|
||||
## Conditional Routing (v0.35)
|
||||
|
||||
The routing engine evaluates `transition_rules.conditions` on the
|
||||
departing stage against accumulated `stage_data`. Conditions are
|
||||
evaluated in order; first match wins.
|
||||
|
||||
### Condition Object
|
||||
|
||||
```json
|
||||
{
|
||||
"conditions": [
|
||||
{ "field": "category", "op": "eq", "value": "billing", "target_stage": "Billing Review" },
|
||||
{ "field": "priority", "op": "gte", "value": 5, "target_stage": "Urgent" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `field` | string | Key in `stage_data` to evaluate |
|
||||
| `op` | string | Operator (see below) |
|
||||
| `value` | any | Value to compare against |
|
||||
| `target_stage` | string | Stage name or ordinal to route to |
|
||||
|
||||
**Operators:** `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `contains`, `exists`, `not_exists`.
|
||||
|
||||
`target_stage` resolves by name (case-insensitive) first, then as
|
||||
a numeric ordinal. Backward jumps are allowed (loop stages).
|
||||
|
||||
If no condition matches, falls back to `ordinal + 1`.
|
||||
|
||||
### workflow_route Tool (v0.35.0)
|
||||
|
||||
Persona-callable tool for AI-triggered routing. Available only in
|
||||
workflow channels (`RequireWorkflow` predicate).
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "workflow_route",
|
||||
"parameters": {
|
||||
"target_stage": "Stage name to route to",
|
||||
"reason": "Explanation for the routing decision"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Resolves `target_stage` by name. Records the routing decision in
|
||||
`stage_data._route_history_latest` with `from`, `to`, `reason`, `ts`.
|
||||
|
||||
### Starlark workflow.route()
|
||||
|
||||
```python
|
||||
workflow.route(channel_id, target_stage, reason)
|
||||
```
|
||||
|
||||
Extension-callable routing for programmatic escalation patterns.
|
||||
Requires `workflow.access` permission.
|
||||
|
||||
## on_advance Hook (v0.35.0)
|
||||
|
||||
Starlark entry point that fires synchronously after a stage transition.
|
||||
Configured per-stage in `transition_rules`:
|
||||
|
||||
```json
|
||||
{
|
||||
"on_advance": {
|
||||
"package_id": "uuid",
|
||||
"entry_point": "on_advance"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The hook receives a context dict:
|
||||
|
||||
```python
|
||||
def on_advance(ctx):
|
||||
# ctx["channel_id"] - workflow channel ID
|
||||
# ctx["previous_stage"] - ordinal of the departed stage
|
||||
# ctx["current_stage"] - ordinal of the new stage
|
||||
# ctx["stage_data"] - accumulated stage data dict
|
||||
return {"stage_data": ctx["stage_data"]} # enriched
|
||||
# return None — no change
|
||||
# return {"error": "msg"} — reject (logged, not rolled back)
|
||||
```
|
||||
|
||||
Use cases: external API enrichment via `http.fetch`, cross-field
|
||||
validation, data transformation between stages.
|
||||
|
||||
## Monitoring Dashboard (v0.35.0)
|
||||
|
||||
Admin endpoints for tracking active workflow instances and SLA status.
|
||||
|
||||
### List Active Instances
|
||||
|
||||
```
|
||||
GET /admin/workflows/monitor/instances
|
||||
GET /teams/:teamId/workflows/monitor/instances
|
||||
```
|
||||
|
||||
Returns all active (`workflow_status=active`) instances with SLA info.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [{
|
||||
"channel_id": "uuid",
|
||||
"channel_title": "...",
|
||||
"workflow_id": "uuid",
|
||||
"workflow_name": "...",
|
||||
"current_stage": 1,
|
||||
"stage_name": "Review",
|
||||
"age_seconds": 3600,
|
||||
"stage_age_seconds": 1200,
|
||||
"sla_seconds": 7200,
|
||||
"sla_remaining_seconds": 6000,
|
||||
"sla_breached": false,
|
||||
"last_activity_at": "..."
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
SLA is computed as `stage_entered_at + sla_seconds` vs current time.
|
||||
|
||||
**Auth:** Admin (global) or team member (team-scoped).
|
||||
|
||||
### Stage Funnel
|
||||
|
||||
```
|
||||
GET /admin/workflows/monitor/funnel/:id
|
||||
```
|
||||
|
||||
Returns per-stage instance counts for bottleneck detection.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{ "stage_ordinal": 0, "stage_name": "Intake", "count": 5 },
|
||||
{ "stage_ordinal": 1, "stage_name": "Review", "count": 12 },
|
||||
{ "stage_ordinal": 2, "stage_name": "Complete", "count": 2 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Auth:** Admin.
|
||||
|
||||
### Stale Instances
|
||||
|
||||
```
|
||||
GET /admin/workflows/monitor/stale?threshold_hours=48
|
||||
```
|
||||
|
||||
Returns instances where `last_activity_at` exceeds the threshold.
|
||||
Default threshold: 48 hours.
|
||||
|
||||
**Auth:** Admin.
|
||||
|
||||
## WebSocket Events
|
||||
|
||||
| Event | When |
|
||||
|
||||
Reference in New Issue
Block a user