@hotmeshio/long-tail 0.9.6 → 0.10.0
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-DQS74RL0.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-DXFIKXO3.js.map → AdminDashboard-DQS74RL0.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-CQE1mfjk.js → AgentConfigPage-CSMCrFR4.js} +7 -7
- package/dashboard/dist/assets/{AgentConfigPage-CQE1mfjk.js.map → AgentConfigPage-CSMCrFR4.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-DM59ozP_.js → AgentDetailPage-CDiopQ7D.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-DM59ozP_.js.map → AgentDetailPage-CDiopQ7D.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-DSK2uXI5.js → AgentsPage-CyUXe9n4.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DSK2uXI5.js.map → AgentsPage-CyUXe9n4.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-AiuiLcz-.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-AiuiLcz-.js.map +1 -0
- package/dashboard/dist/assets/BotPicker-BuCGvEed.js +2 -0
- package/dashboard/dist/assets/BotPicker-BuCGvEed.js.map +1 -0
- package/dashboard/dist/assets/CapabilitiesPage-nTv4STcG.js +2 -0
- package/dashboard/dist/assets/{CapabilitiesPage-DFQryGBj.js.map → CapabilitiesPage-nTv4STcG.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-B7-d5O_x.js → CollapsibleSection-CkOks9Cf.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-B7-d5O_x.js.map → CollapsibleSection-CkOks9Cf.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-DdKL8Ryt.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-DdKL8Ryt.js.map +1 -0
- package/dashboard/dist/assets/CredentialsPage-BwA7k9Cb.js +2 -0
- package/dashboard/dist/assets/{CredentialsPage-BWVgxRpZ.js.map → CredentialsPage-BwA7k9Cb.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DNnyNqXH.js → CronLabel-jC_i-Lh9.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DNnyNqXH.js.map → CronLabel-jC_i-Lh9.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-SNsQZL_U.js → CustomDurationPicker-F6bR2Q5A.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-SNsQZL_U.js.map → CustomDurationPicker-F6bR2Q5A.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-DZ3RTZFc.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-C4r0IvUt.js.map → ElapsedCell-DZ3RTZFc.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-D8Ec8Zhj.js → EscalationListSchemaPage-CaMydHIO.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-D8Ec8Zhj.js.map → EscalationListSchemaPage-CaMydHIO.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-BmEv7qU2.js → EscalationSchemaPage-BlMx2WE6.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-BmEv7qU2.js.map → EscalationSchemaPage-BlMx2WE6.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-DkIy025e.js → EscalationsOverview-DcMkbiSo.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-DkIy025e.js.map → EscalationsOverview-DcMkbiSo.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-Co4RYeKO.js → EventTable-CuZ2Q_wH.js} +2 -2
- package/dashboard/dist/assets/{EventTable-Co4RYeKO.js.map → EventTable-CuZ2Q_wH.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-CXElQqcV.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-b7ukMy0z.js.map → GraphInvokePage-CXElQqcV.js.map} +1 -1
- package/dashboard/dist/assets/HomePage-DcWfjZA-.js +2 -0
- package/dashboard/dist/assets/HomePage-DcWfjZA-.js.map +1 -0
- package/dashboard/dist/assets/ListToolbar-Dmd1UiM-.js +2 -0
- package/dashboard/dist/assets/{ListToolbar-DDG6DXVF.js.map → ListToolbar-Dmd1UiM-.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-P2o3b3mi.js → McpOverview-Cul2LFEw.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-P2o3b3mi.js.map → McpOverview-Cul2LFEw.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-C2CeBLb0.js → McpQueryDetailPage-BVlQ0WBa.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-C2CeBLb0.js.map → McpQueryDetailPage-BVlQ0WBa.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-D_N-3lp0.js → McpQueryPage-B7PmO_V-.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-D_N-3lp0.js.map → McpQueryPage-B7PmO_V-.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-uDw3MWR_.js → McpRunDetailPage-B1RhxxlU.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-uDw3MWR_.js.map → McpRunDetailPage-B1RhxxlU.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CDTUxghb.js → McpRunsPage-DlSrpxiv.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CDTUxghb.js.map → McpRunsPage-DlSrpxiv.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-vm_ByX41.js +2 -0
- package/dashboard/dist/assets/OperationsPage-vm_ByX41.js.map +1 -0
- package/dashboard/dist/assets/OperatorDashboard-zviIxH-0.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-CVRD8nQH.js.map → OperatorDashboard-zviIxH-0.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-txiMHbMI.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-Dt6PAK04.js.map → ProcessDetailPage-txiMHbMI.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-CMkjASgs.js → ProcessesListPage-CRttlU3L.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-CMkjASgs.js.map → ProcessesListPage-CRttlU3L.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-DBIl_mhK.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-DBIl_mhK.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-DzrSv6m-.js +2 -0
- package/dashboard/dist/assets/{RolesPage-DtHIjDss.js.map → RolesPage-DzrSv6m-.js.map} +1 -1
- package/dashboard/dist/assets/RunAsSelector-CiLjlmIE.js +2 -0
- package/dashboard/dist/assets/{RunAsSelector-C198CpFs.js.map → RunAsSelector-CiLjlmIE.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-Di6b2GDE.js +2 -0
- package/dashboard/dist/assets/{StickyPagination-eZ6WhMrL.js.map → StickyPagination-Di6b2GDE.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-7xq_dddO.js → StreamMessageDetail-BbkU45X_.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-7xq_dddO.js.map → StreamMessageDetail-BbkU45X_.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-Yg5QT0nI.js → SwimlaneTimeline-BzsvdstG.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-Yg5QT0nI.js.map → SwimlaneTimeline-BzsvdstG.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-Biikk9o_.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-Wc3AFnqs.js.map → TaskDetailPage-Biikk9o_.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-t9WAft9-.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-V-1J_B_L.js.map → TasksListPage-t9WAft9-.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-XdHghs5F.js → TimeAgo-I7X6jdne.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-XdHghs5F.js.map → TimeAgo-I7X6jdne.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-BrzctPWT.js → TimestampCell-Cex9nr0t.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-BrzctPWT.js.map → TimestampCell-Cex9nr0t.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--MGaDY-1.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-CGfGUsaq.js.map → ToolTestPanel--MGaDY-1.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-CcAgD2k5.js → TopicDetailPage-B8pHHweA.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-CcAgD2k5.js.map → TopicDetailPage-B8pHHweA.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-BOQG7s4P.js → TopicsPage-tiOEQz2m.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-BOQG7s4P.js.map → TopicsPage-tiOEQz2m.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-BzxR0Ihp.js → UserName-Cdhyuv5K.js} +2 -2
- package/dashboard/dist/assets/{UserName-BzxR0Ihp.js.map → UserName-Cdhyuv5K.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-hRsDMByj.js → WorkflowExecutionPage-DD1BKj5k.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-hRsDMByj.js.map → WorkflowExecutionPage-DD1BKj5k.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-BeWjDHYW.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-ky0ulsUe.js.map → WorkflowsDashboard-BeWjDHYW.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-CzRJUV58.js → WorkflowsOverview-CkI-Pmj7.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CzRJUV58.js.map → WorkflowsOverview-CkI-Pmj7.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Beqzy9Ph.js → YamlWorkflowDetailPage-BQnO8-PQ.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Beqzy9Ph.js.map → YamlWorkflowDetailPage-BQnO8-PQ.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-BzBUU2ug.js → YamlWorkflowsPage-DjmNTQ5r.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-BzBUU2ug.js.map → YamlWorkflowsPage-DjmNTQ5r.js.map} +1 -1
- package/dashboard/dist/assets/{agents-Dlu4m6jy.js → agents-2DaoprdB.js} +2 -2
- package/dashboard/dist/assets/{agents-Dlu4m6jy.js.map → agents-2DaoprdB.js.map} +1 -1
- package/dashboard/dist/assets/{bots-C4G5U-6Q.js → bots-BdwLwMUg.js} +2 -2
- package/dashboard/dist/assets/{bots-C4G5U-6Q.js.map → bots-BdwLwMUg.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-BSFP_boq.js → capabilities-BsuFeK9J.js} +2 -2
- package/dashboard/dist/assets/{capabilities-BSFP_boq.js.map → capabilities-BsuFeK9J.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-qCqoP2ZV.js → controlplane-CxKOrcFO.js} +2 -2
- package/dashboard/dist/assets/{controlplane-qCqoP2ZV.js.map → controlplane-CxKOrcFO.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-y5SQ2Ulh.js +2 -0
- package/dashboard/dist/assets/escalation-columns-y5SQ2Ulh.js.map +1 -0
- package/dashboard/dist/assets/index-BC1NxetR.js +2 -0
- package/dashboard/dist/assets/{index-C0KklOM4.js.map → index-BC1NxetR.js.map} +1 -1
- package/dashboard/dist/assets/index-BVZINC_p.js +2 -0
- package/dashboard/dist/assets/{index-QL26JxBF.js.map → index-BVZINC_p.js.map} +1 -1
- package/dashboard/dist/assets/{index-CGVqW6MI.js → index-BXbt5NHG.js} +2 -2
- package/dashboard/dist/assets/{index-CGVqW6MI.js.map → index-BXbt5NHG.js.map} +1 -1
- package/dashboard/dist/assets/{index-CuC_mNNA.js → index-BYEkfaUq.js} +2 -2
- package/dashboard/dist/assets/{index-CuC_mNNA.js.map → index-BYEkfaUq.js.map} +1 -1
- package/dashboard/dist/assets/index-B_68lp-B.js +5 -0
- package/dashboard/dist/assets/index-B_68lp-B.js.map +1 -0
- package/dashboard/dist/assets/index-BeivyVtT.css +1 -0
- package/dashboard/dist/assets/{index-DUhettdV.js → index-CRBXgMsh.js} +2 -2
- package/dashboard/dist/assets/{index-DUhettdV.js.map → index-CRBXgMsh.js.map} +1 -1
- package/dashboard/dist/assets/{index-BObWYPxd.js → index-Cfq3UKd8.js} +2 -2
- package/dashboard/dist/assets/{index-BObWYPxd.js.map → index-Cfq3UKd8.js.map} +1 -1
- package/dashboard/dist/assets/{index-D_FNaSoe.js → index-DJ5nhcGB.js} +2 -2
- package/dashboard/dist/assets/{index-D_FNaSoe.js.map → index-DJ5nhcGB.js.map} +1 -1
- package/dashboard/dist/assets/{index-DCTWUswT.js → index-DVLBkcoD.js} +4 -4
- package/dashboard/dist/assets/index-DVLBkcoD.js.map +1 -0
- package/dashboard/dist/assets/index-DjI2AUbn.js +5 -0
- package/dashboard/dist/assets/{index-DWSNtg28.js.map → index-DjI2AUbn.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dzhn5MDY.js → index-DyS2DHqy.js} +2 -2
- package/dashboard/dist/assets/{index-Dzhn5MDY.js.map → index-DyS2DHqy.js.map} +1 -1
- package/dashboard/dist/assets/index-H6Vp-gPG.js +2 -0
- package/dashboard/dist/assets/{index-BtEWQNcP.js.map → index-H6Vp-gPG.js.map} +1 -1
- package/dashboard/dist/assets/index-YPLTjeli.js +63 -0
- package/dashboard/dist/assets/index-YPLTjeli.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-CTdgLX5j.js → knowledge-CLnYcq0Y.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CTdgLX5j.js.map → knowledge-CLnYcq0Y.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-BEsMr678.js → mcp-C1-iXkg7.js} +2 -2
- package/dashboard/dist/assets/{mcp-BEsMr678.js.map → mcp-C1-iXkg7.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CmamHKIR.js → mcp-query-DcyDxuGY.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CmamHKIR.js.map → mcp-query-DcyDxuGY.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-taOXA2-y.js → pipelines-BvG5D6bV.js} +2 -2
- package/dashboard/dist/assets/{pipelines-taOXA2-y.js.map → pipelines-BvG5D6bV.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-DEaDdyBh.js → tasks-qwh8q5q7.js} +2 -2
- package/dashboard/dist/assets/{tasks-DEaDdyBh.js.map → tasks-qwh8q5q7.js.map} +1 -1
- package/dashboard/dist/assets/{topics-2qRx68Ft.js → topics-MLKKA-Ql.js} +2 -2
- package/dashboard/dist/assets/{topics-2qRx68Ft.js.map → topics-MLKKA-Ql.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-oXjfKrbI.js → useEventHooks-B8uNFB-l.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-oXjfKrbI.js.map → useEventHooks-B8uNFB-l.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-YdT1Zouk.js → useNamespace-DjZmztJM.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-YdT1Zouk.js.map → useNamespace-DjZmztJM.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-Bt-RZ7Zl.js → useYamlActivityEvents-DIvK20uI.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-Bt-RZ7Zl.js.map → useYamlActivityEvents-DIvK20uI.js.map} +1 -1
- package/dashboard/dist/assets/{users-DbX5JZ_w.js → users-CVjajeDg.js} +2 -2
- package/dashboard/dist/assets/{users-DbX5JZ_w.js.map → users-CVjajeDg.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-BziTcJNq.js} +2 -2
- package/dashboard/dist/assets/{workflows-acMcPTC5.js.map → workflows-BziTcJNq.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-D6kzrMYx.js → yaml-workflows-Dr-ZcE_0.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-D6kzrMYx.js.map → yaml-workflows-Dr-ZcE_0.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 +168 -0
- package/docs/hitl/x-lt-widget.md +152 -0
- package/docs/hitl-guide.md +83 -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
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Long Tail
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The queue durable systems forgot.**
|
|
4
|
+
|
|
5
|
+
Durable platforms are built around determinism — that is their strength and their blind spot. They give you rich, first-class queues for deterministic work (retry, timeout, `backoffCoefficient`, exactly-once) and an `await condition()` to park a workflow until a signal arrives. All of it assumes the thing you are waiting on will, eventually, behave.
|
|
6
|
+
|
|
7
|
+
Most of the work that decides an outcome behaves on its own schedule: an approval, a person, a shipment, a machine that needs service. You cannot set a `backoffCoefficient` on a person — retrying someone who has not answered just asks twice. This is why "human-in-the-loop" keeps getting bolted on as a feature: the platform perfected the deterministic queue and left the non-deterministic one for you to improvise.
|
|
8
|
+
|
|
9
|
+
Long Tail makes it a primitive. The same `condition()` wait — except the act of waiting mints a row that is searchable, claimable, deadlined, and role-gated on a shared metadata surface. Machines answer some rows, people answer others; the workflow resumes either way, exactly where it paused.
|
|
4
10
|
|
|
5
11
|
```bash
|
|
6
12
|
npm install @hotmeshio/long-tail
|
|
@@ -8,7 +14,7 @@ npm install @hotmeshio/long-tail
|
|
|
8
14
|
|
|
9
15
|
## How it works
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
One workflow, both queues: `proxyActivities` for the deterministic one, `condition()` for the one reality answers.
|
|
12
18
|
|
|
13
19
|
```typescript
|
|
14
20
|
import { Durable } from '@hotmeshio/hotmesh';
|
|
@@ -54,6 +60,19 @@ export async function analyzeContent(content: string) {
|
|
|
54
60
|
}
|
|
55
61
|
```
|
|
56
62
|
|
|
63
|
+
## What the primitive buys
|
|
64
|
+
|
|
65
|
+
The non-deterministic queue has qualities the deterministic one never needed:
|
|
66
|
+
|
|
67
|
+
- **RBAC is intrinsic.** The queue is a role; permission lives in the queue itself. Any member answers — a person in the dashboard, a service account through the API — and the workflow is indifferent to which.
|
|
68
|
+
- **The form is data.** A versioned JSON Schema on the role renders the resolver's UI — fields, validation, conditionals, layout — with zero frontend code. Assign an escalation to one named user with self scope and you have a just-in-time form: one person, one item, one auditable edition.
|
|
69
|
+
- **Claims are locks.** A claim is a TTL window with an extend prompt before it lapses and a resolve guard behind it — the platform rejects a submission against an expired claim atomically, in the same statement that settles the row. Stale work cannot land.
|
|
70
|
+
- **Cancel is home.** Cancellation is a first-class settlement, not an exception. When the pressure no longer applies, the waiting flow sees `null` and reconciles to a modeled resting state. Handled is broader than resolved — timeout, cancel, and hop-onward are all endings the loop was built to reach.
|
|
71
|
+
- **One shared surface.** Every actor that touches an item writes to the same metadata-keyed row. The contact point is shared by construction, so there is one place to search, one place to claim, one place to audit.
|
|
72
|
+
- **History accretes.** The surface only ever adds: intent at creation, outcome at settlement, every crossing in between. The object's whole history sits there to replay — frames the queue kept for you.
|
|
73
|
+
- **Objects get lifecycles.** Give an object this queue and a `while` loop and it lives — a digital twin: it advertises when free, asks when it needs service, waits for reality's answer, reconciles when reality gives one. An order, a document only compliance may edit, a machine on a line — all the same shape, an object looping at a role.
|
|
74
|
+
- **What repeats, stops needing people.** Every settled row is a worked example. Recurring patterns become deterministic tools — problems that once required a person, then required AI reasoning, eventually require neither.
|
|
75
|
+
|
|
57
76
|
## Start
|
|
58
77
|
|
|
59
78
|
Point at Postgres. Everything else is optional.
|
|
@@ -69,31 +88,9 @@ const lt = await start({
|
|
|
69
88
|
|
|
70
89
|
Dashboard at [http://localhost:3000](http://localhost:3000). The [boilerplate](https://github.com/hotmeshio/long-tail-boilerplate) has a working project with workflows, MCP servers, and MinIO.
|
|
71
90
|
|
|
72
|
-
## The pattern
|
|
73
|
-
|
|
74
|
-
Four moves, from a plan you write to a plan that keeps up with reality.
|
|
75
|
-
|
|
76
|
-
**Step 1 — Author a durable workflow.** Your function checkpoints to Postgres. It can sleep, branch, call child workflows, wait for signals. Standard durable execution.
|
|
77
|
-
|
|
78
|
-
**Step 2 — Certify it.** Promotion to certified adds interceptor guarantees: failures escalate instead of throwing, escalation chains route through RBAC-scoped roles, and every error is either handled or surfaced. It cannot silently fail.
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
curl -X PUT http://localhost:3000/api/workflows/reviewContent/config \
|
|
82
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
83
|
-
-d '{ "invocable": true, "task_queue": "default", "default_role": "reviewer" }'
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
**Step 3 — React to events.** Signals are reality reporting back. Workflows publish topics; agents subscribe. When `activity.failed` fires, an automation can re-run the step, notify a team, or trigger a different workflow. The choreography is dynamic — add subscribers through the dashboard without changing code.
|
|
87
|
-
|
|
88
|
-
**Step 4 — Compile what repeats.** The same workflow has two forms. What you wrote in Step 1 is the *procedural* form — readable, Temporal-like, emulated atop the graph: cheap to maintain, heavier to run. The Designer compiles a working execution into the *graph* form — the same durable workflow as a deterministic DAG: no LLM at runtime, no replay overhead, typed in and out, roughly 3x faster. Every procedural pattern has a graph equivalent and the reverse; you pick readability or speed without giving up durability, escalation, or transactional guarantees. It deploys as a reusable tool that any workflow or API call can invoke.
|
|
89
|
-
|
|
90
|
-
Over time, the system accumulates compiled tools. Problems that once required a human, then required AI reasoning, eventually require neither.
|
|
91
|
-
|
|
92
|
-
These four steps map to how the dashboard is organized. **React** is the reactive side (Step 3) — topics, subscriptions, automations. **Orchestrate** is the orchestrated side (Steps 1 and 4) — procedural and graph flows side by side, both durable and pull-based under the hood. **Design** is the optional bridge: with an `ANTHROPIC_API_KEY` it turns a description or a tool run into a graph flow; without one, choreography and orchestration stand on their own, no tradeoff.
|
|
93
|
-
|
|
94
91
|
## Register MCP tools
|
|
95
92
|
|
|
96
|
-
Long Tail connects to any MCP server. Registered tools become durable activities
|
|
93
|
+
Long Tail connects to any MCP server. Registered tools become durable activities.
|
|
97
94
|
|
|
98
95
|
**Existing package — no code:**
|
|
99
96
|
|
|
@@ -147,72 +144,6 @@ const lt = await start({
|
|
|
147
144
|
|
|
148
145
|
All three paths produce the same outcome: tools callable as durable activities. See the [MCP guide](https://github.com/hotmeshio/long-tail/blob/main/docs/mcp.md).
|
|
149
146
|
|
|
150
|
-
## Compile workflows
|
|
151
|
-
|
|
152
|
-
The `ltc` compiler scans TypeScript workflow files and compiles them to YAML DAGs — like `tsc` for workflows.
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
export ANTHROPIC_API_KEY=sk-ant-...
|
|
156
|
-
npx ltc compile workflows/
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
The source is the spec. The compiled YAML is the optimized execution. Both live in the repo. See the [Compiler Guide](https://github.com/hotmeshio/long-tail/blob/main/docs/compiler.md).
|
|
160
|
-
|
|
161
|
-
## Register a graph flow by hand
|
|
162
|
-
|
|
163
|
-
`graphWorkflows` is the graph-form peer of `workers`: hand-author the HotMesh YAML and it's created, deployed, and activated at startup. The same human surface is declarative here — a `hook` with an `escalation:` block. When the flow reaches it, the workflow suspends and writes the escalation row; a person resolves it and the flow continues:
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const lt = await start({
|
|
167
|
-
database: { connectionString: process.env.DATABASE_URL },
|
|
168
|
-
graphWorkflows: [{
|
|
169
|
-
name: 'order_approval',
|
|
170
|
-
namespace: 'graph',
|
|
171
|
-
inputSchema: { type: 'object', properties: { orderId: { type: 'string' }, region: { type: 'string' } }, required: ['orderId'] },
|
|
172
|
-
yaml: `
|
|
173
|
-
app:
|
|
174
|
-
id: graph
|
|
175
|
-
version: '1'
|
|
176
|
-
graphs:
|
|
177
|
-
- subscribes: order_approval
|
|
178
|
-
publishes: order_approval.done
|
|
179
|
-
input: { schema: { type: object, properties: { orderId: { type: string }, region: { type: string } } } }
|
|
180
|
-
output: { schema: { type: object, properties: { approved: { type: boolean } } } }
|
|
181
|
-
activities:
|
|
182
|
-
trigger:
|
|
183
|
-
type: trigger
|
|
184
|
-
review:
|
|
185
|
-
type: hook
|
|
186
|
-
escalation:
|
|
187
|
-
role: approver
|
|
188
|
-
type: order-approval
|
|
189
|
-
priority: 2
|
|
190
|
-
description: Approve order for dispatch
|
|
191
|
-
metadata:
|
|
192
|
-
orderId: '{trigger.output.data.orderId}'
|
|
193
|
-
region: '{trigger.output.data.region}'
|
|
194
|
-
envelope:
|
|
195
|
-
instructions: Review and approve or reject
|
|
196
|
-
job:
|
|
197
|
-
maps:
|
|
198
|
-
approved: '{review.hook.data.approved}'
|
|
199
|
-
transitions:
|
|
200
|
-
trigger:
|
|
201
|
-
- to: review
|
|
202
|
-
hooks:
|
|
203
|
-
order_approval.approve:
|
|
204
|
-
- to: review
|
|
205
|
-
conditions:
|
|
206
|
-
match:
|
|
207
|
-
- expected: '{$job.metadata.jid}'
|
|
208
|
-
actual: '{$self.hook.data.id}'
|
|
209
|
-
`,
|
|
210
|
-
}],
|
|
211
|
-
});
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
It appears under **Orchestrate › Graph** and routes to a person exactly like the procedural `condition()` above — the same claim-and-resolve surface, declared in YAML. The `metadata` expressions resolve against the live job at suspension time, so the row carries the real order values.
|
|
215
|
-
|
|
216
147
|
## Full configuration
|
|
217
148
|
|
|
218
149
|
```typescript
|
|
@@ -221,10 +152,8 @@ const lt = await start({
|
|
|
221
152
|
workers: [{ taskQueue: 'default', workflow: reviewContent }],
|
|
222
153
|
|
|
223
154
|
// Everything below is optional
|
|
224
|
-
graphWorkflows: [{ name: 'hello_world', namespace: 'graph', yaml: helloWorldYaml }],
|
|
225
155
|
seed: { admin: { externalId: 'admin', password: process.env.ADMIN_PASSWORD } },
|
|
226
156
|
mcp: { server: { enabled: true }, serverFactories: { 'my-tools': createMyToolsServer } },
|
|
227
|
-
escalation: { strategy: 'mcp' },
|
|
228
157
|
auth: { secret: process.env.JWT_SECRET },
|
|
229
158
|
telemetry: { honeycomb: { apiKey: process.env.HNY } },
|
|
230
159
|
logging: { pino: { level: 'info' } },
|
|
@@ -59,6 +59,33 @@ export declare function assertWriteAccess(userId: string, escalation: {
|
|
|
59
59
|
status: number;
|
|
60
60
|
error: string;
|
|
61
61
|
} | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Claim-liveness gate for resolve, aligned with `isEffectivelyClaimed`: a
|
|
64
|
+
* claim is a work LOCK that exists only while a TTL window (`assigned_until`)
|
|
65
|
+
* is active. Blocks exactly two states:
|
|
66
|
+
* - another principal holds a live window (the lock is theirs), and
|
|
67
|
+
* - the caller's own window has lapsed (stale work — re-claim to resolve).
|
|
68
|
+
*
|
|
69
|
+
* Everything else passes: unclaimed rows (system resolvers act on them by
|
|
70
|
+
* design), durable pre-assignments (`assigned_to` with no window — the
|
|
71
|
+
* one-time-user JIT-form shape), and rows whose window lapsed under a
|
|
72
|
+
* different assignee (the lock is gone; the row is back in the pool).
|
|
73
|
+
*
|
|
74
|
+
* Applies to every principal, including global access — the claim is a work
|
|
75
|
+
* lock, not an authorization scope, so superadmins are equally bound by it.
|
|
76
|
+
*
|
|
77
|
+
* Advisory (read-then-check): the signal-key and notification resolve paths
|
|
78
|
+
* re-assert the same predicate atomically inside the SDK's guarded resolve
|
|
79
|
+
* UPDATE (`assertClaim`); this pre-check exists so the signal/triage/re-run
|
|
80
|
+
* paths reject BEFORE their side effects fire.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertLiveClaimant(userId: string, escalation: {
|
|
83
|
+
assigned_to?: string | null;
|
|
84
|
+
assigned_until?: Date | string | null;
|
|
85
|
+
}): {
|
|
86
|
+
status: number;
|
|
87
|
+
error: string;
|
|
88
|
+
} | null;
|
|
62
89
|
/**
|
|
63
90
|
* Require write_scope='all' (or global) for a queue-management verb — release and
|
|
64
91
|
* escalate move an item out of the user's hands, so self-scope owners (who only
|
|
@@ -39,6 +39,7 @@ exports.validateIds = validateIds;
|
|
|
39
39
|
exports.getEscalationWriteScope = getEscalationWriteScope;
|
|
40
40
|
exports.assertReadAccess = assertReadAccess;
|
|
41
41
|
exports.assertWriteAccess = assertWriteAccess;
|
|
42
|
+
exports.assertLiveClaimant = assertLiveClaimant;
|
|
42
43
|
exports.assertQueueManageAccess = assertQueueManageAccess;
|
|
43
44
|
exports.checkBulkPermission = checkBulkPermission;
|
|
44
45
|
exports.resolveAssignee = resolveAssignee;
|
|
@@ -130,6 +131,39 @@ async function assertWriteAccess(userId, escalation) {
|
|
|
130
131
|
}
|
|
131
132
|
return { status: 403, error: `You do not have write access to the "${escalation.role}" role` };
|
|
132
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Claim-liveness gate for resolve, aligned with `isEffectivelyClaimed`: a
|
|
136
|
+
* claim is a work LOCK that exists only while a TTL window (`assigned_until`)
|
|
137
|
+
* is active. Blocks exactly two states:
|
|
138
|
+
* - another principal holds a live window (the lock is theirs), and
|
|
139
|
+
* - the caller's own window has lapsed (stale work — re-claim to resolve).
|
|
140
|
+
*
|
|
141
|
+
* Everything else passes: unclaimed rows (system resolvers act on them by
|
|
142
|
+
* design), durable pre-assignments (`assigned_to` with no window — the
|
|
143
|
+
* one-time-user JIT-form shape), and rows whose window lapsed under a
|
|
144
|
+
* different assignee (the lock is gone; the row is back in the pool).
|
|
145
|
+
*
|
|
146
|
+
* Applies to every principal, including global access — the claim is a work
|
|
147
|
+
* lock, not an authorization scope, so superadmins are equally bound by it.
|
|
148
|
+
*
|
|
149
|
+
* Advisory (read-then-check): the signal-key and notification resolve paths
|
|
150
|
+
* re-assert the same predicate atomically inside the SDK's guarded resolve
|
|
151
|
+
* UPDATE (`assertClaim`); this pre-check exists so the signal/triage/re-run
|
|
152
|
+
* paths reject BEFORE their side effects fire.
|
|
153
|
+
*/
|
|
154
|
+
function assertLiveClaimant(userId, escalation) {
|
|
155
|
+
if (!escalation.assigned_to || !escalation.assigned_until)
|
|
156
|
+
return null;
|
|
157
|
+
const live = new Date(escalation.assigned_until).getTime() > Date.now();
|
|
158
|
+
const mine = escalation.assigned_to === userId;
|
|
159
|
+
if (live && !mine) {
|
|
160
|
+
return { status: 409, error: 'Escalation is claimed by another user' };
|
|
161
|
+
}
|
|
162
|
+
if (!live && mine) {
|
|
163
|
+
return { status: 409, error: 'Your claim has expired — re-claim this escalation to resolve it' };
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
133
167
|
/**
|
|
134
168
|
* Require write_scope='all' (or global) for a queue-management verb — release and
|
|
135
169
|
* escalate move an item out of the user's hands, so self-scope owners (who only
|
|
@@ -49,7 +49,8 @@ function hasFacetQuery(i) {
|
|
|
49
49
|
i.exists?.length ||
|
|
50
50
|
i.orderBy?.length ||
|
|
51
51
|
i.roles?.length ||
|
|
52
|
-
i.available != null
|
|
52
|
+
i.available != null ||
|
|
53
|
+
i.jeopardy === true);
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
56
|
* List escalations with optional filters.
|
|
@@ -94,6 +95,7 @@ async function listEscalations(input, auth) {
|
|
|
94
95
|
block: input.block,
|
|
95
96
|
range: input.range,
|
|
96
97
|
exists: input.exists,
|
|
98
|
+
jeopardy: input.jeopardy,
|
|
97
99
|
orderBy: input.orderBy,
|
|
98
100
|
},
|
|
99
101
|
type: input.type,
|
|
@@ -168,6 +170,7 @@ async function listAvailableEscalations(input, auth) {
|
|
|
168
170
|
block: input.block,
|
|
169
171
|
range: input.range,
|
|
170
172
|
exists: input.exists,
|
|
173
|
+
jeopardy: input.jeopardy,
|
|
171
174
|
orderBy: input.orderBy,
|
|
172
175
|
},
|
|
173
176
|
type: input.type,
|
|
@@ -273,11 +276,18 @@ async function getEscalationStats(input, auth) {
|
|
|
273
276
|
}
|
|
274
277
|
async function getStationMetrics(input, auth) {
|
|
275
278
|
try {
|
|
279
|
+
// Station metrics are aggregate queue-SHAPE counts (pending/claimed/resolved
|
|
280
|
+
// + jeopardy) per role — the depth of a lane, not the disclosure of any one
|
|
281
|
+
// item. Membership in a role entitles you to see its shape, so this view
|
|
282
|
+
// spans every role the user belongs to, including read_scope='self' roles
|
|
283
|
+
// (whose per-item lists stay narrowed elsewhere). Without the selfRoles
|
|
284
|
+
// union, a self-scoped operator would see an empty board on their own lanes.
|
|
276
285
|
const scope = await (0, helpers_1.getEscalationReadScope)(auth.userId);
|
|
277
|
-
|
|
286
|
+
const memberRoles = [...scope.allRoles, ...scope.selfRoles];
|
|
287
|
+
if (!scope.global && memberRoles.length === 0) {
|
|
278
288
|
return { status: 200, data: { stations: [] } };
|
|
279
289
|
}
|
|
280
|
-
const stations = await escalationService.getStationMetrics(scope.global ? undefined :
|
|
290
|
+
const stations = await escalationService.getStationMetrics(scope.global ? undefined : memberRoles, input.period);
|
|
281
291
|
return { status: 200, data: { stations } };
|
|
282
292
|
}
|
|
283
293
|
catch (err) {
|
|
@@ -83,6 +83,13 @@ async function resolveEscalation(input, auth) {
|
|
|
83
83
|
const denied = await (0, helpers_1.assertWriteAccess)(auth.userId, escalation);
|
|
84
84
|
if (denied)
|
|
85
85
|
return denied;
|
|
86
|
+
// Claim-liveness gate: a claim-tracked row resolves only under a live claim
|
|
87
|
+
// held by the caller. Advisory here (rejects the signal/triage/re-run paths
|
|
88
|
+
// before their side effects); the signal-key and notification paths below
|
|
89
|
+
// re-assert the same predicate atomically inside the guarded resolve UPDATE.
|
|
90
|
+
const stale = (0, helpers_1.assertLiveClaimant)(auth.userId, escalation);
|
|
91
|
+
if (stale)
|
|
92
|
+
return stale;
|
|
86
93
|
// The resolver payload is stored exactly as submitted — its shape is the
|
|
87
94
|
// workflow's contract, formed by the caller (the React app maps the form to
|
|
88
95
|
// the payload via x-lt-bind before submitting). No server-side transform.
|
|
@@ -106,7 +113,7 @@ async function resolveEscalation(input, auth) {
|
|
|
106
113
|
// `condition(signalId, config)`. The SDK's resolve marks it resolved AND
|
|
107
114
|
// delivers the signal to `signal_key`, resuming THIS job — no re-run.
|
|
108
115
|
if (escalation.signal_key) {
|
|
109
|
-
return resolveViaSignalKey(escalation, resolverPayload, metadata);
|
|
116
|
+
return resolveViaSignalKey(escalation, resolverPayload, metadata, auth.userId);
|
|
110
117
|
}
|
|
111
118
|
// Path C: escalation strategy may redirect to triage
|
|
112
119
|
const envelope = await reconstructEnvelope(escalation);
|
|
@@ -119,7 +126,10 @@ async function resolveEscalation(input, auth) {
|
|
|
119
126
|
}
|
|
120
127
|
// Path D: notification-only — no workflow to restart. One atomic resolve.
|
|
121
128
|
if (!escalation.workflow_type || !escalation.task_queue) {
|
|
122
|
-
await escalationService.resolveEscalation(escalation.id, resolverPayload, metadata);
|
|
129
|
+
const resolved = await escalationService.resolveEscalation(escalation.id, resolverPayload, metadata, auth.userId);
|
|
130
|
+
if (!resolved) {
|
|
131
|
+
return { status: 409, error: 'Escalation not available for resolution' };
|
|
132
|
+
}
|
|
123
133
|
return { status: 200, data: { acknowledged: true, escalationId: escalation.id } };
|
|
124
134
|
}
|
|
125
135
|
// Path E: standard re-run
|
|
@@ -323,11 +333,13 @@ async function resolveViaConditionSignal(escalation, resolverPayload, metadata)
|
|
|
323
333
|
* transaction, so the original job resumes in place (no re-run, no separate
|
|
324
334
|
* resolve activity). Password fields are redacted before they enter the signal.
|
|
325
335
|
*/
|
|
326
|
-
async function resolveViaSignalKey(escalation, resolverPayload, metadata) {
|
|
336
|
+
async function resolveViaSignalKey(escalation, resolverPayload, metadata, assertClaim) {
|
|
327
337
|
const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
|
|
328
338
|
// One atomic call: status→resolved, signal delivered, and the outcome patch merged
|
|
329
339
|
// into the GIN-indexed metadata — all inside the single WHERE-guarded UPDATE.
|
|
330
|
-
|
|
340
|
+
// `assertClaim` (interactive path only) makes the claim-liveness gate part of
|
|
341
|
+
// that same UPDATE; the webhook path (resolveBySignalKey) stays claim-agnostic.
|
|
342
|
+
const resolved = await escalationService.resolveEscalation(escalation.id, signalPayload, metadata, assertClaim);
|
|
331
343
|
if (!resolved) {
|
|
332
344
|
return { status: 409, error: 'Escalation not available for resolution' };
|
|
333
345
|
}
|
|
@@ -107,6 +107,7 @@ function registerListRoutes(router) {
|
|
|
107
107
|
exists: jsonParam(req.query.exists),
|
|
108
108
|
orderBy: jsonParam(req.query.orderBy),
|
|
109
109
|
available: req.query.available != null ? req.query.available === 'true' : undefined,
|
|
110
|
+
jeopardy: req.query.jeopardy === '1' || req.query.jeopardy === 'true' || undefined,
|
|
110
111
|
}, req.auth);
|
|
111
112
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
112
113
|
});
|
|
@@ -133,6 +134,7 @@ function registerListRoutes(router) {
|
|
|
133
134
|
range: jsonParam(req.query.range),
|
|
134
135
|
exists: jsonParam(req.query.exists),
|
|
135
136
|
orderBy: jsonParam(req.query.orderBy),
|
|
137
|
+
jeopardy: req.query.jeopardy === '1' || req.query.jeopardy === 'true' || undefined,
|
|
136
138
|
}, req.auth);
|
|
137
139
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
138
140
|
});
|
|
@@ -20,7 +20,7 @@ export declare function claimEscalation(id: string, userId: string, durationMinu
|
|
|
20
20
|
* ("what was intended"). Distinct from `resolverPayload`, which is delivered to
|
|
21
21
|
* the waiting workflow as `condition()`'s return value and is not GIN-indexed.
|
|
22
22
|
*/
|
|
23
|
-
export declare function resolveEscalation(id: string, resolverPayload: Record<string, any>, metadata?: Record<string, any
|
|
23
|
+
export declare function resolveEscalation(id: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>, assertClaim?: string): Promise<LTEscalationRecord | null>;
|
|
24
24
|
/**
|
|
25
25
|
* Resolve a SET of escalations by id in ONE guarded statement
|
|
26
26
|
* (`UPDATE … WHERE id = ANY($ids) AND status='pending'`). Returns the rows that
|
|
@@ -115,9 +115,13 @@ async function claimEscalation(id, userId, durationMinutes = 30) {
|
|
|
115
115
|
* ("what was intended"). Distinct from `resolverPayload`, which is delivered to
|
|
116
116
|
* the waiting workflow as `condition()`'s return value and is not GIN-indexed.
|
|
117
117
|
*/
|
|
118
|
-
async function resolveEscalation(id, resolverPayload, metadata) {
|
|
118
|
+
async function resolveEscalation(id, resolverPayload, metadata, assertClaim) {
|
|
119
119
|
const client = await (0, client_1.escalations)();
|
|
120
|
-
|
|
120
|
+
// `assertClaim` rides the SDK's guarded UPDATE: no active claim lock may
|
|
121
|
+
// stand against this assignee, or the resolve blocks (claim-expired /
|
|
122
|
+
// claimed-by-other). The API layer's advisory pre-check (assertLiveClaimant)
|
|
123
|
+
// rejects early; this assertion is the atomic arbiter.
|
|
124
|
+
const result = await client.resolve({ id, resolverPayload, metadata, assertClaim });
|
|
121
125
|
if (!result.ok)
|
|
122
126
|
return null;
|
|
123
127
|
const escalation = (0, map_1.toEscalationRecord)(result.entry);
|
|
@@ -4,8 +4,15 @@ export declare const FACET_KEY: RegExp;
|
|
|
4
4
|
* Build the WHERE clause for a faceted query, pushing bound params into `params`.
|
|
5
5
|
* Every value is parameterized; only validated column names / metadata keys and
|
|
6
6
|
* a fixed operator set are interpolated. Returns `TRUE` when empty.
|
|
7
|
+
*
|
|
8
|
+
* `opts.tableRef` names the outer table for correlated clauses (the jeopardy
|
|
9
|
+
* predicate joins lt_roles, whose own `role` column would otherwise shadow the
|
|
10
|
+
* outer row's). Callers querying `public.hmsh_escalations` pass its name; the
|
|
11
|
+
* default matches the `public.lt_escalations` view the list paths read.
|
|
7
12
|
*/
|
|
8
|
-
export declare function buildFacetWhere(q: FacetQuery, params: unknown[]
|
|
13
|
+
export declare function buildFacetWhere(q: FacetQuery, params: unknown[], opts?: {
|
|
14
|
+
tableRef?: string;
|
|
15
|
+
}): string;
|
|
9
16
|
/** Build an injection-safe ORDER BY mixing top-level columns and metadata facets. */
|
|
10
17
|
export declare function buildFacetOrder(orderBy?: FacetOrder[]): string;
|
|
11
18
|
/**
|
|
@@ -26,8 +26,14 @@ function metaExpr(key, numeric = false) {
|
|
|
26
26
|
* Build the WHERE clause for a faceted query, pushing bound params into `params`.
|
|
27
27
|
* Every value is parameterized; only validated column names / metadata keys and
|
|
28
28
|
* a fixed operator set are interpolated. Returns `TRUE` when empty.
|
|
29
|
+
*
|
|
30
|
+
* `opts.tableRef` names the outer table for correlated clauses (the jeopardy
|
|
31
|
+
* predicate joins lt_roles, whose own `role` column would otherwise shadow the
|
|
32
|
+
* outer row's). Callers querying `public.hmsh_escalations` pass its name; the
|
|
33
|
+
* default matches the `public.lt_escalations` view the list paths read.
|
|
29
34
|
*/
|
|
30
|
-
function buildFacetWhere(q, params) {
|
|
35
|
+
function buildFacetWhere(q, params, opts = {}) {
|
|
36
|
+
const ref = opts.tableRef ?? 'lt_escalations';
|
|
31
37
|
const clauses = [];
|
|
32
38
|
if (q.role) {
|
|
33
39
|
params.push(q.role);
|
|
@@ -66,6 +72,28 @@ function buildFacetWhere(q, params) {
|
|
|
66
72
|
else if (q.available === false) {
|
|
67
73
|
clauses.push(`(assigned_to IS NOT NULL AND assigned_until > NOW())`);
|
|
68
74
|
}
|
|
75
|
+
// Jeopardy: the row is past its role's priority threshold — the SAME
|
|
76
|
+
// expression the Pace Board's priority_count uses (services/escalation/
|
|
77
|
+
// sql.ts live_counts), so a jeopardy list's total always equals the pill.
|
|
78
|
+
// Age origin: the role's priority_facet metadata timestamp (created_at when
|
|
79
|
+
// unset), guarded by pg_input_is_valid so malformed values never throw.
|
|
80
|
+
// Threshold: priority_threshold_minutes, falling back to sla_minutes. A role
|
|
81
|
+
// with neither configured (or a missing lt_roles row) yields NULL → false →
|
|
82
|
+
// contributes no rows, matching a pill that never rendered. Purely the age
|
|
83
|
+
// predicate — compose `available: true` for the unclaimed pool the pill
|
|
84
|
+
// counts. The per-row scalar subquery is a PK lookup on the small, cached
|
|
85
|
+
// lt_roles table (same cost shape as the count query); if a fixed-facet
|
|
86
|
+
// deployment ever needs more, an expression index on the cast is the
|
|
87
|
+
// escape hatch.
|
|
88
|
+
if (q.jeopardy === true) {
|
|
89
|
+
clauses.push(`(SELECT CASE
|
|
90
|
+
WHEN rt.priority_facet IS NULL THEN ${ref}.created_at
|
|
91
|
+
WHEN pg_input_is_valid(${ref}.metadata->>rt.priority_facet, 'timestamptz')
|
|
92
|
+
THEN (${ref}.metadata->>rt.priority_facet)::timestamptz
|
|
93
|
+
ELSE NULL
|
|
94
|
+
END < NOW() - (COALESCE(rt.priority_threshold_minutes, rt.sla_minutes) * INTERVAL '1 minute')
|
|
95
|
+
FROM lt_roles rt WHERE rt.role = ${ref}.role)`);
|
|
96
|
+
}
|
|
69
97
|
return clauses.length ? clauses.join('\n AND ') : 'TRUE';
|
|
70
98
|
}
|
|
71
99
|
const DEFAULT_ORDER = 'priority ASC, created_at ASC';
|
package/build/types/facets.d.ts
CHANGED
|
@@ -33,6 +33,15 @@ export interface FacetQuery {
|
|
|
33
33
|
status?: string;
|
|
34
34
|
/** true = only available (unclaimed/expired); false = only held now. */
|
|
35
35
|
available?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* true = only rows past their role's priority threshold — the same predicate
|
|
38
|
+
* that produces the Pace Board's priority_count, so a jeopardy list's total
|
|
39
|
+
* always equals the pill's count. Age is measured from the role's
|
|
40
|
+
* priority_facet metadata timestamp (created_at when unset) against
|
|
41
|
+
* priority_threshold_minutes (sla_minutes when unset); roles with no
|
|
42
|
+
* threshold configured contribute no rows.
|
|
43
|
+
*/
|
|
44
|
+
jeopardy?: boolean;
|
|
36
45
|
orderBy?: FacetOrder[];
|
|
37
46
|
limit?: number;
|
|
38
47
|
offset?: number;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as o}from"./vendor-query-B2UbickB.js";import{c as n}from"./tasks-
|
|
2
|
-
//# sourceMappingURL=AdminDashboard-
|
|
1
|
+
import{j as o}from"./vendor-query-B2UbickB.js";import{c as n}from"./tasks-qwh8q5q7.js";import{k as m}from"./index-YPLTjeli.js";import{f as c}from"./useEventHooks-B8uNFB-l.js";import{u}from"./mcp-C1-iXkg7.js";import{P as d}from"./PageHeaderWithStats-TUyglVW0.js";import"./vendor-react-CXumBFUA.js";import"./vendor-icons-Qk9qLjF_.js";function j(){var a;c();const{data:s}=n({limit:1}),{data:e}=m({status:"pending",limit:1}),{data:t}=u(),r=((a=t==null?void 0:t.servers)==null?void 0:a.filter(i=>i.status==="connected").length)??0,l=t?`${r}/${t.total}`:"—";return o.jsx("div",{children:o.jsx(d,{title:"Admin",stats:[{label:"Tasks",value:(s==null?void 0:s.total)??"—"},{label:"Pending Escalations",value:(e==null?void 0:e.total)??"—",dotClass:"bg-status-pending"},{label:"MCP Servers",value:l},{label:"System",value:"Healthy",dotClass:"bg-status-success"}]})})}export{j as AdminDashboard};
|
|
2
|
+
//# sourceMappingURL=AdminDashboard-DQS74RL0.js.map
|
package/dashboard/dist/assets/{AdminDashboard-DXFIKXO3.js.map → AdminDashboard-DQS74RL0.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboard-
|
|
1
|
+
{"version":3,"file":"AdminDashboard-DQS74RL0.js","sources":["../../src/pages/admin/AdminDashboard.tsx"],"sourcesContent":["import { useTasks } from '../../api/tasks';\nimport { useEscalations } from '../../api/escalations';\nimport { useEscalationListEvents } from '../../hooks/useEventHooks';\nimport { useMcpServers } from '../../api/mcp';\nimport { PageHeaderWithStats } from '../../components/common/layout/PageHeaderWithStats';\n\nexport function AdminDashboard() {\n useEscalationListEvents();\n const { data: taskData } = useTasks({ limit: 1 });\n const { data: pendingEsc } = useEscalations({ status: 'pending', limit: 1 });\n const { data: mcpData } = useMcpServers();\n\n const connectedServers =\n mcpData?.servers?.filter((s) => s.status === 'connected').length ?? 0;\n const mcpLabel = mcpData ? `${connectedServers}/${mcpData.total}` : '—';\n\n return (\n <div>\n <PageHeaderWithStats\n title=\"Admin\"\n stats={[\n { label: 'Tasks', value: taskData?.total ?? '—' },\n { label: 'Pending Escalations', value: pendingEsc?.total ?? '—', dotClass: 'bg-status-pending' },\n { label: 'MCP Servers', value: mcpLabel },\n { label: 'System', value: 'Healthy', dotClass: 'bg-status-success' },\n ]}\n />\n </div>\n );\n}\n"],"names":["AdminDashboard","useEscalationListEvents","taskData","useTasks","pendingEsc","useEscalations","mcpData","useMcpServers","connectedServers","_a","s","mcpLabel","jsx","PageHeaderWithStats"],"mappings":"4UAMO,SAASA,GAAiB,OAC/BC,EAAA,EACA,KAAM,CAAE,KAAMC,CAAA,EAAaC,EAAS,CAAE,MAAO,EAAG,EAC1C,CAAE,KAAMC,CAAA,EAAeC,EAAe,CAAE,OAAQ,UAAW,MAAO,EAAG,EACrE,CAAE,KAAMC,CAAA,EAAYC,EAAA,EAEpBC,IACJC,EAAAH,GAAA,YAAAA,EAAS,UAAT,YAAAG,EAAkB,OAAQC,GAAMA,EAAE,SAAW,aAAa,SAAU,EAChEC,EAAWL,EAAU,GAAGE,CAAgB,IAAIF,EAAQ,KAAK,GAAK,IAEpE,aACG,MAAA,CACC,SAAAM,EAAAA,IAACC,EAAA,CACC,MAAM,QACN,MAAO,CACL,CAAE,MAAO,QAAS,OAAOX,GAAA,YAAAA,EAAU,QAAS,GAAA,EAC5C,CAAE,MAAO,sBAAuB,OAAOE,GAAA,YAAAA,EAAY,QAAS,IAAK,SAAU,mBAAA,EAC3E,CAAE,MAAO,cAAe,MAAOO,CAAA,EAC/B,CAAE,MAAO,SAAU,MAAO,UAAW,SAAU,mBAAA,CAAoB,CACrE,CAAA,EAEJ,CAEJ"}
|