@hotmeshio/long-tail 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/services/export/index.js +36 -2
- package/dashboard/dist/assets/{AdminDashboard-C0_qN2h3.js → AdminDashboard-Dr5wTIZT.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-C0_qN2h3.js.map → AdminDashboard-Dr5wTIZT.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-DG1zOIiz.js → AgentConfigPage-D52KyJY-.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-DG1zOIiz.js.map → AgentConfigPage-D52KyJY-.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-B5kaAJDM.js → AgentDetailPage-Cbo_qrYF.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-B5kaAJDM.js.map → AgentDetailPage-Cbo_qrYF.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-DJWSuoJA.js → AgentsPage-CvuF8--H.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DJWSuoJA.js.map → AgentsPage-CvuF8--H.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-DrarbHov.js → AvailableEscalationsPage-UzjXcO3W.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-DrarbHov.js.map → AvailableEscalationsPage-UzjXcO3W.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-CvXQwE5Z.js → BotPicker-CM-_u73k.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-CvXQwE5Z.js.map → BotPicker-CM-_u73k.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-BiL9QUxI.js → CapabilitiesPage-D1QEHMKw.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-BiL9QUxI.js.map → CapabilitiesPage-D1QEHMKw.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-D9F01Tny.js → CollapsibleSection-CnPKa7df.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-D9F01Tny.js.map → CollapsibleSection-CnPKa7df.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-C-rjAIK3.js → CredentialsPage-CImIzra4.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-C-rjAIK3.js.map → CredentialsPage-CImIzra4.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DnZF8_vw.js → CronLabel-5HPAmciI.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DnZF8_vw.js.map → CronLabel-5HPAmciI.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-BYDrcsYT.js → CustomDurationPicker-5JzEgS7I.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-BYDrcsYT.js.map → CustomDurationPicker-5JzEgS7I.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-BkiVdGaJ.js → ElapsedCell-B79BF5Mj.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-BkiVdGaJ.js.map → ElapsedCell-B79BF5Mj.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-Cg2SN0WK.js → EscalationsOverview-QvWvbpx3.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-Cg2SN0WK.js.map → EscalationsOverview-QvWvbpx3.js.map} +1 -1
- package/dashboard/dist/assets/EventTable-DAclQwfr.js +2 -0
- package/dashboard/dist/assets/EventTable-DAclQwfr.js.map +1 -0
- package/dashboard/dist/assets/{HomePage-74mCQ5nB.js → HomePage-DVOi7rR1.js} +2 -2
- package/dashboard/dist/assets/{HomePage-74mCQ5nB.js.map → HomePage-DVOi7rR1.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-DL1wEuvL.js → ListToolbar-Bntl2hex.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-DL1wEuvL.js.map → ListToolbar-Bntl2hex.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-D34bLMuP.js → McpOverview-B-xSYPJj.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-D34bLMuP.js.map → McpOverview-B-xSYPJj.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-gLGTGX6g.js → McpQueryDetailPage-Cq71BzjB.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-gLGTGX6g.js.map → McpQueryDetailPage-Cq71BzjB.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-wPHJkhEp.js → McpQueryPage-Dfz87aZF.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-wPHJkhEp.js.map → McpQueryPage-Dfz87aZF.js.map} +1 -1
- package/dashboard/dist/assets/McpRunDetailPage-CXXRgMmJ.js +2 -0
- package/dashboard/dist/assets/{McpRunDetailPage-SoXudCbq.js.map → McpRunDetailPage-CXXRgMmJ.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-BUSxdydO.js → McpRunsPage-MUYvUYqz.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-BUSxdydO.js.map → McpRunsPage-MUYvUYqz.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-CYCl2our.js → OperatorDashboard-Rih1SZrn.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-CYCl2our.js.map → OperatorDashboard-Rih1SZrn.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-DzGacZpO.js → ProcessDetailPage-BMkWCjqD.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-DzGacZpO.js.map → ProcessDetailPage-BMkWCjqD.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-Bmn33g_l.js → ProcessesListPage-vZjUSc7S.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-Bmn33g_l.js.map → ProcessesListPage-vZjUSc7S.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-BTtaabkS.js → RolesPage-kjeAsj3_.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-BTtaabkS.js.map → RolesPage-kjeAsj3_.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-B1R8nJvE.js → RunAsSelector-DC4SLtTq.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-B1R8nJvE.js.map → RunAsSelector-DC4SLtTq.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-CU2ZD9cC.js → SwimlaneTimeline-MUUXgT3o.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-CU2ZD9cC.js.map → SwimlaneTimeline-MUUXgT3o.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-CZw_F8y4.js → TaskDetailPage-C0AlG_2t.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CZw_F8y4.js.map → TaskDetailPage-C0AlG_2t.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-D-vHndyV.js → TasksListPage-69ZWCaCP.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-D-vHndyV.js.map → TasksListPage-69ZWCaCP.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-B6Gz4RAU.js → TimeAgo-CttiZG0k.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-B6Gz4RAU.js.map → TimeAgo-CttiZG0k.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-DZu9PtN2.js → TimestampCell-CYFbEhqc.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-DZu9PtN2.js.map → TimestampCell-CYFbEhqc.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-D4cgYW2p.js → ToolTestPanel-CY_PLZSe.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-D4cgYW2p.js.map → ToolTestPanel-CY_PLZSe.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-C5ZUZpU5.js → TopicDetailPage-Aq4-6GLl.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-C5ZUZpU5.js.map → TopicDetailPage-Aq4-6GLl.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-Cbc0nVj9.js → TopicsPage-BYFKjRY_.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-Cbc0nVj9.js.map → TopicsPage-BYFKjRY_.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-YUoNrFAq.js → UserName-C4_T5-rI.js} +2 -2
- package/dashboard/dist/assets/{UserName-YUoNrFAq.js.map → UserName-C4_T5-rI.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-DUSTBasv.js +2 -0
- package/dashboard/dist/assets/WorkflowExecutionPage-DUSTBasv.js.map +1 -0
- package/dashboard/dist/assets/{WorkflowsDashboard-BFzCyvgv.js → WorkflowsDashboard-_LMWc-OC.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-BFzCyvgv.js.map → WorkflowsDashboard-_LMWc-OC.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-C_y7JCg2.js → WorkflowsOverview-DFrfw554.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-C_y7JCg2.js.map → WorkflowsOverview-DFrfw554.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-CYQjp3JI.js → YamlWorkflowsPage-CZzGwfol.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-CYQjp3JI.js.map → YamlWorkflowsPage-CZzGwfol.js.map} +1 -1
- package/dashboard/dist/assets/{agents-2pDPv2Ww.js → agents-CsKILVSU.js} +2 -2
- package/dashboard/dist/assets/{agents-2pDPv2Ww.js.map → agents-CsKILVSU.js.map} +1 -1
- package/dashboard/dist/assets/{bots-2uGZ2l7A.js → bots-BzEs6Q9L.js} +2 -2
- package/dashboard/dist/assets/{bots-2uGZ2l7A.js.map → bots-BzEs6Q9L.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-CQ29M7lK.js → controlplane-COYEIIAz.js} +2 -2
- package/dashboard/dist/assets/{controlplane-CQ29M7lK.js.map → controlplane-COYEIIAz.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-DWOUjrgL.js → escalation-BZjS2202.js} +2 -2
- package/dashboard/dist/assets/{escalation-DWOUjrgL.js.map → escalation-BZjS2202.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-WrgLIl-W.js → escalation-columns-DtRVmPSB.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-WrgLIl-W.js.map → escalation-columns-DtRVmPSB.js.map} +1 -1
- package/dashboard/dist/assets/{helpers-LN5b1KBx.js → helpers-BngVEys5.js} +2 -2
- package/dashboard/dist/assets/{helpers-LN5b1KBx.js.map → helpers-BngVEys5.js.map} +1 -1
- package/dashboard/dist/assets/{index-C_A76Dl1.js → index-ABsZZf_U.js} +2 -2
- package/dashboard/dist/assets/{index-C_A76Dl1.js.map → index-ABsZZf_U.js.map} +1 -1
- package/dashboard/dist/assets/{index-DqFfgd-7.js → index-BCsShN2l.js} +2 -2
- package/dashboard/dist/assets/{index-DqFfgd-7.js.map → index-BCsShN2l.js.map} +1 -1
- package/dashboard/dist/assets/{index-CdNXBj7w.js → index-Bq5MSkCL.js} +2 -2
- package/dashboard/dist/assets/{index-CdNXBj7w.js.map → index-Bq5MSkCL.js.map} +1 -1
- package/dashboard/dist/assets/{index-BMo7wCw8.js → index-BwivT39P.js} +2 -2
- package/dashboard/dist/assets/{index-BMo7wCw8.js.map → index-BwivT39P.js.map} +1 -1
- package/dashboard/dist/assets/{index-J0dMfAmE.js → index-Byp8BDPs.js} +2 -2
- package/dashboard/dist/assets/{index-J0dMfAmE.js.map → index-Byp8BDPs.js.map} +1 -1
- package/dashboard/dist/assets/{index-dzxsXeMO.js → index-ByxH4qQ-.js} +2 -2
- package/dashboard/dist/assets/{index-dzxsXeMO.js.map → index-ByxH4qQ-.js.map} +1 -1
- package/dashboard/dist/assets/index-COgyD_H2.js +2 -0
- package/dashboard/dist/assets/{index-CryoNbg0.js.map → index-COgyD_H2.js.map} +1 -1
- package/dashboard/dist/assets/index-DXEYynKO.css +1 -0
- package/dashboard/dist/assets/{index-CBS8FBcp.js → index-DcpCR9c_.js} +3 -3
- package/dashboard/dist/assets/{index-CBS8FBcp.js.map → index-DcpCR9c_.js.map} +1 -1
- package/dashboard/dist/assets/index-DuPY59Yu.js +5 -0
- package/dashboard/dist/assets/index-DuPY59Yu.js.map +1 -0
- package/dashboard/dist/assets/{index-ugekH3E2.js → index-DxMNiFPS.js} +2 -2
- package/dashboard/dist/assets/{index-ugekH3E2.js.map → index-DxMNiFPS.js.map} +1 -1
- package/dashboard/dist/assets/{index-CovZsMow.js → index-DykjJxzr.js} +2 -2
- package/dashboard/dist/assets/{index-CovZsMow.js.map → index-DykjJxzr.js.map} +1 -1
- package/dashboard/dist/assets/{index-CFJc47B8.js → index-R61-yG56.js} +2 -2
- package/dashboard/dist/assets/{index-CFJc47B8.js.map → index-R61-yG56.js.map} +1 -1
- package/dashboard/dist/assets/{index-CvzfRxnj.js → index-xgl431mG.js} +2 -2
- package/dashboard/dist/assets/{index-CvzfRxnj.js.map → index-xgl431mG.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-BhZk3Wy9.js → knowledge-DhCbDgy4.js} +2 -2
- package/dashboard/dist/assets/{knowledge-BhZk3Wy9.js.map → knowledge-DhCbDgy4.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-BoNH5uCn.js → mcp-query-il3CfU3U.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-BoNH5uCn.js.map → mcp-query-il3CfU3U.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-runs-BhkGaw2y.js → mcp-runs-4fyRpegc.js} +2 -2
- package/dashboard/dist/assets/{mcp-runs-BhkGaw2y.js.map → mcp-runs-4fyRpegc.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-FOHNY7Zj.js → mcp-xh7T0I-f.js} +2 -2
- package/dashboard/dist/assets/{mcp-FOHNY7Zj.js.map → mcp-xh7T0I-f.js.map} +1 -1
- package/dashboard/dist/assets/{namespaces-duQCQRHq.js → namespaces-BBTvHnRF.js} +2 -2
- package/dashboard/dist/assets/{namespaces-duQCQRHq.js.map → namespaces-BBTvHnRF.js.map} +1 -1
- package/dashboard/dist/assets/{roles-DW6lI_g5.js → roles-D7bx5FAM.js} +2 -2
- package/dashboard/dist/assets/{roles-DW6lI_g5.js.map → roles-D7bx5FAM.js.map} +1 -1
- package/dashboard/dist/assets/{settings-wTRbazzw.js → settings-nt6qyR1S.js} +2 -2
- package/dashboard/dist/assets/{settings-wTRbazzw.js.map → settings-nt6qyR1S.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-C-QX245z.js → tasks-BgxRbhVM.js} +2 -2
- package/dashboard/dist/assets/{tasks-C-QX245z.js.map → tasks-BgxRbhVM.js.map} +1 -1
- package/dashboard/dist/assets/{topics-CAnsyo3w.js → topics-CTtCboHe.js} +2 -2
- package/dashboard/dist/assets/{topics-CAnsyo3w.js.map → topics-CTtCboHe.js.map} +1 -1
- package/dashboard/dist/assets/useCollapsedSections-BU5HULGs.js +2 -0
- package/dashboard/dist/assets/useCollapsedSections-BU5HULGs.js.map +1 -0
- package/dashboard/dist/assets/{useEventHooks-C689a4F7.js → useEventHooks-CkJOmbF-.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-C689a4F7.js.map → useEventHooks-CkJOmbF-.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-BTHFGIsD.js → useYamlActivityEvents-CSMX9He5.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-BTHFGIsD.js.map → useYamlActivityEvents-CSMX9He5.js.map} +1 -1
- package/dashboard/dist/assets/{users--D3LoFOD.js → users-CyF-5WLI.js} +2 -2
- package/dashboard/dist/assets/{users--D3LoFOD.js.map → users-CyF-5WLI.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-MpNdzreD.js → workflows-Bf4_w24H.js} +2 -2
- package/dashboard/dist/assets/{workflows-MpNdzreD.js.map → workflows-Bf4_w24H.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-CFhnJzQy.js → yaml-workflows-Cp1N0NMK.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-CFhnJzQy.js.map → yaml-workflows-Cp1N0NMK.js.map} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/docs/agents.md +29 -1
- package/docs/events.md +7 -0
- package/docs/hitl-guide.md +560 -0
- package/package.json +2 -2
- package/dashboard/dist/assets/EventTable-B9wYf13g.js +0 -2
- package/dashboard/dist/assets/EventTable-B9wYf13g.js.map +0 -1
- package/dashboard/dist/assets/McpRunDetailPage-SoXudCbq.js +0 -2
- package/dashboard/dist/assets/WorkflowExecutionPage-CVGztAdK.js +0 -2
- package/dashboard/dist/assets/WorkflowExecutionPage-CVGztAdK.js.map +0 -1
- package/dashboard/dist/assets/index-ChGBlYKj.css +0 -1
- package/dashboard/dist/assets/index-CryoNbg0.js +0 -2
- package/dashboard/dist/assets/index-DDxZOINn.js +0 -5
- package/dashboard/dist/assets/index-DDxZOINn.js.map +0 -1
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# Human-in-the-Loop (HITL) Guide
|
|
2
|
+
|
|
3
|
+
Build durable workflows that pause for human input and resume automatically when the human responds. Long-tail provides the full escalation lifecycle — claiming, routing, forms, resolution — so you focus on business logic and form design.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture Overview
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Durable Workflow Long-tail Platform Dashboard
|
|
11
|
+
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
12
|
+
│ Run logic │ │ Create record │ │ List view │
|
|
13
|
+
│ Hit decision│──escalate───────>│ Route to role │───event──> │ Detail page │
|
|
14
|
+
│ point │ │ Persist schema │ │ Render form │
|
|
15
|
+
│ ...pause... │ │ │ │ Human edits │
|
|
16
|
+
│ │<──signal─────────│ Signal workflow │<──submit── │ Submit │
|
|
17
|
+
│ Resume with │ │ Mark resolved │ │ │
|
|
18
|
+
│ payload │ └──────────────────┘ └──────────────┘
|
|
19
|
+
└─────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
1. **Workflow escalates** — creates an escalation record with a role, description, and optional form schema
|
|
23
|
+
2. **Platform routes** — the escalation appears in the dashboard for users with the matching role
|
|
24
|
+
3. **Human claims** — a user claims the work item (soft-lock with TTL)
|
|
25
|
+
4. **Human submits** — the form response is sent back as a signal to the paused workflow
|
|
26
|
+
5. **Workflow resumes** — continues execution with the human's input as the resolver payload
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Creating Escalations
|
|
31
|
+
|
|
32
|
+
### Pattern 1: `conditionLT` Signal (Recommended)
|
|
33
|
+
|
|
34
|
+
The workflow stays running and waits for a signal. Lightweight, no re-run needed.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { conditionLT } from 'long-tail/orchestrator';
|
|
38
|
+
import { ltCreateEscalation } from 'long-tail/activities';
|
|
39
|
+
|
|
40
|
+
export async function approvalWorkflow(envelope: LTEnvelope) {
|
|
41
|
+
// ... do initial work ...
|
|
42
|
+
|
|
43
|
+
const signalId = `approval-${ctx.workflowId}`;
|
|
44
|
+
|
|
45
|
+
// Create the escalation with a form schema
|
|
46
|
+
await ltCreateEscalation({
|
|
47
|
+
type: 'approval',
|
|
48
|
+
subtype: 'budget-request',
|
|
49
|
+
description: `Budget approval needed: $${envelope.amount}`,
|
|
50
|
+
role: 'finance-reviewer',
|
|
51
|
+
priority: 2,
|
|
52
|
+
envelope: JSON.stringify(envelope),
|
|
53
|
+
workflowId: ctx.workflowId,
|
|
54
|
+
taskQueue: ctx.taskQueue,
|
|
55
|
+
workflowType: 'approvalWorkflow',
|
|
56
|
+
metadata: {
|
|
57
|
+
signal_id: signalId,
|
|
58
|
+
form_schema: {
|
|
59
|
+
title: 'Budget Approval',
|
|
60
|
+
description: 'Review the budget request and approve or reject.',
|
|
61
|
+
properties: {
|
|
62
|
+
approved: { type: 'boolean', description: 'Approve this request?' },
|
|
63
|
+
notes: { type: 'string', format: 'textarea', description: 'Optional reviewer notes' },
|
|
64
|
+
},
|
|
65
|
+
required: ['approved'],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Workflow pauses here until the human responds
|
|
71
|
+
const decision = await conditionLT<{ approved: boolean; notes?: string }>(signalId);
|
|
72
|
+
|
|
73
|
+
if (decision.approved) {
|
|
74
|
+
// ... proceed with approved flow ...
|
|
75
|
+
} else {
|
|
76
|
+
// ... handle rejection ...
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Pattern 2: Interceptor Return
|
|
82
|
+
|
|
83
|
+
The workflow returns an escalation result. The interceptor handles creation. On resolution, the workflow is re-run with the resolver payload injected into the envelope.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
export async function reviewWorkflow(envelope: LTEnvelope) {
|
|
87
|
+
if (needsHumanReview(envelope)) {
|
|
88
|
+
return {
|
|
89
|
+
type: 'escalation',
|
|
90
|
+
data: { document: envelope.documentUrl },
|
|
91
|
+
message: 'Document requires human review before publishing',
|
|
92
|
+
priority: 2,
|
|
93
|
+
role: 'content-reviewer',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// ... normal flow ...
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Per-Escalation vs Workflow-Level Schema
|
|
101
|
+
|
|
102
|
+
- **Workflow config `resolver_schema`**: Default form for all escalations of this workflow type. Set in the workflow registry.
|
|
103
|
+
- **`metadata.form_schema`**: Per-escalation override. Takes precedence over workflow config. Use when different escalation points in the same workflow need different forms.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## JSON Schema Form Authoring
|
|
108
|
+
|
|
109
|
+
The dashboard renders forms automatically from JSON Schema. No frontend code needed.
|
|
110
|
+
|
|
111
|
+
### Supported Field Types
|
|
112
|
+
|
|
113
|
+
| JSON Type | Renders As |
|
|
114
|
+
|-----------|-----------|
|
|
115
|
+
| `boolean` | Checkbox toggle |
|
|
116
|
+
| `number` | Number input |
|
|
117
|
+
| `string` | Text input (default) |
|
|
118
|
+
| `string` + `enum` | Dropdown select |
|
|
119
|
+
| `null` | Read-only "null" display |
|
|
120
|
+
| `array` | Tag display (read-only) |
|
|
121
|
+
| `object` | Nested section with recursive fields |
|
|
122
|
+
|
|
123
|
+
### String Format Extensions
|
|
124
|
+
|
|
125
|
+
Use the `format` keyword to get specialized inputs:
|
|
126
|
+
|
|
127
|
+
| Format | Input Type |
|
|
128
|
+
|--------|-----------|
|
|
129
|
+
| `"password"` | Password field (masked, with ephemeral token redaction) |
|
|
130
|
+
| `"date"` | Date picker |
|
|
131
|
+
| `"date-time"` | Date + time picker |
|
|
132
|
+
| `"email"` | Email input with validation |
|
|
133
|
+
| `"uri"` | URL input |
|
|
134
|
+
| `"textarea"` | Multi-line textarea (always, regardless of content length) |
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"properties": {
|
|
139
|
+
"due_date": { "type": "string", "format": "date" },
|
|
140
|
+
"contact_email": { "type": "string", "format": "email" },
|
|
141
|
+
"detailed_notes": { "type": "string", "format": "textarea" }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Custom Widgets (`x-lt-widget`)
|
|
147
|
+
|
|
148
|
+
For rich inputs beyond standard HTML types:
|
|
149
|
+
|
|
150
|
+
| Widget | Description |
|
|
151
|
+
|--------|------------|
|
|
152
|
+
| `"file-upload"` | File picker with drag-and-drop. Stores base64 data URL. Use `accept` to filter file types. |
|
|
153
|
+
| `"code-editor"` | Monospace textarea with tab-key support. Use `x-lt-language` for syntax hint. |
|
|
154
|
+
| `"signature"` | HTML5 Canvas drawing pad. Outputs PNG data URL. |
|
|
155
|
+
| `"rich-text"` | Tall textarea for formatted text input. |
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"properties": {
|
|
160
|
+
"screenshot": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"x-lt-widget": "file-upload",
|
|
163
|
+
"accept": "image/*",
|
|
164
|
+
"description": "Upload a screenshot of the issue"
|
|
165
|
+
},
|
|
166
|
+
"fix_script": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"x-lt-widget": "code-editor",
|
|
169
|
+
"x-lt-language": "sql",
|
|
170
|
+
"description": "SQL migration to apply"
|
|
171
|
+
},
|
|
172
|
+
"signature": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"x-lt-widget": "signature",
|
|
175
|
+
"description": "Sign to confirm"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Layout Options (`x-lt-layout`)
|
|
182
|
+
|
|
183
|
+
Control how fields are arranged:
|
|
184
|
+
|
|
185
|
+
| Layout | Behavior |
|
|
186
|
+
|--------|----------|
|
|
187
|
+
| `"two-column"` | Fields in a 2-column grid. Use `x-lt-span: 2` on a field for full-width. |
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"x-lt-layout": "two-column",
|
|
192
|
+
"properties": {
|
|
193
|
+
"first_name": { "type": "string" },
|
|
194
|
+
"last_name": { "type": "string" },
|
|
195
|
+
"notes": { "type": "string", "format": "textarea", "x-lt-span": 2 }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Field Ordering (`x-lt-order`)
|
|
201
|
+
|
|
202
|
+
By default, fields render in JSON key order. Use `x-lt-order` to control sequence:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"x-lt-order": ["priority", "decision", "notes"],
|
|
207
|
+
"properties": {
|
|
208
|
+
"notes": { "type": "string" },
|
|
209
|
+
"decision": { "type": "string", "enum": ["approve", "reject", "defer"] },
|
|
210
|
+
"priority": { "type": "number" }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Validation (`required`)
|
|
216
|
+
|
|
217
|
+
Fields listed in `required` show a red asterisk and block submission when empty:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{
|
|
221
|
+
"required": ["decision"],
|
|
222
|
+
"properties": {
|
|
223
|
+
"decision": { "type": "string", "enum": ["approve", "reject"] },
|
|
224
|
+
"notes": { "type": "string", "description": "Optional comments" }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Read-Only Fields (`readOnly`)
|
|
230
|
+
|
|
231
|
+
Fields with `readOnly: true` display as static text. Useful for showing context alongside editable fields:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"properties": {
|
|
236
|
+
"request_amount": { "type": "number", "readOnly": true },
|
|
237
|
+
"approved_amount": { "type": "number", "description": "Enter the approved amount" }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Schema Title and Description
|
|
243
|
+
|
|
244
|
+
The `title` and `description` at the schema root are used in the UI:
|
|
245
|
+
- **`title`**: Shown as the section header (replaces "Submit Your Resolution" in user mode)
|
|
246
|
+
- **`description`** or **`x-lt-context`**: In user mode, displayed as a context panel alongside the form in a two-panel layout
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"title": "Expense Approval",
|
|
251
|
+
"description": "Review the expense report below. Verify receipts match the claimed amounts. Approve or reject with notes.",
|
|
252
|
+
"properties": { ... }
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Iframe Viewport Protocol
|
|
259
|
+
|
|
260
|
+
For fully custom UIs (PDF viewers, complex multi-step forms, specialized domain UIs), use an iframe viewport.
|
|
261
|
+
|
|
262
|
+
### Schema Declaration
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"x-lt-viewport": {
|
|
267
|
+
"type": "iframe",
|
|
268
|
+
"src": "https://your-app.example.com/hitl-form"
|
|
269
|
+
},
|
|
270
|
+
"properties": { ... }
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
When `x-lt-viewport` is present, the dashboard renders an iframe instead of the standard form.
|
|
275
|
+
|
|
276
|
+
### Message Protocol
|
|
277
|
+
|
|
278
|
+
Communication happens via `window.postMessage`.
|
|
279
|
+
|
|
280
|
+
#### Parent to Iframe
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Sent when the iframe signals ready or on load
|
|
284
|
+
{
|
|
285
|
+
type: 'lt:init',
|
|
286
|
+
escalation: {
|
|
287
|
+
id: string,
|
|
288
|
+
type: string,
|
|
289
|
+
subtype: string,
|
|
290
|
+
description: string | null,
|
|
291
|
+
status: string,
|
|
292
|
+
priority: number,
|
|
293
|
+
role: string,
|
|
294
|
+
workflow_type: string | null,
|
|
295
|
+
},
|
|
296
|
+
schema: Record<string, unknown>, // The full form schema
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Optional: parent requests the iframe to submit
|
|
300
|
+
{
|
|
301
|
+
type: 'lt:requestSubmit'
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Iframe to Parent
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Signal that the iframe is ready to receive init data
|
|
309
|
+
{ type: 'lt:ready' }
|
|
310
|
+
|
|
311
|
+
// Submit the human's response — triggers escalation resolution
|
|
312
|
+
{ type: 'lt:submit', payload: { approved: true, notes: '...' } }
|
|
313
|
+
|
|
314
|
+
// Escalate to a different role
|
|
315
|
+
{ type: 'lt:escalate', target: 'senior-reviewer' }
|
|
316
|
+
|
|
317
|
+
// Auto-resize the iframe
|
|
318
|
+
{ type: 'lt:resize', height: 600 }
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Minimal Example
|
|
322
|
+
|
|
323
|
+
```html
|
|
324
|
+
<!DOCTYPE html>
|
|
325
|
+
<html>
|
|
326
|
+
<head><title>Custom HITL Form</title></head>
|
|
327
|
+
<body>
|
|
328
|
+
<div id="form"></div>
|
|
329
|
+
<button id="submit">Approve</button>
|
|
330
|
+
|
|
331
|
+
<script>
|
|
332
|
+
// Signal ready
|
|
333
|
+
window.parent.postMessage({ type: 'lt:ready' }, '*');
|
|
334
|
+
|
|
335
|
+
// Receive init data
|
|
336
|
+
window.addEventListener('message', (event) => {
|
|
337
|
+
if (event.data.type === 'lt:init') {
|
|
338
|
+
const { escalation, schema } = event.data;
|
|
339
|
+
document.getElementById('form').textContent =
|
|
340
|
+
`Reviewing: ${escalation.description}`;
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Submit response
|
|
345
|
+
document.getElementById('submit').addEventListener('click', () => {
|
|
346
|
+
window.parent.postMessage({
|
|
347
|
+
type: 'lt:submit',
|
|
348
|
+
payload: { approved: true, reviewed_at: new Date().toISOString() },
|
|
349
|
+
}, '*');
|
|
350
|
+
});
|
|
351
|
+
</script>
|
|
352
|
+
</body>
|
|
353
|
+
</html>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Security
|
|
357
|
+
|
|
358
|
+
- The iframe runs with `sandbox="allow-scripts allow-same-origin allow-forms"`
|
|
359
|
+
- The parent validates message origins — only messages from the iframe's origin are accepted
|
|
360
|
+
- The `envelope` field (which may contain secrets) is NOT sent to the iframe
|
|
361
|
+
- Only safe escalation metadata (id, type, description, status, priority, role) is exposed
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Dev Mode vs User Mode
|
|
366
|
+
|
|
367
|
+
The escalation detail page has two viewing modes:
|
|
368
|
+
|
|
369
|
+
| Aspect | Dev Mode | User Mode |
|
|
370
|
+
|--------|----------|-----------|
|
|
371
|
+
| **Default for** | Admins, superadmins, engineers | All other roles |
|
|
372
|
+
| **Shows** | Everything | Title, description, form, action bar |
|
|
373
|
+
| **Hides** | Nothing | Technical IDs, raw JSON, envelope data, AI triage, raw JSON editor |
|
|
374
|
+
| **Persistence** | sessionStorage (per browser session) | sessionStorage |
|
|
375
|
+
|
|
376
|
+
**Key principle**: User mode only hides technical debugging information. All HITL workflow actions (claim, submit, escalate, release) are always visible in both modes.
|
|
377
|
+
|
|
378
|
+
Privileged users can toggle between modes via the switch in the page header.
|
|
379
|
+
|
|
380
|
+
### Designing for User Mode
|
|
381
|
+
|
|
382
|
+
To create a polished user mode experience:
|
|
383
|
+
|
|
384
|
+
1. Set `title` on your schema — it replaces the section header
|
|
385
|
+
2. Set `description` or `x-lt-context` — it appears as a context panel in a two-panel layout
|
|
386
|
+
3. Use `readOnly` fields for context the human needs to see but shouldn't edit
|
|
387
|
+
4. Use `x-lt-order` to put the most important fields first
|
|
388
|
+
5. Use `required` to guide users on what must be filled
|
|
389
|
+
6. Use descriptive `description` on individual fields for inline help text
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Role-Based Routing
|
|
394
|
+
|
|
395
|
+
Escalations are routed by role. Users only see escalations for roles they hold.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
// Workflow escalates to a specific role
|
|
399
|
+
await ltCreateEscalation({
|
|
400
|
+
role: 'finance-reviewer', // Only users with this role see it
|
|
401
|
+
// ...
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Escalation Chains
|
|
406
|
+
|
|
407
|
+
Users can escalate to other roles via the "Escalate" tab:
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
Analyst → Senior Analyst → Manager → VP
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Configure escalation targets in the role configuration (Admin > Roles). Each role defines which other roles it can escalate to.
|
|
414
|
+
|
|
415
|
+
### Multi-Tier Example
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
// Level 1: Auto-review
|
|
419
|
+
const result = await autoReview(document);
|
|
420
|
+
|
|
421
|
+
if (result.confidence < 0.8) {
|
|
422
|
+
// Level 2: Human analyst
|
|
423
|
+
await ltCreateEscalation({
|
|
424
|
+
role: 'analyst',
|
|
425
|
+
description: `Low confidence review (${result.confidence})`,
|
|
426
|
+
metadata: {
|
|
427
|
+
form_schema: {
|
|
428
|
+
title: 'Document Review',
|
|
429
|
+
properties: {
|
|
430
|
+
approved: { type: 'boolean' },
|
|
431
|
+
corrections: { type: 'string', format: 'textarea' },
|
|
432
|
+
},
|
|
433
|
+
required: ['approved'],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
// User can further escalate to 'senior-analyst' or 'manager' from the dashboard
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Worked Examples
|
|
444
|
+
|
|
445
|
+
### Simple Approval
|
|
446
|
+
|
|
447
|
+
A workflow needs a yes/no decision with optional notes.
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
metadata: {
|
|
451
|
+
signal_id: signalId,
|
|
452
|
+
form_schema: {
|
|
453
|
+
title: 'Approve Request',
|
|
454
|
+
description: 'Review the details and approve or reject this request.',
|
|
455
|
+
required: ['approved'],
|
|
456
|
+
properties: {
|
|
457
|
+
approved: { type: 'boolean', description: 'Check to approve' },
|
|
458
|
+
notes: { type: 'string', format: 'textarea', description: 'Optional comments' },
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Document Review with PDF Viewer
|
|
465
|
+
|
|
466
|
+
Use an iframe viewport to embed a PDF viewer alongside approval controls.
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
metadata: {
|
|
470
|
+
signal_id: signalId,
|
|
471
|
+
form_schema: {
|
|
472
|
+
title: 'Document Review',
|
|
473
|
+
'x-lt-viewport': {
|
|
474
|
+
type: 'iframe',
|
|
475
|
+
src: 'https://internal.example.com/pdf-reviewer',
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The iframe at `pdf-reviewer` loads the document, renders it with a viewer, and posts `lt:submit` with the review decision.
|
|
482
|
+
|
|
483
|
+
### Multi-Field Data Entry
|
|
484
|
+
|
|
485
|
+
A complex form with layout and validation.
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
metadata: {
|
|
489
|
+
signal_id: signalId,
|
|
490
|
+
form_schema: {
|
|
491
|
+
title: 'Customer Intake',
|
|
492
|
+
description: 'Complete the customer information form. All required fields must be filled before submission.',
|
|
493
|
+
'x-lt-layout': 'two-column',
|
|
494
|
+
'x-lt-order': ['first_name', 'last_name', 'email', 'phone', 'tier', 'notes'],
|
|
495
|
+
required: ['first_name', 'last_name', 'email', 'tier'],
|
|
496
|
+
properties: {
|
|
497
|
+
first_name: { type: 'string' },
|
|
498
|
+
last_name: { type: 'string' },
|
|
499
|
+
email: { type: 'string', format: 'email' },
|
|
500
|
+
phone: { type: 'string' },
|
|
501
|
+
tier: {
|
|
502
|
+
type: 'string',
|
|
503
|
+
enum: ['free', 'pro', 'enterprise'],
|
|
504
|
+
description: 'Select the customer tier',
|
|
505
|
+
},
|
|
506
|
+
notes: {
|
|
507
|
+
type: 'string',
|
|
508
|
+
format: 'textarea',
|
|
509
|
+
'x-lt-span': 2,
|
|
510
|
+
description: 'Additional notes about this customer',
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Credential Provisioning
|
|
518
|
+
|
|
519
|
+
Password fields are automatically redacted and replaced with ephemeral tokens (15-min TTL) before being sent back to the workflow.
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
metadata: {
|
|
523
|
+
signal_id: signalId,
|
|
524
|
+
form_schema: {
|
|
525
|
+
title: 'Provide Credentials',
|
|
526
|
+
description: 'Enter the API credentials for this integration. Passwords are encrypted and stored as ephemeral tokens.',
|
|
527
|
+
required: ['api_key', 'api_secret'],
|
|
528
|
+
properties: {
|
|
529
|
+
api_key: { type: 'string', description: 'API Key' },
|
|
530
|
+
api_secret: { type: 'string', format: 'password', description: 'API Secret (will be redacted)' },
|
|
531
|
+
environment: {
|
|
532
|
+
type: 'string',
|
|
533
|
+
enum: ['sandbox', 'production'],
|
|
534
|
+
description: 'Target environment',
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## What Long-tail Provides (For Free)
|
|
544
|
+
|
|
545
|
+
When you author a HITL-backed workflow, the platform handles:
|
|
546
|
+
|
|
547
|
+
- **Escalation routing** — role-based, priority-ordered work queues
|
|
548
|
+
- **Claim/release** — soft-lock with TTL, prevents duplicate work
|
|
549
|
+
- **Real-time updates** — NATS/Socket.IO events push changes to the dashboard instantly
|
|
550
|
+
- **Form rendering** — JSON Schema to rich form controls, no frontend code needed
|
|
551
|
+
- **Dev/user mode** — technical vs clean views, per-session preference
|
|
552
|
+
- **Section state persistence** — collapsed sections remembered across navigation
|
|
553
|
+
- **Escalation chains** — users can re-route work to other roles
|
|
554
|
+
- **AI triage** — optional auto-resolution for common patterns (dev mode)
|
|
555
|
+
- **Signal routing** — 5 resolution paths (conditionLT, waitFor, triage, re-run, notification-only)
|
|
556
|
+
- **Credential security** — password fields use ephemeral tokens, never stored in plain text
|
|
557
|
+
- **Telemetry** — trace IDs link escalations to OpenTelemetry traces
|
|
558
|
+
- **Bulk operations** — bulk claim, assign, escalate, triage for queue management
|
|
559
|
+
|
|
560
|
+
You write the workflow and the schema. Everything else is provided.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/long-tail",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@anthropic-ai/sdk": "^0.92.0",
|
|
71
71
|
"@aws-sdk/client-s3": "^3.1017.0",
|
|
72
72
|
"@aws-sdk/s3-request-presigner": "^3.1045.0",
|
|
73
|
-
"@hotmeshio/hotmesh": "^0.19.
|
|
73
|
+
"@hotmeshio/hotmesh": "^0.19.2",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
75
75
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
|
|
76
76
|
"@opentelemetry/resources": "^2.5.1",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as d,j as t}from"./vendor-query-B2UbickB.js";import{F as $,b as g}from"./FilterBar-Ck4K4rzu.js";import{f as T,C as F}from"./index-CBS8FBcp.js";import{E as O}from"./SwimlaneTimeline-CU2ZD9cC.js";const y="lt-collapsed-sections";function D(r){try{const n=localStorage.getItem(`${y}:${r}`);return n?new Set(JSON.parse(n)):new Set}catch{return new Set}}function P(r,n){localStorage.setItem(`${y}:${r}`,JSON.stringify([...n]))}function G(r){const[n,l]=d.useState(()=>D(r)),p=d.useCallback(o=>n.has(o),[n]),u=d.useCallback(o=>{l(x=>{const c=new Set(x);return c.has(o)?c.delete(o):c.add(o),P(r,c),c})},[r]);return{isCollapsed:p,toggle:u}}const A=new Set(["activity_task_scheduled","timer_started","child_workflow_execution_started","signal_wait_started"]),L=new Set(["activity_task_completed","activity_task_failed","timer_fired","child_workflow_execution_completed","child_workflow_execution_failed","workflow_execution_signaled"]);function R(r){const n=new Set;for(const l of r)L.has(l.event_type)&&l.attributes.timeline_key&&n.add(l.attributes.timeline_key);return n}function K({events:r,childTasks:n}){const[l,p]=d.useState(""),[u,o]=d.useState(""),[x,c]=d.useState("asc"),[m,f]=d.useState(new Set),b=[...new Set(r.map(e=>e.category))].sort(),w=[...new Set(r.map(e=>e.event_type))].sort(),v=R(r),S=e=>{if(e.duration_ms!==null||!A.has(e.event_type))return!1;const s=e.attributes.timeline_key;return!(s&&v.has(s))};let a=r;l&&(a=a.filter(e=>e.category===l)),u&&(a=a.filter(e=>e.event_type===u)),a=[...a].sort((e,s)=>x==="asc"?e.event_id-s.event_id:s.event_id-e.event_id);const j=e=>{switch(e){case"activity":return"bg-blue-500";case"signal":return"bg-emerald-500";case"timer":return"bg-status-warning";case"child_workflow":return"bg-violet-500";default:return"bg-text-tertiary"}},k=e=>{const s=e.event_type;if(e.attributes.activity_type)return`${s} — ${e.attributes.activity_type}`;if(e.attributes.signal_name)return`${s} — ${e.attributes.signal_name}`;if(e.attributes.child_workflow_id){const i=e.attributes.child_workflow_id,C=i.length>24?`${i.slice(0,24)}...`:i;return`${s} — ${C}`}return s},N=e=>{if(!(n!=null&&n.length))return;const s=e.attributes.activity_type;if(s)return n.find(i=>i.workflow_type===s)},_=e=>{f(s=>{const i=new Set(s);return i.has(e)?i.delete(e):i.add(e),i})},h=a.length>0&&a.every(e=>m.has(e.event_id)),E=()=>{f(h?new Set:new Set(a.map(e=>e.event_id)))};return t.jsxs("div",{children:[t.jsxs("div",{className:"px-6 py-4 border-b border-surface-border flex items-center justify-between flex-wrap gap-3",children:[t.jsxs("div",{className:"flex items-center gap-4",children:[t.jsxs("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary",children:["Events (",a.length,")"]}),a.length>0&&t.jsx("button",{onClick:E,className:"text-[10px] text-accent hover:underline",children:h?"Collapse all":"Expand all"})]}),t.jsxs($,{children:[t.jsx(g,{label:"Category",value:l,onChange:p,options:b.map(e=>({value:e,label:e}))}),t.jsx(g,{label:"Type",value:u,onChange:o,options:w.map(e=>({value:e,label:e}))}),t.jsx("button",{onClick:()=>c(e=>e==="asc"?"desc":"asc"),className:"btn-ghost text-xs",children:x==="asc"?"Oldest first":"Newest first"})]})]}),t.jsx("div",{children:a.length===0?t.jsx("div",{className:"px-6 py-8 text-center",children:t.jsx("p",{className:"text-sm text-text-tertiary",children:"No events match the current filters"})}):a.map(e=>{const s=m.has(e.event_id),i=S(e);return t.jsxs("div",{className:"border-b border-surface-border last:border-b-0",children:[t.jsxs("div",{className:"px-6 py-3 flex items-center gap-4 cursor-pointer hover:bg-surface-hover transition-colors duration-200",onClick:()=>_(e.event_id),children:[t.jsx("span",{className:`text-[10px] text-text-tertiary transition-transform duration-300 ${s?"rotate-90":""}`,children:"▶"}),t.jsx("span",{className:"text-xs font-mono text-text-tertiary w-8 shrink-0",children:e.event_id}),t.jsx("span",{className:`w-2 h-2 rounded-full shrink-0 ${j(e.category)}`}),t.jsx("span",{className:"text-sm text-text-primary flex-1 truncate",children:k(e)}),t.jsx("span",{className:"text-xs font-mono text-text-tertiary shrink-0",children:i?t.jsxs("span",{className:"inline-flex items-center gap-1 text-status-warning",children:[t.jsx("span",{className:"w-1.5 h-1.5 rounded-full bg-status-warning animate-pulse"}),"Pending"]}):e.duration_ms!==null?T(e.duration_ms):"--"}),t.jsx("time",{className:"text-[10px] font-mono text-text-tertiary shrink-0",children:new Date(e.event_time).toLocaleTimeString()})]}),t.jsx(F,{open:s&&!!e.attributes,children:t.jsx("div",{className:"px-6 pb-4 pl-16",children:t.jsx(O,{event:e,childTask:N(e),pending:i,onClose:()=>_(e.event_id)})})})]},e.event_id)})})]})}export{K as E,G as u};
|
|
2
|
-
//# sourceMappingURL=EventTable-B9wYf13g.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EventTable-B9wYf13g.js","sources":["../../src/hooks/useCollapsedSections.ts","../../src/pages/workflows/workflow-execution/EventTable.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\n\nconst STORAGE_KEY = 'lt-collapsed-sections';\n\nfunction load(pageKey: string): Set<string> {\n try {\n const raw = localStorage.getItem(`${STORAGE_KEY}:${pageKey}`);\n if (!raw) return new Set();\n return new Set(JSON.parse(raw));\n } catch {\n return new Set();\n }\n}\n\nfunction save(pageKey: string, set: Set<string>) {\n localStorage.setItem(`${STORAGE_KEY}:${pageKey}`, JSON.stringify([...set]));\n}\n\nexport function useCollapsedSections(pageKey: string) {\n const [collapsed, setCollapsed] = useState<Set<string>>(() => load(pageKey));\n\n const isCollapsed = useCallback(\n (section: string) => collapsed.has(section),\n [collapsed],\n );\n\n const toggle = useCallback(\n (section: string) => {\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(section)) {\n next.delete(section);\n } else {\n next.add(section);\n }\n save(pageKey, next);\n return next;\n });\n },\n [pageKey],\n );\n\n return { isCollapsed, toggle };\n}\n","import { useState } from 'react';\nimport { FilterBar, FilterSelect } from '../../../components/common/data/FilterBar';\nimport { Collapsible } from '../../../components/common/layout/Collapsible';\nimport type { WorkflowExecutionEvent, LTTaskRecord } from '../../../api/types';\nimport { EventDetailPanel } from './EventDetailPanel';\nimport { formatDuration } from './utils';\n\ninterface EventTableProps {\n events: WorkflowExecutionEvent[];\n childTasks?: LTTaskRecord[];\n}\n\n/** Event types that represent the \"start\" phase of a paired operation */\nconst STARTED_TYPES = new Set([\n 'activity_task_scheduled',\n 'timer_started',\n 'child_workflow_execution_started',\n 'signal_wait_started',\n]);\n\n/** Completion event types that close a paired operation */\nconst COMPLETED_TYPES = new Set([\n 'activity_task_completed',\n 'activity_task_failed',\n 'timer_fired',\n 'child_workflow_execution_completed',\n 'child_workflow_execution_failed',\n 'workflow_execution_signaled',\n]);\n\n/**\n * Build a set of timeline_keys that have a matching completion event.\n * A \"scheduled\" event is only truly pending if no completion exists.\n */\nfunction buildCompletedKeys(events: WorkflowExecutionEvent[]): Set<string> {\n const keys = new Set<string>();\n for (const e of events) {\n if (COMPLETED_TYPES.has(e.event_type) && e.attributes.timeline_key) {\n keys.add(e.attributes.timeline_key);\n }\n }\n return keys;\n}\n\nexport function EventTable({ events, childTasks }: EventTableProps) {\n const [categoryFilter, setCategoryFilter] = useState('');\n const [typeFilter, setTypeFilter] = useState('');\n const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');\n const [expandedEvents, setExpandedEvents] = useState<Set<number>>(new Set());\n\n const categories = [...new Set(events.map((e) => e.category))].sort();\n const eventTypes = [...new Set(events.map((e) => e.event_type))].sort();\n const completedKeys = buildCompletedKeys(events);\n\n /** True only when a \"started/scheduled\" event has no matching completion */\n const isPending = (evt: WorkflowExecutionEvent): boolean => {\n if (evt.duration_ms !== null) return false;\n if (!STARTED_TYPES.has(evt.event_type)) return false;\n const tlk = evt.attributes.timeline_key;\n if (tlk && completedKeys.has(tlk)) return false;\n return true;\n };\n\n let filtered = events;\n if (categoryFilter) filtered = filtered.filter((e) => e.category === categoryFilter);\n if (typeFilter) filtered = filtered.filter((e) => e.event_type === typeFilter);\n\n filtered = [...filtered].sort((a, b) =>\n sortOrder === 'asc' ? a.event_id - b.event_id : b.event_id - a.event_id,\n );\n\n const categoryDot = (category: string) => {\n switch (category) {\n case 'activity':\n return 'bg-blue-500';\n case 'signal':\n return 'bg-emerald-500';\n case 'timer':\n return 'bg-status-warning';\n case 'child_workflow':\n return 'bg-violet-500';\n default:\n return 'bg-text-tertiary';\n }\n };\n\n /** Build a descriptive label for an event row */\n const eventLabel = (evt: WorkflowExecutionEvent): string => {\n const base = evt.event_type;\n if (evt.attributes.activity_type) {\n return `${base} — ${evt.attributes.activity_type}`;\n }\n if (evt.attributes.signal_name) {\n return `${base} — ${evt.attributes.signal_name}`;\n }\n if (evt.attributes.child_workflow_id) {\n const id = evt.attributes.child_workflow_id;\n const truncated = id.length > 24 ? `${id.slice(0, 24)}...` : id;\n return `${base} — ${truncated}`;\n }\n return base;\n };\n\n /** Find a matching child task for an event's activity_type */\n const findChildTask = (evt: WorkflowExecutionEvent): LTTaskRecord | undefined => {\n if (!childTasks?.length) return undefined;\n const activityType = evt.attributes.activity_type;\n if (!activityType) return undefined;\n return childTasks.find((t) => t.workflow_type === activityType);\n };\n\n const toggleEvent = (id: number) => {\n setExpandedEvents((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n };\n\n const allExpanded = filtered.length > 0 && filtered.every((e) => expandedEvents.has(e.event_id));\n\n const toggleAll = () => {\n if (allExpanded) {\n setExpandedEvents(new Set());\n } else {\n setExpandedEvents(new Set(filtered.map((e) => e.event_id)));\n }\n };\n\n return (\n <div>\n <div className=\"px-6 py-4 border-b border-surface-border flex items-center justify-between flex-wrap gap-3\">\n <div className=\"flex items-center gap-4\">\n <p className=\"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary\">\n Events ({filtered.length})\n </p>\n {filtered.length > 0 && (\n <button onClick={toggleAll} className=\"text-[10px] text-accent hover:underline\">\n {allExpanded ? 'Collapse all' : 'Expand all'}\n </button>\n )}\n </div>\n <FilterBar>\n <FilterSelect\n label=\"Category\"\n value={categoryFilter}\n onChange={setCategoryFilter}\n options={categories.map((c) => ({ value: c, label: c }))}\n />\n <FilterSelect\n label=\"Type\"\n value={typeFilter}\n onChange={setTypeFilter}\n options={eventTypes.map((t) => ({ value: t, label: t }))}\n />\n <button\n onClick={() => setSortOrder((s) => (s === 'asc' ? 'desc' : 'asc'))}\n className=\"btn-ghost text-xs\"\n >\n {sortOrder === 'asc' ? 'Oldest first' : 'Newest first'}\n </button>\n </FilterBar>\n </div>\n\n <div>\n {filtered.length === 0 ? (\n <div className=\"px-6 py-8 text-center\">\n <p className=\"text-sm text-text-tertiary\">No events match the current filters</p>\n </div>\n ) : (\n filtered.map((evt) => {\n const isExpanded = expandedEvents.has(evt.event_id);\n const pending = isPending(evt);\n\n return (\n <div\n key={evt.event_id}\n className=\"border-b border-surface-border last:border-b-0\"\n >\n {/* Row header */}\n <div\n className=\"px-6 py-3 flex items-center gap-4 cursor-pointer hover:bg-surface-hover transition-colors duration-200\"\n onClick={() => toggleEvent(evt.event_id)}\n >\n {/* Expand chevron */}\n <span\n className={`text-[10px] text-text-tertiary transition-transform duration-300 ${isExpanded ? 'rotate-90' : ''}`}\n >\n ▶\n </span>\n\n <span className=\"text-xs font-mono text-text-tertiary w-8 shrink-0\">\n {evt.event_id}\n </span>\n <span\n className={`w-2 h-2 rounded-full shrink-0 ${categoryDot(evt.category)}`}\n />\n <span className=\"text-sm text-text-primary flex-1 truncate\">\n {eventLabel(evt)}\n </span>\n <span className=\"text-xs font-mono text-text-tertiary shrink-0\">\n {pending ? (\n <span className=\"inline-flex items-center gap-1 text-status-warning\">\n <span className=\"w-1.5 h-1.5 rounded-full bg-status-warning animate-pulse\" />\n Pending\n </span>\n ) : evt.duration_ms !== null ? (\n formatDuration(evt.duration_ms)\n ) : (\n '--'\n )}\n </span>\n <time className=\"text-[10px] font-mono text-text-tertiary shrink-0\">\n {new Date(evt.event_time).toLocaleTimeString()}\n </time>\n </div>\n\n {/* Inline detail panel — directly below the clicked row */}\n <Collapsible open={isExpanded && !!evt.attributes}>\n <div className=\"px-6 pb-4 pl-16\">\n <EventDetailPanel\n event={evt}\n childTask={findChildTask(evt)}\n pending={pending}\n onClose={() => toggleEvent(evt.event_id)}\n />\n </div>\n </Collapsible>\n </div>\n );\n })\n )}\n </div>\n </div>\n );\n}\n"],"names":["STORAGE_KEY","load","pageKey","raw","save","set","useCollapsedSections","collapsed","setCollapsed","useState","isCollapsed","useCallback","section","toggle","prev","next","STARTED_TYPES","COMPLETED_TYPES","buildCompletedKeys","events","keys","e","EventTable","childTasks","categoryFilter","setCategoryFilter","typeFilter","setTypeFilter","sortOrder","setSortOrder","expandedEvents","setExpandedEvents","categories","eventTypes","completedKeys","isPending","evt","tlk","filtered","a","b","categoryDot","category","eventLabel","base","id","truncated","findChildTask","activityType","t","toggleEvent","allExpanded","toggleAll","jsxs","jsx","FilterBar","FilterSelect","c","s","isExpanded","pending","formatDuration","Collapsible","EventDetailPanel"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{b as P,a as I,j as e}from"./vendor-query-B2UbickB.js";import{b as D}from"./mcp-runs-BhkGaw2y.js";import{u as R}from"./settings-wTRbazzw.js";import{S as K}from"./StatusBadge-XQlNFwmH.js";import{u as U,D as $,c as N,J as w,N as J}from"./index-CBS8FBcp.js";import{P as k}from"./PageHeader-Bo0SpcCK.js";import{C as L}from"./CopyableId-DmLF-RqZ.js";import{C as j}from"./CollapsibleSection-D9F01Tny.js";import{u as M,E as V}from"./EventTable-B9wYf13g.js";import{L as q}from"./ListToolbar-DL1wEuvL.js";import{S as B}from"./SwimlaneTimeline-CU2ZD9cC.js";import{e as F,f as H}from"./vendor-react-CX88sFS5.js";import"./vendor-icons-BNtvBbnj.js";import"./FilterBar-Ck4K4rzu.js";const Q={running:"in_progress",completed:"completed",failed:"failed"};function le(){var g,v;const{jobId:s}=F(),[o]=H(),r=o.get("namespace")||"longtail",h=P(),{data:t,isLoading:S,error:i,refetch:_,isFetching:C}=D(s,r),{data:x}=R(),{isCollapsed:d,toggle:m}=M("mcp-run-detail"),f=((g=x==null?void 0:x.telemetry)==null?void 0:g.traceUrl)??null,E=I.useCallback(c=>{!s||c.workflowId!==s||h.invalidateQueries({queryKey:["mcpRunExecution",s]})},[s,h]);if(U(`${J}.activity.>`,E),S)return e.jsxs("div",{className:"animate-pulse space-y-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-60 bg-surface-sunken rounded"})]});if(i||!t)return e.jsxs("div",{children:[e.jsx(k,{title:"Pipeline Execution"}),e.jsxs("div",{className:"mt-4 text-center py-8",children:[e.jsx("p",{className:"text-sm text-text-primary mb-1",children:(v=i==null?void 0:i.message)!=null&&v.includes("expired")?"Execution data is no longer available":"Unable to load execution"}),e.jsx("p",{className:"text-xs text-text-tertiary",children:(i==null?void 0:i.message)??"The run could not be resolved."})]})]});const{events:u,summary:a}=t,p=u.find(c=>{var y;return c.category==="activity"&&((y=c.attributes.kind)==null?void 0:y.includes("completed"))}),T=(p==null?void 0:p.attributes.result)??null,n=t.result,b=(n==null?void 0:n.data)??n??null;return e.jsxs("div",{children:[e.jsx(k,{title:"Pipeline Execution",actions:e.jsx(q,{onRefresh:()=>_(),isFetching:C,apiPath:`/mcp-runs/${s}/execution?app_id=${r}`})}),e.jsxs("div",{className:"bg-surface-raised border border-surface-border rounded-md p-5 mb-8",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-4",children:[e.jsx("h2",{className:"text-sm font-mono text-text-primary truncate flex-1",children:t.workflow_id}),e.jsxs("div",{className:"flex items-center gap-4 shrink-0",children:[e.jsx(l,{label:"Tools",value:a.activities.user}),e.jsx(l,{label:"System",value:a.activities.system,muted:!0}),a.child_workflows.total>0&&e.jsx(l,{label:"Children",value:a.child_workflows.total}),a.timers>0&&e.jsx(l,{label:"Timers",value:a.timers}),a.signals>0&&e.jsx(l,{label:"Signals",value:a.signals}),e.jsx(K,{status:Q[t.status]??t.status})]})]}),e.jsxs("div",{className:"grid grid-cols-6 gap-x-6",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-0.5",children:"Server"}),e.jsx("p",{className:"text-xs font-mono text-text-primary truncate",children:r})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-0.5",children:"Tool"}),e.jsx("p",{className:"text-xs font-mono text-text-primary truncate",children:t.workflow_type||"—"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-0.5",children:"Duration"}),e.jsx($,{ms:t.duration_ms,className:"font-mono text-text-primary"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-0.5",children:"Started"}),t.start_time?e.jsx(N,{date:t.start_time,format:"datetime",className:"font-mono text-text-primary"}):e.jsx("span",{className:"text-xs text-text-tertiary",children:"--"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-0.5",children:"Completed"}),t.close_time?e.jsx(N,{date:t.close_time,format:"datetime",className:"font-mono text-text-primary"}):e.jsx("span",{className:"text-xs text-text-tertiary",children:"--"})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-0.5",children:"Trace"}),t.trace_id?e.jsx(L,{label:"",value:t.trace_id,href:f?f.replace("{traceId}",t.trace_id):void 0,external:!0}):e.jsx("span",{className:"text-xs text-text-tertiary",children:"—"})]})]})]}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(j,{title:"Details",sectionKey:"details",isCollapsed:d("details"),onToggle:m,children:e.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 mb-6",children:[e.jsx("div",{children:e.jsx(w,{data:T??{},label:"Input"})}),b!==null&&e.jsx("div",{children:e.jsx(w,{data:b,label:"Result"})})]})}),e.jsx(j,{title:"Execution Timeline",sectionKey:"timeline",isCollapsed:d("timeline"),onToggle:m,children:e.jsx(B,{events:u,outline:!0})}),e.jsx(j,{title:"Events",sectionKey:"events",isCollapsed:d("events"),onToggle:m,children:e.jsx(V,{events:u})})]})]})}function l({label:s,value:o,muted:r}){return e.jsxs("div",{className:"flex items-center gap-1.5 text-xs",children:[e.jsx("span",{className:"text-text-tertiary",children:s}),e.jsx("span",{className:`font-medium tabular-nums ${r?"text-text-tertiary":"text-text-primary"}`,children:o})]})}export{le as McpRunDetailPage};
|
|
2
|
-
//# sourceMappingURL=McpRunDetailPage-SoXudCbq.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{j as e,a as b}from"./vendor-query-B2UbickB.js";import{a as $,g as L}from"./workflows-MpNdzreD.js";import{h as R}from"./useEventHooks-C689a4F7.js";import{u as P,E as A}from"./EventTable-B9wYf13g.js";import{b as D,e as K}from"./tasks-C-QX245z.js";import{c as k,D as Q,J as N,d as V}from"./index-CBS8FBcp.js";import{P as z}from"./PageHeader-Bo0SpcCK.js";import{C as g}from"./CollapsibleSection-D9F01Tny.js";import{L as B}from"./ListToolbar-DL1wEuvL.js";import{S as _}from"./StatusBadge-XQlNFwmH.js";import{L as u,e as H,u as J,c as M}from"./vendor-react-CX88sFS5.js";import{S as O}from"./SwimlaneTimeline-CU2ZD9cC.js";import"./FilterBar-Ck4K4rzu.js";import"./vendor-icons-BNtvBbnj.js";function x({label:t,value:a,mono:r,truncate:s,children:n}){return e.jsxs("div",{children:[e.jsx("p",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary mb-1",children:t}),n||e.jsx("p",{className:`text-xs text-text-primary ${r?"font-mono":""} ${s?"truncate":""}`,title:s?a:void 0,children:a})]})}function F(t){const a=t.lastIndexOf("-");return a<=0?{taskQueue:t,workflowType:t}:{taskQueue:t.substring(0,a),workflowType:t.substring(a+1)}}function U({execution:t,task:a,escalations:r}){const n=a&&a.workflow_id===t.workflow_id?a.parent_workflow_id:null,{taskQueue:o,workflowType:m}=F(t.workflow_type);return e.jsxs("div",{className:"px-6 py-6 mb-6",children:[e.jsxs("div",{className:"grid grid-cols-2 sm:grid-cols-4 gap-y-4 gap-x-8",children:[e.jsx(x,{label:"Workflow Type",value:m,mono:!0}),e.jsx(x,{label:"Task Queue",value:o,mono:!0}),e.jsx(x,{label:"Start Time",children:t.start_time?e.jsx(k,{date:t.start_time,format:"datetime",className:"text-text-primary"}):e.jsx("span",{className:"text-xs text-text-tertiary",children:"--"})}),e.jsx(x,{label:"End Time",children:t.close_time?e.jsx(k,{date:t.close_time,format:"datetime",className:"text-text-primary"}):e.jsx("span",{className:"text-xs text-text-tertiary",children:"--"})}),e.jsx(x,{label:"Duration",children:e.jsx(Q,{ms:t.duration_ms,className:"font-mono text-text-primary"})}),e.jsx(x,{label:"History Size",value:`${t.summary.total_events} events`}),e.jsx(x,{label:"Activities",value:`${t.summary.activities.completed} / ${t.summary.activities.total}`}),e.jsx(x,{label:"Run ID",value:t.workflow_id,mono:!0,truncate:!0})]}),(n||a||r&&r.length>0)&&e.jsxs("div",{className:"mt-5 pt-4 border-t border-surface-border space-y-3",children:[n&&e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary shrink-0",children:"Parent"}),e.jsx(u,{to:`/workflows/executions/${n}`,className:"text-xs font-mono text-accent hover:underline truncate",title:n,children:n})]}),!1,r&&r.length>0&&e.jsxs("div",{className:"flex items-start gap-2",children:[e.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-widest text-text-tertiary shrink-0 mt-0.5",children:r.length===1?"Escalation":"Escalations"}),e.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1",children:r.map(l=>e.jsxs(u,{to:`/escalations/detail/${l.id}`,className:"inline-flex items-center gap-1.5 text-xs font-mono text-accent hover:underline",children:[e.jsx("span",{children:l.type}),e.jsx(_,{status:l.status})]},l.id))})]})]})]})}function q(t){var s;const a=t.events.find(n=>n.event_type==="workflow_execution_started"),r=(s=a==null?void 0:a.attributes)==null?void 0:s.input;return r&&typeof r=="object"?r:null}function Z({execution:t}){const a=q(t),r=t.result,s=(r==null?void 0:r.data)??r??null;return!a&&!s?null:e.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 mb-6",children:[a!==null&&e.jsx("div",{children:e.jsx(N,{data:a,label:"Input Envelope"})}),s!==null&&e.jsx("div",{children:e.jsx(N,{data:s,label:"Result"})})]})}function G({isRunning:t,hasToolCalls:a,workflowId:r,onAction:s}){const[n,o]=b.useState(!1),m=b.useRef(null);return b.useEffect(()=>{if(!n)return;const l=p=>{m.current&&!m.current.contains(p.target)&&o(!1)};return document.addEventListener("mousedown",l),()=>document.removeEventListener("mousedown",l)},[n]),e.jsxs("div",{className:"relative",ref:m,children:[e.jsx("button",{onClick:()=>o(!n),className:"btn-primary text-xs",children:"Actions"}),n&&e.jsxs("div",{className:"absolute right-0 mt-1 w-44 bg-surface-raised border border-surface-border rounded-md shadow-lg z-10",children:[e.jsx("button",{onClick:()=>{s("restart"),o(!1)},className:"block w-full text-left px-4 py-2 text-xs text-text-secondary hover:bg-surface-hover",children:"Restart Workflow"}),t&&e.jsx("button",{onClick:()=>{s("terminate"),o(!1)},className:"block w-full text-left px-4 py-2 text-xs text-status-error hover:bg-surface-hover",children:"Terminate"}),a&&e.jsx(u,{to:`/mcp/queries/${r}?step=3`,className:"block w-full text-left px-4 py-2 text-xs text-accent hover:bg-surface-hover",onClick:()=>o(!1),children:"Compile into Pipeline"})]})]})}function me(){const{workflowId:t}=H(),{pathname:a}=J();R(t);const r=(a.startsWith("/workflows/durable/"),"Durable Execution"),{data:s,isLoading:n,error:o,refetch:m,isFetching:l}=$(t),{data:p}=D(t),{data:c}=K(t),{data:f}=V(t),T=M(),h=L(),{isCollapsed:w,toggle:j}=P("workflow-execution"),E=d=>{var i;if(d==="terminate")confirm("Are you sure you want to terminate this workflow?")&&h.mutate(t);else if(d==="restart"&&s){const I=s.workflow_id.replace(/-[A-Za-z0-9_-]{20,}$/,""),v=s.events.find(S=>S.event_type==="workflow_execution_started"),y=(i=v==null?void 0:v.attributes)==null?void 0:i.input;y&&sessionStorage.setItem("lt:invoke:prefill",JSON.stringify(y)),T(`/workflows/start?type=${encodeURIComponent(I)}&mode=now`)}};if(n)return e.jsxs("div",{className:"animate-pulse space-y-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-60 bg-surface-sunken rounded"})]});if(o||!s){const d=(o==null?void 0:o.message)??"",i=d.includes("expired")||d.includes("no longer available");return e.jsxs("div",{children:[e.jsx(u,{to:"/workflows/executions",className:"text-xs text-text-tertiary hover:text-text-primary",children:"← Workflows"}),e.jsxs("div",{className:"mt-4 text-center py-8",children:[e.jsx("p",{className:"text-sm text-text-primary mb-1",children:i?"Execution data is no longer available":o?"Unable to load execution":"Execution not found"}),e.jsx("p",{className:"text-xs text-text-tertiary",children:i?"This workflow's underlying job has expired. The task record is preserved, but the execution timeline has been cleaned up.":d||"The workflow could not be resolved."})]})]})}const C=s.status!=="completed"&&s.status!=="failed",W=s.status==="completed"&&s.events.some(d=>{if(d.event_type!=="activity_task_completed")return!1;const i=d.attributes.activity_type;return i==="callDbTool"||i==="callVisionTool"||i==="callMcpTool"||(i==null?void 0:i.startsWith("mcp_"))});return e.jsxs("div",{children:[e.jsx(z,{title:r,actions:e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(B,{onRefresh:()=>m(),isFetching:l,apiPath:`/workflow-states/${t}/execution`}),e.jsx(_,{status:s.status}),e.jsx(G,{isRunning:C,hasToolCalls:W,workflowId:t,onAction:E})]})}),e.jsx(U,{execution:s,task:p,childTasks:c==null?void 0:c.tasks,escalations:f==null?void 0:f.escalations}),h.error&&e.jsx("div",{className:"py-3 mb-6",children:e.jsxs("p",{className:"text-xs text-status-error",children:["Terminate failed: ",h.error.message]})}),e.jsxs("div",{className:"space-y-6",children:[e.jsx(g,{title:"Details",sectionKey:"details",isCollapsed:w("details"),onToggle:j,contentClassName:"mt-4 ml-9",children:e.jsx(Z,{execution:s})}),e.jsx(g,{title:"Execution Timeline",sectionKey:"timeline",isCollapsed:w("timeline"),onToggle:j,contentClassName:"mt-4 ml-9",children:e.jsx(O,{events:s.events,childTasks:c==null?void 0:c.tasks})}),e.jsx(g,{title:"Events",sectionKey:"events",isCollapsed:w("events"),onToggle:j,contentClassName:"mt-4 ml-9",children:e.jsx(A,{events:s.events,childTasks:c==null?void 0:c.tasks})})]})]})}export{me as WorkflowExecutionPage};
|
|
2
|
-
//# sourceMappingURL=WorkflowExecutionPage-CVGztAdK.js.map
|