@hotmeshio/long-tail 0.9.6 → 0.10.1
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/README.md +22 -93
- package/build/api/escalations/helpers.d.ts +27 -0
- package/build/api/escalations/helpers.js +34 -0
- package/build/api/escalations/list.d.ts +2 -0
- package/build/api/escalations/list.js +13 -3
- package/build/api/escalations/resolve.js +16 -4
- package/build/routes/escalations/list.js +2 -0
- package/build/services/escalation/crud.d.ts +1 -1
- package/build/services/escalation/crud.js +6 -2
- package/build/services/escalation/facet-sql.d.ts +8 -1
- package/build/services/escalation/facet-sql.js +29 -1
- package/build/types/facets.d.ts +9 -0
- package/dashboard/dist/assets/{AdminDashboard-DXFIKXO3.js → AdminDashboard-D7Amh-EJ.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-DXFIKXO3.js.map → AdminDashboard-D7Amh-EJ.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-CQE1mfjk.js → AgentConfigPage-CW2udbSO.js} +7 -7
- package/dashboard/dist/assets/{AgentConfigPage-CQE1mfjk.js.map → AgentConfigPage-CW2udbSO.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-DM59ozP_.js → AgentDetailPage-Dy3Qvlfk.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-DM59ozP_.js.map → AgentDetailPage-Dy3Qvlfk.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-DSK2uXI5.js → AgentsPage-D7WePZ0o.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DSK2uXI5.js.map → AgentsPage-D7WePZ0o.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-Dx51E6s_.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-Dx51E6s_.js.map +1 -0
- package/dashboard/dist/assets/BotPicker-KebTxTDi.js +2 -0
- package/dashboard/dist/assets/BotPicker-KebTxTDi.js.map +1 -0
- package/dashboard/dist/assets/CapabilitiesPage-3VTLEPGO.js +2 -0
- package/dashboard/dist/assets/{CapabilitiesPage-DFQryGBj.js.map → CapabilitiesPage-3VTLEPGO.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-B7-d5O_x.js → CollapsibleSection-BYgeZz9M.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-B7-d5O_x.js.map → CollapsibleSection-BYgeZz9M.js.map} +1 -1
- package/dashboard/dist/assets/{ConfirmDeleteModal-D9_1b4MW.js → ConfirmDeleteModal-C0x0HcHX.js} +2 -2
- package/dashboard/dist/assets/{ConfirmDeleteModal-D9_1b4MW.js.map → ConfirmDeleteModal-C0x0HcHX.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-DFqEYmiF.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-DFqEYmiF.js.map +1 -0
- package/dashboard/dist/assets/CredentialsPage-CaH3J3SE.js +2 -0
- package/dashboard/dist/assets/{CredentialsPage-BWVgxRpZ.js.map → CredentialsPage-CaH3J3SE.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DNnyNqXH.js → CronLabel-Bma_4AC2.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DNnyNqXH.js.map → CronLabel-Bma_4AC2.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-SNsQZL_U.js → CustomDurationPicker-BxyjEX27.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-SNsQZL_U.js.map → CustomDurationPicker-BxyjEX27.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-C1TpkVQG.js → DropZone-zxH-9B8c.js} +2 -2
- package/dashboard/dist/assets/{DropZone-C1TpkVQG.js.map → DropZone-zxH-9B8c.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-C4r0IvUt.js → ElapsedCell-Dr1cEwoi.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-C4r0IvUt.js.map → ElapsedCell-Dr1cEwoi.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-D8Ec8Zhj.js → EscalationListSchemaPage-CLdsR-hK.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-D8Ec8Zhj.js.map → EscalationListSchemaPage-CLdsR-hK.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-BmEv7qU2.js → EscalationSchemaPage-DnEnvolM.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-BmEv7qU2.js.map → EscalationSchemaPage-DnEnvolM.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-DkIy025e.js → EscalationsOverview-BH8cxgLI.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-DkIy025e.js.map → EscalationsOverview-BH8cxgLI.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-Co4RYeKO.js → EventTable-p-xsfYYg.js} +2 -2
- package/dashboard/dist/assets/{EventTable-Co4RYeKO.js.map → EventTable-p-xsfYYg.js.map} +1 -1
- package/dashboard/dist/assets/FilterBar-B5Vk5tBF.js +2 -0
- package/dashboard/dist/assets/FilterBar-B5Vk5tBF.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-b7ukMy0z.js → GraphInvokePage-Cf9c_LJ-.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-b7ukMy0z.js.map → GraphInvokePage-Cf9c_LJ-.js.map} +1 -1
- package/dashboard/dist/assets/HomePage-BdCtKXAV.js +2 -0
- package/dashboard/dist/assets/HomePage-BdCtKXAV.js.map +1 -0
- package/dashboard/dist/assets/ListToolbar-CDlol8XJ.js +2 -0
- package/dashboard/dist/assets/{ListToolbar-DDG6DXVF.js.map → ListToolbar-CDlol8XJ.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-P2o3b3mi.js → McpOverview-ChZ36p9t.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-P2o3b3mi.js.map → McpOverview-ChZ36p9t.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-C2CeBLb0.js → McpQueryDetailPage-X4j_jm3r.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-C2CeBLb0.js.map → McpQueryDetailPage-X4j_jm3r.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-D_N-3lp0.js → McpQueryPage-C30HsQfZ.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-D_N-3lp0.js.map → McpQueryPage-C30HsQfZ.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-uDw3MWR_.js → McpRunDetailPage-18d7Iqfy.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-uDw3MWR_.js.map → McpRunDetailPage-18d7Iqfy.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CDTUxghb.js → McpRunsPage-BHZHCwMM.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CDTUxghb.js.map → McpRunsPage-BHZHCwMM.js.map} +1 -1
- package/dashboard/dist/assets/Modal-D4afnAjc.js +2 -0
- package/dashboard/dist/assets/Modal-D4afnAjc.js.map +1 -0
- package/dashboard/dist/assets/{NamespacePill--ikKtMvx.js → NamespacePill-BQNMfY0m.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill--ikKtMvx.js.map → NamespacePill-BQNMfY0m.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-GMojwL3q.js +2 -0
- package/dashboard/dist/assets/OperationsPage-GMojwL3q.js.map +1 -0
- package/dashboard/dist/assets/OperatorDashboard-BGJrxLMU.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-CVRD8nQH.js.map → OperatorDashboard-BGJrxLMU.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-CiFhzGc0.js → PageHeader-BIY1YvKR.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-CiFhzGc0.js.map → PageHeader-BIY1YvKR.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-_zOnWAhq.js → PageHeaderWithStats-TUyglVW0.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-_zOnWAhq.js.map → PageHeaderWithStats-TUyglVW0.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-Dt6PAK04.js → ProcessDetailPage-Bcp3GKFo.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-Dt6PAK04.js.map → ProcessDetailPage-Bcp3GKFo.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-CMkjASgs.js → ProcessesListPage-DPrvuLoU.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-CMkjASgs.js.map → ProcessesListPage-DPrvuLoU.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-Ws3umQKQ.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-Ws3umQKQ.js.map +1 -0
- package/dashboard/dist/assets/RolePill-4LO_CuO3.js +2 -0
- package/dashboard/dist/assets/RolePill-4LO_CuO3.js.map +1 -0
- package/dashboard/dist/assets/RolesPage-D3NNU92h.js +2 -0
- package/dashboard/dist/assets/{RolesPage-DtHIjDss.js.map → RolesPage-D3NNU92h.js.map} +1 -1
- package/dashboard/dist/assets/RunAsSelector-Z2Yunqdf.js +2 -0
- package/dashboard/dist/assets/{RunAsSelector-C198CpFs.js.map → RunAsSelector-Z2Yunqdf.js.map} +1 -1
- package/dashboard/dist/assets/{SlidePanel-BcWKelpn.js → SlidePanel-DK4sWXmp.js} +2 -2
- package/dashboard/dist/assets/{SlidePanel-BcWKelpn.js.map → SlidePanel-DK4sWXmp.js.map} +1 -1
- package/dashboard/dist/assets/StickyPagination-CeLsnCIS.js +2 -0
- package/dashboard/dist/assets/{StickyPagination-eZ6WhMrL.js.map → StickyPagination-CeLsnCIS.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-7xq_dddO.js → StreamMessageDetail-DWs7XkmH.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-7xq_dddO.js.map → StreamMessageDetail-DWs7XkmH.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-Yg5QT0nI.js → SwimlaneTimeline-Dxl9a51q.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-Yg5QT0nI.js.map → SwimlaneTimeline-Dxl9a51q.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-BaFJCq9A.js → TagInput-CK-JhBG2.js} +2 -2
- package/dashboard/dist/assets/{TagInput-BaFJCq9A.js.map → TagInput-CK-JhBG2.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-Wc3AFnqs.js → TaskDetailPage-DVr31zDE.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-Wc3AFnqs.js.map → TaskDetailPage-DVr31zDE.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-D9e16qkU.js → TaskQueuePill-C_Vo2Clc.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-D9e16qkU.js.map → TaskQueuePill-C_Vo2Clc.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-V-1J_B_L.js → TasksListPage-DNGp5lfi.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-V-1J_B_L.js.map → TasksListPage-DNGp5lfi.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-XdHghs5F.js → TimeAgo-j2Q4w4lY.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-XdHghs5F.js.map → TimeAgo-j2Q4w4lY.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-BrzctPWT.js → TimestampCell-BLEfob2C.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-BrzctPWT.js.map → TimestampCell-BLEfob2C.js.map} +1 -1
- package/dashboard/dist/assets/ToolPill-BCi101Jh.js +2 -0
- package/dashboard/dist/assets/{ToolPill-Enc3nyic.js.map → ToolPill-BCi101Jh.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-CGfGUsaq.js → ToolTestPanel-ZJZT_mDP.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-CGfGUsaq.js.map → ToolTestPanel-ZJZT_mDP.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-CcAgD2k5.js → TopicDetailPage-ynUkO1A8.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-CcAgD2k5.js.map → TopicDetailPage-ynUkO1A8.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-BOQG7s4P.js → TopicsPage-Ctb_85Tw.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-BOQG7s4P.js.map → TopicsPage-Ctb_85Tw.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-BzxR0Ihp.js → UserName-CkFpUqVK.js} +2 -2
- package/dashboard/dist/assets/{UserName-BzxR0Ihp.js.map → UserName-CkFpUqVK.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-hRsDMByj.js → WorkflowExecutionPage-CSw2aQJC.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-hRsDMByj.js.map → WorkflowExecutionPage-CSw2aQJC.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowPill-DHxrAkp3.js +2 -0
- package/dashboard/dist/assets/{WorkflowPill-S6VV2Eax.js.map → WorkflowPill-DHxrAkp3.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-ky0ulsUe.js → WorkflowsDashboard-DOyZbqzf.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-ky0ulsUe.js.map → WorkflowsDashboard-DOyZbqzf.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-CzRJUV58.js → WorkflowsOverview-CRyHvUBx.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CzRJUV58.js.map → WorkflowsOverview-CRyHvUBx.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Beqzy9Ph.js → YamlWorkflowDetailPage-DRBmQXY6.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Beqzy9Ph.js.map → YamlWorkflowDetailPage-DRBmQXY6.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-BzBUU2ug.js → YamlWorkflowsPage-HnI-9eBW.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-BzBUU2ug.js.map → YamlWorkflowsPage-HnI-9eBW.js.map} +1 -1
- package/dashboard/dist/assets/{agents-Dlu4m6jy.js → agents-BA2CvQO9.js} +2 -2
- package/dashboard/dist/assets/{agents-Dlu4m6jy.js.map → agents-BA2CvQO9.js.map} +1 -1
- package/dashboard/dist/assets/{bots-C4G5U-6Q.js → bots-DHHnoo8x.js} +2 -2
- package/dashboard/dist/assets/{bots-C4G5U-6Q.js.map → bots-DHHnoo8x.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-BSFP_boq.js → capabilities-BYBKLRRI.js} +2 -2
- package/dashboard/dist/assets/{capabilities-BSFP_boq.js.map → capabilities-BYBKLRRI.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-qCqoP2ZV.js → controlplane-H0Ljhb-1.js} +2 -2
- package/dashboard/dist/assets/{controlplane-qCqoP2ZV.js.map → controlplane-H0Ljhb-1.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-BV3Ztn7D.js +2 -0
- package/dashboard/dist/assets/escalation-columns-BV3Ztn7D.js.map +1 -0
- package/dashboard/dist/assets/index-B4FaugP1.js +2 -0
- package/dashboard/dist/assets/{index-BtEWQNcP.js.map → index-B4FaugP1.js.map} +1 -1
- package/dashboard/dist/assets/{index-D_FNaSoe.js → index-BI-9M7z3.js} +2 -2
- package/dashboard/dist/assets/{index-D_FNaSoe.js.map → index-BI-9M7z3.js.map} +1 -1
- package/dashboard/dist/assets/{index-CuC_mNNA.js → index-BXrm76Hm.js} +2 -2
- package/dashboard/dist/assets/{index-CuC_mNNA.js.map → index-BXrm76Hm.js.map} +1 -1
- package/dashboard/dist/assets/index-BeFjLCIQ.js +5 -0
- package/dashboard/dist/assets/index-BeFjLCIQ.js.map +1 -0
- package/dashboard/dist/assets/index-BrEcQQ1p.css +1 -0
- package/dashboard/dist/assets/{index-BObWYPxd.js → index-CP6D_Gtw.js} +2 -2
- package/dashboard/dist/assets/{index-BObWYPxd.js.map → index-CP6D_Gtw.js.map} +1 -1
- package/dashboard/dist/assets/index-CVMA5PVQ.js +2 -0
- package/dashboard/dist/assets/{index-C0KklOM4.js.map → index-CVMA5PVQ.js.map} +1 -1
- package/dashboard/dist/assets/{index-CGVqW6MI.js → index-CWkAN6wc.js} +2 -2
- package/dashboard/dist/assets/{index-CGVqW6MI.js.map → index-CWkAN6wc.js.map} +1 -1
- package/dashboard/dist/assets/index-D1Q8WfGN.js +2 -0
- package/dashboard/dist/assets/{index-QL26JxBF.js.map → index-D1Q8WfGN.js.map} +1 -1
- package/dashboard/dist/assets/{index-DUhettdV.js → index-DSjih1iX.js} +2 -2
- package/dashboard/dist/assets/{index-DUhettdV.js.map → index-DSjih1iX.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dzhn5MDY.js → index-Dx6-4esw.js} +2 -2
- package/dashboard/dist/assets/{index-Dzhn5MDY.js.map → index-Dx6-4esw.js.map} +1 -1
- package/dashboard/dist/assets/index-O2wWcCLo.js +5 -0
- package/dashboard/dist/assets/{index-DWSNtg28.js.map → index-O2wWcCLo.js.map} +1 -1
- package/dashboard/dist/assets/{index-DCTWUswT.js → index-YWvrMcqh.js} +4 -4
- package/dashboard/dist/assets/index-YWvrMcqh.js.map +1 -0
- package/dashboard/dist/assets/index-iX8HLLB9.js +63 -0
- package/dashboard/dist/assets/index-iX8HLLB9.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-CTdgLX5j.js → knowledge-BVbElRHf.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CTdgLX5j.js.map → knowledge-BVbElRHf.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-BEsMr678.js → mcp-CstDdowJ.js} +2 -2
- package/dashboard/dist/assets/{mcp-BEsMr678.js.map → mcp-CstDdowJ.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CmamHKIR.js → mcp-query-DHUs0N8H.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CmamHKIR.js.map → mcp-query-DHUs0N8H.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-taOXA2-y.js → pipelines-BDmAzE3U.js} +2 -2
- package/dashboard/dist/assets/{pipelines-taOXA2-y.js.map → pipelines-BDmAzE3U.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-DEaDdyBh.js → tasks--qZocuG-.js} +2 -2
- package/dashboard/dist/assets/{tasks-DEaDdyBh.js.map → tasks--qZocuG-.js.map} +1 -1
- package/dashboard/dist/assets/{topics-2qRx68Ft.js → topics-P3jkjMsx.js} +2 -2
- package/dashboard/dist/assets/{topics-2qRx68Ft.js.map → topics-P3jkjMsx.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-oXjfKrbI.js → useEventHooks-CqH8Pasj.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-oXjfKrbI.js.map → useEventHooks-CqH8Pasj.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-YdT1Zouk.js → useNamespace-aI3tPfo3.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-YdT1Zouk.js.map → useNamespace-aI3tPfo3.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-Bt-RZ7Zl.js → useYamlActivityEvents-CPVyuW8i.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-Bt-RZ7Zl.js.map → useYamlActivityEvents-CPVyuW8i.js.map} +1 -1
- package/dashboard/dist/assets/{users-DbX5JZ_w.js → users-BrcmIH7O.js} +2 -2
- package/dashboard/dist/assets/{users-DbX5JZ_w.js.map → users-BrcmIH7O.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-DPmIjhZB.js → vendor-icons-Qk9qLjF_.js} +131 -126
- package/dashboard/dist/assets/vendor-icons-Qk9qLjF_.js.map +1 -0
- package/dashboard/dist/assets/{workflows-acMcPTC5.js → workflows-DPhX5L5C.js} +2 -2
- package/dashboard/dist/assets/{workflows-acMcPTC5.js.map → workflows-DPhX5L5C.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-D6kzrMYx.js → yaml-workflows-KZI2cufF.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-D6kzrMYx.js.map → yaml-workflows-KZI2cufF.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/dashboard.md +2 -2
- package/docs/faceted-routing.md +9 -0
- package/docs/hitl/escalation.md +206 -0
- package/docs/hitl/form.md +162 -0
- package/docs/hitl/iframe.md +170 -0
- package/docs/hitl/resolution.md +166 -0
- package/docs/hitl/roles.md +72 -0
- package/docs/hitl/x-lt-layout.md +167 -0
- package/docs/hitl/x-lt-list-schema.md +75 -0
- package/docs/hitl/x-lt-show-if.md +128 -0
- package/docs/hitl/x-lt-validation.md +195 -0
- package/docs/hitl/x-lt-widget.md +162 -0
- package/docs/hitl-guide.md +84 -1315
- package/docs/iam.md +1 -1
- package/package.json +2 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-BVgMtftp.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-BVgMtftp.js.map +0 -1
- package/dashboard/dist/assets/BotPicker-DJ0YYKZA.js +0 -2
- package/dashboard/dist/assets/BotPicker-DJ0YYKZA.js.map +0 -1
- package/dashboard/dist/assets/CapabilitiesPage-DFQryGBj.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-BTJmh4Xw.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-BTJmh4Xw.js.map +0 -1
- package/dashboard/dist/assets/CredentialsPage-BWVgxRpZ.js +0 -2
- package/dashboard/dist/assets/FilterBar-DDIn8TOs.js +0 -2
- package/dashboard/dist/assets/FilterBar-DDIn8TOs.js.map +0 -1
- package/dashboard/dist/assets/HomePage-K0U3yMdU.js +0 -2
- package/dashboard/dist/assets/HomePage-K0U3yMdU.js.map +0 -1
- package/dashboard/dist/assets/ListToolbar-DDG6DXVF.js +0 -2
- package/dashboard/dist/assets/Modal-CSrxpXeM.js +0 -2
- package/dashboard/dist/assets/Modal-CSrxpXeM.js.map +0 -1
- package/dashboard/dist/assets/OperationsPage-Daah4pNk.js +0 -2
- package/dashboard/dist/assets/OperationsPage-Daah4pNk.js.map +0 -1
- package/dashboard/dist/assets/OperatorDashboard-CVRD8nQH.js +0 -2
- package/dashboard/dist/assets/RoleDetailPage-6H1AY6sK.js +0 -8
- package/dashboard/dist/assets/RoleDetailPage-6H1AY6sK.js.map +0 -1
- package/dashboard/dist/assets/RolePill-CUIJSgRq.js +0 -2
- package/dashboard/dist/assets/RolePill-CUIJSgRq.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-DtHIjDss.js +0 -2
- package/dashboard/dist/assets/RunAsSelector-C198CpFs.js +0 -2
- package/dashboard/dist/assets/StickyPagination-eZ6WhMrL.js +0 -2
- package/dashboard/dist/assets/ToolPill-Enc3nyic.js +0 -2
- package/dashboard/dist/assets/WorkflowPill-S6VV2Eax.js +0 -2
- package/dashboard/dist/assets/escalation-columns-Ck7ZOd7Z.js +0 -2
- package/dashboard/dist/assets/escalation-columns-Ck7ZOd7Z.js.map +0 -1
- package/dashboard/dist/assets/index-BtEWQNcP.js +0 -2
- package/dashboard/dist/assets/index-C0KklOM4.js +0 -2
- package/dashboard/dist/assets/index-CY3hpJR-.js +0 -5
- package/dashboard/dist/assets/index-CY3hpJR-.js.map +0 -1
- package/dashboard/dist/assets/index-DCTWUswT.js.map +0 -1
- package/dashboard/dist/assets/index-DWSNtg28.js +0 -5
- package/dashboard/dist/assets/index-DcQiD6OF.js +0 -63
- package/dashboard/dist/assets/index-DcQiD6OF.js.map +0 -1
- package/dashboard/dist/assets/index-QL26JxBF.js +0 -2
- package/dashboard/dist/assets/index-waKF7bKZ.css +0 -1
- package/dashboard/dist/assets/roles-BiUJAsVM.js +0 -2
- package/dashboard/dist/assets/roles-BiUJAsVM.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-DPmIjhZB.js.map +0 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Resolution
|
|
2
|
+
|
|
3
|
+
## Claim Lifecycle
|
|
4
|
+
|
|
5
|
+
A claim is a **work lock**: `assigned_to` names the holder and `assigned_until` bounds the window. The lock exists only while the window is active — this is what `isEffectivelyClaimed` means throughout the platform. Two related shapes use the same columns without being locks:
|
|
6
|
+
|
|
7
|
+
- **Unclaimed** (`assigned_to` null) — available to the whole role queue, and resolvable by system code.
|
|
8
|
+
- **Durable pre-assignment** (`assigned_to` set, no window) — a workflow targeting a named user (the one-time-user JIT-form shape, see [roles.md](roles.md#one-time-and-pre-assigned-escalations)). Routing, not a lock.
|
|
9
|
+
|
|
10
|
+
### Claiming and extending
|
|
11
|
+
|
|
12
|
+
Claiming takes a duration (configurable options via `LT_CLAIM_DURATION_OPTIONS`; a custom duration is always available). Re-claiming a row you already hold extends the window — the claim endpoint is idempotent per assignee and reports `isExtension: true`.
|
|
13
|
+
|
|
14
|
+
In the dashboard, 90 seconds before the window lapses a **Claim Expiring** dialog offers the same duration options to extend. Dismissing it lets the window run out: at expiry the form locks, the action bar returns to its claim state, and the item is available to the queue again. Typed input survives — see draft persistence in [form.md](form.md#draft-persistence) — so re-claiming picks up exactly where the resolver left off.
|
|
15
|
+
|
|
16
|
+
### The claim-liveness rule
|
|
17
|
+
|
|
18
|
+
A resolve presented **by escalation id** must not act against a claim lock. The rule blocks exactly two states:
|
|
19
|
+
|
|
20
|
+
| Row state | Resolve by id |
|
|
21
|
+
|-----------|---------------|
|
|
22
|
+
| Unclaimed | Allowed (system resolvers act on unclaimed rows by design) |
|
|
23
|
+
| Durable pre-assignment (no window) | Allowed |
|
|
24
|
+
| Live window, held by the caller | Allowed |
|
|
25
|
+
| Live window, held by someone else | **409** — the lock is theirs |
|
|
26
|
+
| Caller's own window, lapsed | **409** — stale work; re-claim to resolve |
|
|
27
|
+
| Someone else's window, lapsed | Allowed — the lock is gone, the row is back in the pool |
|
|
28
|
+
|
|
29
|
+
The rule applies to every principal, including superadmins: the claim is a work lock, not an authorization scope. It is enforced twice — an advisory check before any resolution side effects fire, and atomically inside the SDK's guarded resolve UPDATE (`assertClaim`), so a claim that lapses mid-request still cannot land a stale resolution.
|
|
30
|
+
|
|
31
|
+
`resolveBySignalKey` and `resolveByMetadata` are claim-agnostic: they are system ingress surfaces (webhooks, domain events) that resolve on behalf of the process, not a claimant.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Resolving from System Code
|
|
36
|
+
|
|
37
|
+
When a backend service (not the dashboard UI) needs to resolve an escalation — for example, an ingress handler that receives a webhook or processes a domain event — use the escalation SDK methods directly.
|
|
38
|
+
|
|
39
|
+
### By escalation ID
|
|
40
|
+
|
|
41
|
+
Use when you already have the escalation UUID (e.g. stored in your own DB alongside the order):
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const result = await lt.escalations.resolve({
|
|
45
|
+
id: escalationId,
|
|
46
|
+
resolverPayload: { approved: true, targetStatus: 'ready' },
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This routes through the full resolution path for all escalation types.
|
|
51
|
+
|
|
52
|
+
### By metadata key-value pair
|
|
53
|
+
|
|
54
|
+
Use when you know a domain identifier (e.g. `orderId`) but not the escalation UUID. `resolveByMetadata` finds the highest-priority pending escalation matching the key-value pair and resolves it atomically — no pre-flight lookup, no TOCTOU:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const result = await lt.escalations.resolveByMetadata({
|
|
58
|
+
key: 'orderId',
|
|
59
|
+
value: orderId,
|
|
60
|
+
resolverPayload: { approved: true, targetStatus: 'ready' },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.status === 404) {
|
|
64
|
+
// No pending escalation for this orderId
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### By signal key
|
|
69
|
+
|
|
70
|
+
When the signal key is deterministic and known to the caller (e.g. `station-done-${workflowId}`), use the signal-key path to skip the metadata lookup:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
await lt.escalations.resolveBySignalKey({
|
|
74
|
+
signalKey: `station-done-${workflowId}`,
|
|
75
|
+
resolverPayload: { approved: true },
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Resolving a set atomically
|
|
80
|
+
|
|
81
|
+
When one decision settles a set of waits — each with its own payload — use `resolveAllOrNone`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
await lt.escalations.resolveAllOrNone({ items: [
|
|
85
|
+
{ id: 'esc-aaa', resolverPayload: { decision: 'approve' } },
|
|
86
|
+
{ id: 'esc-bbb', resolverPayload: { decision: 'reject' } },
|
|
87
|
+
] });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Every listed row resolves with its own payload in one SQL statement, waking each parked workflow with its own value — or nothing resolves. A 409 names exactly the rows that blocked (`failedIds` + reasons). Pass `requireClaimed: true` to assert, inside the same statement, that every row is still assigned to the caller. See the [SDK reference](../api/sdk/escalations.md#resolveallornone) for the full contract.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Recording the Outcome
|
|
95
|
+
|
|
96
|
+
An escalation row carries **intent** — what was asked, who it routed to. Resolving it stamps the **outcome** onto the same row: every resolve surface takes an optional `metadata` patch merged into the row's GIN-indexed metadata.
|
|
97
|
+
|
|
98
|
+
| Surface | How to pass it |
|
|
99
|
+
|---------|----------------|
|
|
100
|
+
| HTTP | `metadata` in the resolve body (`POST /api/escalations/:id/resolve`) |
|
|
101
|
+
| SDK facade | `lt.escalations.resolve({ id, resolverPayload, metadata })` |
|
|
102
|
+
| MCP | `metadata` arg on `claim_and_resolve` / `resolve_escalation` |
|
|
103
|
+
| In-process library | `resolveEscalation(id, payload, metadata)` |
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
await lt.escalations.resolve({
|
|
107
|
+
id,
|
|
108
|
+
resolverPayload: { approved: true }, // resumes the workflow; not indexed
|
|
109
|
+
metadata: { outcome: 'approved', reviewedBy: 'alice', durationMs: elapsed },
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The metadata patch is the durable, queryable record on the row. The resolver payload resumes the paused workflow and is not indexed separately. Use the metadata patch for audit trail and analytics — disposition, reviewer, time-to-resolve — so the escalation table answers *what was asked, what was decided, and how long it took* without a parallel log.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Cancelling Escalations
|
|
118
|
+
|
|
119
|
+
Cancellation is terminal — a cancelled escalation cannot be re-opened.
|
|
120
|
+
|
|
121
|
+
### When cancellation happens
|
|
122
|
+
|
|
123
|
+
- **Workflow termination** — `POST /api/workflows/:workflowId/terminate` automatically cancels any pending escalations tied to it. The waiting `conditionLT` call returns `null`.
|
|
124
|
+
- **Explicit cancel** — cancel a single escalation via the API or from the dashboard. Any workflow waiting on that escalation via `conditionLT` receives `null`.
|
|
125
|
+
|
|
126
|
+
### API
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
POST /api/escalations/:id/cancel # single
|
|
130
|
+
POST /api/escalations/bulk-cancel # { "ids": [...] }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Returns 409 if the escalation is already resolved or cancelled.
|
|
134
|
+
|
|
135
|
+
### Dashboard
|
|
136
|
+
|
|
137
|
+
- **Escalations list** — select one or more rows and click **Cancel** in the bulk action bar.
|
|
138
|
+
- **Escalation detail page** — a Cancel link appears in the action bar when the escalation is in `available` or `claimed_by_me` state.
|
|
139
|
+
|
|
140
|
+
### Handling cancellation in workflows
|
|
141
|
+
|
|
142
|
+
`conditionLT` returns `T | false | null`. Always guard before accessing the payload:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
const decision = await conditionLT<{ approved: boolean }>(signalId, escalationConfig);
|
|
146
|
+
|
|
147
|
+
if (decision === null) {
|
|
148
|
+
// Escalation was cancelled
|
|
149
|
+
return { type: 'return' as const, data: { cancelled: true } };
|
|
150
|
+
}
|
|
151
|
+
if (decision === false) {
|
|
152
|
+
// Escalation timed out (SLA)
|
|
153
|
+
return { type: 'return' as const, data: { timedOut: true } };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Normal path — decision is the resolver's payload
|
|
157
|
+
if (decision.approved) { ... }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The `!decision` shorthand handles both cases when you don't need to distinguish between them:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
if (!decision) {
|
|
164
|
+
return { type: 'return' as const, data: { cancelled: true } };
|
|
165
|
+
}
|
|
166
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Role Routing and RBAC
|
|
2
|
+
|
|
3
|
+
## Role-Based Routing
|
|
4
|
+
|
|
5
|
+
Escalations are routed by role, and any member of the role can handle them — a person working the queue in the dashboard, or a service account claiming and resolving through the API or MCP tools. Members only see escalations for roles they hold. Set `role` on every `conditionLT` config to name the target queue:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
const decision = await conditionLT<{ approved: boolean }>(signalId, {
|
|
9
|
+
role: 'finance-reviewer', // only users with this role see it
|
|
10
|
+
description: `Approve spend of $${amount}`,
|
|
11
|
+
// ...
|
|
12
|
+
});
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Work-Surface Scope
|
|
18
|
+
|
|
19
|
+
A `member` of a role carries a work-surface scope that narrows what they see and act on within that queue:
|
|
20
|
+
|
|
21
|
+
| Scope field | Values | Meaning |
|
|
22
|
+
|-------------|--------|---------|
|
|
23
|
+
| `read_scope` | `self` / `all` | Which escalations the member sees |
|
|
24
|
+
| `write_scope` | `none` / `self` / `all` | Which escalations the member can claim, resolve, or cancel |
|
|
25
|
+
|
|
26
|
+
`self` means escalations assigned to that member (`assigned_to = user`); `all` means the whole role queue. `admin` and `superadmin` always work the whole queue.
|
|
27
|
+
|
|
28
|
+
Scope is a property of the **membership** (`lt_user_roles`), not of the escalation row. The escalation engine is unchanged — `conditionLT` and `ltCreateEscalation` write rows exactly as before. Scope is resolved at read time when a user lists or acts on the queue.
|
|
29
|
+
|
|
30
|
+
See the [Roles API](../api/http/roles.md#work-surface-scope) for the five member profiles and the assignment contract.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## One-Time and Pre-Assigned Escalations
|
|
35
|
+
|
|
36
|
+
To route a single item to a named person, assign the escalation to them and provision them with `read_scope=self` + `write_scope=self`. The workflow sets `assigned_to` to the person's user ID when it creates the escalation, then provisions or updates that user as a `member` with self/self scope on the target role. They land directly on that one item — a just-in-time form scoped by RBAC — with no access to the rest of the queue.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const decision = await conditionLT<{ confirmed: boolean; address: string }>(signalId, {
|
|
40
|
+
role: 'customer-triage',
|
|
41
|
+
assigned_to: userId, // pre-claim
|
|
42
|
+
description: 'Confirm your shipping address',
|
|
43
|
+
metadata: {
|
|
44
|
+
form_schema: {
|
|
45
|
+
title: 'Confirm Address',
|
|
46
|
+
properties: {
|
|
47
|
+
address: { type: 'string' },
|
|
48
|
+
confirmed: { type: 'boolean' },
|
|
49
|
+
},
|
|
50
|
+
required: ['confirmed'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Escalation Chains
|
|
59
|
+
|
|
60
|
+
Users can escalate to other roles via the "Escalate" tab on the detail page. Configure chain targets in Admin > Roles. Each role lists which other roles it can escalate to. Common shape:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Analyst → Senior Analyst → Manager → VP
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Chains are runtime RBAC — they control who can receive an escalation handed off from this role. `parent_role` and `ops_visible` are the process graph Operations renders.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Schema Versioning
|
|
71
|
+
|
|
72
|
+
See [escalation.md](escalation.md#versioned-role-schemas) for the full versioning contract — how to pin a schema version on an escalation row, how to inspect versions, and how to save a new version.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Layout, Ordering, Sections, Binding, and Help
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Two-Column Layout (`x-lt-layout`)
|
|
6
|
+
|
|
7
|
+
Set `"x-lt-layout": "two-column"` at the schema root to arrange fields in a 2-column grid. Use `"x-lt-span": 2` on a field to span the full width.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"x-lt-layout": "two-column",
|
|
12
|
+
"properties": {
|
|
13
|
+
"first_name": { "type": "string" },
|
|
14
|
+
"last_name": { "type": "string" },
|
|
15
|
+
"notes": { "type": "string", "format": "textarea", "x-lt-span": 2 }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Field Ordering (`x-lt-order`)
|
|
23
|
+
|
|
24
|
+
By default, fields render in JSON key order. `x-lt-order` sets an explicit sequence. Fields not listed in `x-lt-order` render after the listed ones in their original key order.
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"x-lt-order": ["priority", "decision", "notes"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"notes": { "type": "string" },
|
|
31
|
+
"decision": { "type": "string", "enum": ["approve", "reject", "defer"] },
|
|
32
|
+
"priority": { "type": "number" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Section Groups (`x-lt-section`)
|
|
40
|
+
|
|
41
|
+
Group related fields under a labeled section by adding `"x-lt-section": "Label"` to each property. Fields that share the same consecutive section name are collected into one group with a header.
|
|
42
|
+
|
|
43
|
+
Sections are ordered by the first field that carries the section name (respects `x-lt-order`). A field without `x-lt-section` belongs to an unnamed group rendered without a header. Named sections render with a left accent line, a small icon, and the label in uppercase.
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"x-lt-layout": "two-column",
|
|
48
|
+
"x-lt-order": ["patient_id", "heel_cup", "pdac", "approved", "notes"],
|
|
49
|
+
"properties": {
|
|
50
|
+
"patient_id": { "type": "string", "readOnly": true, "x-lt-section": "Facts", "x-lt-hide-if-empty": true },
|
|
51
|
+
"heel_cup": { "type": "string", "readOnly": true, "x-lt-section": "Facts", "x-lt-hide-if-empty": true },
|
|
52
|
+
"pdac": { "type": "boolean","readOnly": true, "x-lt-section": "Facts", "x-lt-hide-if-empty": true },
|
|
53
|
+
"approved": { "type": "string", "enum": ["yes", "no"], "x-lt-section": "Action" },
|
|
54
|
+
"notes": { "type": "string", "format": "textarea", "x-lt-span": 2, "x-lt-section": "Action" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Payload Binding (`x-lt-bind`)
|
|
62
|
+
|
|
63
|
+
The form is flat; the payload the workflow consumes rarely is. A field may declare `x-lt-bind` — the path its value occupies in the resolver payload (dot keys, optional `[n]` indices). The dashboard maps the flat form through the binds on submit, and reverse-maps `envelope.formDefaults` through them to prefill. A field with no bind lands at its own name at the payload root.
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"properties": {
|
|
68
|
+
"customer_name": { "type": "string", "x-lt-bind": "customer.name" },
|
|
69
|
+
"contact_email": { "type": "string", "format": "email", "x-lt-bind": "customer.email" },
|
|
70
|
+
"tier": { "type": "string", "enum": ["starter", "professional"], "x-lt-bind": "contract.tier" },
|
|
71
|
+
"notes": { "type": "string", "format": "textarea" }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Submitting `{ customer_name, contact_email, tier, notes }` produces:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"customer": { "name": "…", "email": "…" },
|
|
81
|
+
"contract": { "tier": "…" },
|
|
82
|
+
"notes": "…"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Only the form is versioned on the role — the payload shape is the workflow's own contract, produced by the binds. Evolve the form and its binds together, and the workflow's resolver type in the same commit.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Help Panel (`x-lt-help`)
|
|
91
|
+
|
|
92
|
+
Schema-level `x-lt-help` carries the form's guidance — checklists, tier tables, callouts, links — as markdown. The dashboard renders it in the side panel beside the form, so the form itself stays a clean title-and-fields surface while the SOP sits one glance to the right. The help text versions with the form: it lives in the same `form_schema` snapshot in `lt_role_schemas`.
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"title": "Customer Intake",
|
|
97
|
+
"x-lt-help": "### Review checklist\n\n1. Confirm the **legal name** matches.\n2. Send a test message before approving.\n\nThis escalation is **{{escalation.status}}** in the **{{escalation.role}}** queue.",
|
|
98
|
+
"properties": { }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `{{domain.path}}` tokens
|
|
103
|
+
|
|
104
|
+
`{{domain.path}}` tokens in `x-lt-help` interpolate live values from the escalation record. Five domains are available:
|
|
105
|
+
|
|
106
|
+
| Domain | Resolves against |
|
|
107
|
+
|--------|-----------------|
|
|
108
|
+
| `escalation` | The escalation row (`{{escalation.role}}`, `{{escalation.status}}`) |
|
|
109
|
+
| `metadata` | The row's metadata dict (`{{metadata.schema_version}}`) |
|
|
110
|
+
| `envelope` | The workflow-sent input envelope (`{{envelope.min_score}}`) |
|
|
111
|
+
| `payload` | The escalation context payload (`{{payload.category}}`) |
|
|
112
|
+
| `resolver` | The submitted resolver payload (`{{resolver.notes}}`) |
|
|
113
|
+
|
|
114
|
+
A missing value renders as an em dash. Links whose `href` starts with `/` navigate inside the dashboard.
|
|
115
|
+
|
|
116
|
+
### Help fallback chain
|
|
117
|
+
|
|
118
|
+
The side panel's Help view resolves in order: `x-lt-help` → `x-lt-context` (plain text) → a state-aware hint ("Claim this escalation to enable the form", "Fill out the form and submit to resolve it", and so on). The panel always gives the resolver a prompt.
|
|
119
|
+
|
|
120
|
+
The panel's other views surface the record itself:
|
|
121
|
+
|
|
122
|
+
| View | Shows | Available to |
|
|
123
|
+
|------|-------|--------------|
|
|
124
|
+
| **Help** | `x-lt-help` markdown, or a state-aware hint | Everyone |
|
|
125
|
+
| **Details** | Status, role, priority, claim provenance, timestamps | Everyone |
|
|
126
|
+
| **AI Analysis** | Triage diagnosis and corrections | When AI is enabled and triage data is present |
|
|
127
|
+
| **Metadata** | The row's metadata values | Everyone |
|
|
128
|
+
| **Context** | Input envelope, escalation context, resolver payload | Everyone |
|
|
129
|
+
| **Errors** | Pre-submission validation failures with click-to-focus | When submit is blocked |
|
|
130
|
+
| **Record** | The raw escalation JSON | Builders (admins, superadmins, engineers) |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Complete Layout Example
|
|
135
|
+
|
|
136
|
+
Two-column intake form with sections, help panel, ordering, and bindings:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"title": "Customer Intake",
|
|
141
|
+
"description": "Complete all required fields then submit.",
|
|
142
|
+
"x-lt-layout": "two-column",
|
|
143
|
+
"x-lt-order": ["first_name", "last_name", "email", "phone", "tier", "notes"],
|
|
144
|
+
"x-lt-help": "### Intake checklist\n\n1. Confirm the **legal name** matches the ID on file.\n2. Verify the email is reachable — send a test message.\n3. Select tier based on the sales order.\n\n> Escalate non-standard contract language to legal.",
|
|
145
|
+
"required": ["first_name", "last_name", "email", "tier"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"first_name": { "type": "string", "x-lt-section": "Contact" },
|
|
148
|
+
"last_name": { "type": "string", "x-lt-section": "Contact" },
|
|
149
|
+
"email": { "type": "string", "format": "email", "x-lt-section": "Contact", "x-lt-bind": "customer.email" },
|
|
150
|
+
"phone": { "type": "string", "x-lt-section": "Contact" },
|
|
151
|
+
"tier": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"enum": ["free", "pro", "enterprise"],
|
|
154
|
+
"description": "Select the customer tier",
|
|
155
|
+
"x-lt-section": "Contract",
|
|
156
|
+
"x-lt-bind": "contract.tier"
|
|
157
|
+
},
|
|
158
|
+
"notes": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"format": "textarea",
|
|
161
|
+
"x-lt-span": 2,
|
|
162
|
+
"description": "Additional notes",
|
|
163
|
+
"x-lt-section": "Contract"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# List Schema (`x-lt-list-schema`)
|
|
2
|
+
|
|
3
|
+
The form schema formats one escalation on the detail page. A role can also own a `list_schema` that formats its whole list page — the list-page analog of the resolve form. It is opt-in and applies only when the list is scoped to exactly one role (`/escalations/available?role=<role>`). Absent, the list renders the standard engineer table; present, a rich role-authored view renders with a "Table view" toggle one click away.
|
|
4
|
+
|
|
5
|
+
The list schema is versioned independently of the form schema — a list edit never bumps the form version. Edit it at `/admin/roles/:role/list-schema`. The list always renders the latest version.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Vocabulary
|
|
10
|
+
|
|
11
|
+
Every string is a markdown/text template run through the same `{{domain.path}}` token binding as `x-lt-help` (domains: `escalation | metadata | envelope | payload | resolver`, evaluated against each row). `body` strings render through the markdown renderer.
|
|
12
|
+
|
|
13
|
+
| Key | Level | Purpose |
|
|
14
|
+
|-----|-------|---------|
|
|
15
|
+
| `x-lt-layout` | schema | `"active-history"`, `"active"`, `"facet-table"`, or `"table"` |
|
|
16
|
+
| `x-lt-help` | schema | Optional markdown header, interpolated with the active row |
|
|
17
|
+
| `x-lt-active` | schema | The live item card: `{ title, subtitle?, body?, fields?: [{ label, value }] }` |
|
|
18
|
+
| `x-lt-history` | schema | History column: `{ row: { title, subtitle?, meta? }, limit?, status? }` |
|
|
19
|
+
| `x-lt-columns` | schema | Column definitions for `facet-table` layout: `[{ label: string, value: string }]` |
|
|
20
|
+
|
|
21
|
+
The **active** item is the first non-terminal escalation. The **history** column is lazy-loaded — a "Load full history" link fetches resolved items on demand (`status` defaults to `"resolved"`, `limit` to 25). Unknown or absent `x-lt-layout` is a safe no-op that falls back to the table.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Layouts
|
|
26
|
+
|
|
27
|
+
### `"active-history"` — document workflow
|
|
28
|
+
|
|
29
|
+
Use when the queue contains exactly one live item at a time and a history column is meaningful — a policy document, a looped review, a sequential approval chain.
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"x-lt-layout": "active-history",
|
|
34
|
+
"x-lt-help": "# {{metadata.title}}\nThe authoritative policy. One revision is live at a time.",
|
|
35
|
+
"x-lt-active": {
|
|
36
|
+
"title": "{{metadata.title}}",
|
|
37
|
+
"subtitle": "Revision {{metadata.revision}} · effective {{metadata.effective_date}}",
|
|
38
|
+
"body": "{{metadata.document_markdown}}",
|
|
39
|
+
"fields": [
|
|
40
|
+
{ "label": "Owner", "value": "{{metadata.owner}}" },
|
|
41
|
+
{ "label": "Claimed by", "value": "{{escalation.assigned_to}}" }
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"x-lt-history": {
|
|
45
|
+
"row": { "title": "{{metadata.title}} — revision {{metadata.revision}}" },
|
|
46
|
+
"limit": 25
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The reference: `examples/workflows/policy-document/` (role seeded by `examples/seed-policy-document.ts`) — a looped workflow keeps exactly one escalation live and each resolution folds into the next revision.
|
|
52
|
+
|
|
53
|
+
### `"active"` — current item card only
|
|
54
|
+
|
|
55
|
+
Use when there is one live item but no history view is needed — a single-request approval or a just-in-time form.
|
|
56
|
+
|
|
57
|
+
### `"facet-table"` — scannable queue
|
|
58
|
+
|
|
59
|
+
Use when the queue contains many concurrent rows and the role's context is best expressed as a table — a print farm, order queue, or batch-processing pond. Every pending escalation is a row; columns are defined by `x-lt-columns`.
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"x-lt-layout": "facet-table",
|
|
64
|
+
"x-lt-columns": [
|
|
65
|
+
{ "label": "Patient", "value": "{{metadata.patientId}}" },
|
|
66
|
+
{ "label": "Heel cup", "value": "{{metadata.heelCup}}" },
|
|
67
|
+
{ "label": "PDAC", "value": "{{metadata.pdac}}" },
|
|
68
|
+
{ "label": "Station", "value": "{{metadata.station}}" },
|
|
69
|
+
{ "label": "Priority", "value": "{{escalation.priority}}" },
|
|
70
|
+
{ "label": "Created", "value": "{{escalation.created_at}}" }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A status dot precedes the first column automatically. ISO datetime values render as a readable relative date with a full-timestamp tooltip. Missing token values render as an em dash. Clicking any row navigates to the detail page. `x-lt-help` and `x-lt-active` are ignored in this layout.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Conditional Visibility
|
|
2
|
+
|
|
3
|
+
## `x-lt-showIf`
|
|
4
|
+
|
|
5
|
+
A field can be shown or hidden based on a value in the escalation context. Apply `x-lt-showIf` to any property:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
"x-lt-showIf": "domain.path"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The value at `domain.path` is evaluated for truthiness. If truthy, the field shows; if absent, null, false, or an empty string, it is hidden. Prefix `!` to invert — show when the value is absent or falsy.
|
|
12
|
+
|
|
13
|
+
### Domains
|
|
14
|
+
|
|
15
|
+
| Domain | Resolves against |
|
|
16
|
+
|--------|-----------------|
|
|
17
|
+
| `metadata` | The row's metadata dict |
|
|
18
|
+
| `payload` | The escalation context payload (`escalation_payload`) |
|
|
19
|
+
| `envelope` | The workflow-sent input envelope |
|
|
20
|
+
| `escalation` | Top-level escalation row fields (`role`, `status`, `priority`, …) |
|
|
21
|
+
| `resolver` | The submitted resolver payload — **live**: reacts in real time as the user edits the form |
|
|
22
|
+
|
|
23
|
+
Conditions based on `metadata`, `payload`, `envelope`, and `escalation` are static (read from the stored row). Conditions based on `resolver` are dynamic — the field appears or disappears immediately as the user edits a sibling field, without a page reload.
|
|
24
|
+
|
|
25
|
+
### Item type branching
|
|
26
|
+
|
|
27
|
+
A queue that receives both regular items and a special signal type. The payload carries `crew_pill` to distinguish them:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"title": "Worker Station",
|
|
32
|
+
"properties": {
|
|
33
|
+
"action_taken": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": ["completed", "deferred", "escalated"],
|
|
36
|
+
"description": "Outcome for this work item",
|
|
37
|
+
"x-lt-showIf": "!payload.crew_pill"
|
|
38
|
+
},
|
|
39
|
+
"notes": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"format": "textarea",
|
|
42
|
+
"x-lt-showIf": "!payload.crew_pill"
|
|
43
|
+
},
|
|
44
|
+
"shutdown_ack": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"title": "Acknowledge shutdown",
|
|
47
|
+
"description": "Confirm you are stopping work and clearing the station",
|
|
48
|
+
"x-lt-showIf": "payload.crew_pill"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Approval/rejection — live conditional required
|
|
55
|
+
|
|
56
|
+
`rejection_reason` is hidden when `approved` is checked. Because the condition targets `resolver.approved`, the field appears immediately when the user unchecks the toggle — no submit needed. The `required` array marks `rejection_reason` as required — the required check is skipped for fields hidden by `x-lt-showIf`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"title": "Quality Review",
|
|
61
|
+
"x-lt-order": ["approved", "rejection_reason", "notes"],
|
|
62
|
+
"required": ["rejection_reason"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"approved": {
|
|
65
|
+
"type": "boolean",
|
|
66
|
+
"description": "Approve this submission"
|
|
67
|
+
},
|
|
68
|
+
"rejection_reason": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"format": "textarea",
|
|
71
|
+
"description": "Explain why this submission is not approved",
|
|
72
|
+
"x-lt-showIf": "!resolver.approved"
|
|
73
|
+
},
|
|
74
|
+
"notes": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"format": "textarea",
|
|
77
|
+
"description": "Reviewer notes"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
In the workflow, guard the payload fields before accessing them:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const decision = await conditionLT<{
|
|
87
|
+
approved: boolean;
|
|
88
|
+
rejection_reason?: string;
|
|
89
|
+
notes?: string;
|
|
90
|
+
}>(signalId, { role: 'quality-reviewer', /* ... */ });
|
|
91
|
+
|
|
92
|
+
if (!decision.approved) {
|
|
93
|
+
await sendForRework({ reason: decision.rejection_reason, notes: decision.notes });
|
|
94
|
+
} else {
|
|
95
|
+
await advanceOrder();
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Submission behavior
|
|
100
|
+
|
|
101
|
+
Hidden fields are not rendered but their values (if any) remain in form state and are submitted only if they were filled before being hidden. Required validation is not applied to fields hidden at submission time.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## `x-lt-hide-if-empty`
|
|
106
|
+
|
|
107
|
+
A field with `"x-lt-hide-if-empty": true` is suppressed entirely when its value is null, an empty string, `false`, or `0`. This is primarily useful for `readOnly` fact fields that are only present on some escalations:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"properties": {
|
|
112
|
+
"heel_raise": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"readOnly": true,
|
|
115
|
+
"x-lt-hide-if-empty": true,
|
|
116
|
+
"description": "Heel raise specification"
|
|
117
|
+
},
|
|
118
|
+
"notes": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"readOnly": true,
|
|
121
|
+
"x-lt-hide-if-empty": true,
|
|
122
|
+
"description": "Additional processing notes"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`x-lt-hide-if-empty` is evaluated against the field's current value in form state. It is distinct from `x-lt-showIf`, which evaluates against a path in the escalation context.
|