@hotmeshio/long-tail 0.5.10 → 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/controlplane.d.ts +1 -0
- package/build/api/controlplane.js +1 -0
- package/build/api/diagnostics.d.ts +8 -1
- package/build/api/diagnostics.js +5 -1
- package/build/api/escalations/list.d.ts +2 -0
- package/build/api/escalations/list.js +2 -0
- package/build/api/settings.d.ts +3 -2
- package/build/api/settings.js +13 -2
- package/build/api/workflows/invocation.d.ts +6 -0
- package/build/api/workflows/invocation.js +6 -2
- package/build/modules/features.d.ts +19 -0
- package/build/modules/features.js +16 -0
- package/build/modules/version.d.ts +16 -0
- package/build/modules/version.js +34 -0
- package/build/routes/controlplane.js +1 -0
- package/build/routes/diagnostics.js +32 -11
- package/build/routes/escalations/list.js +2 -0
- package/build/routes/workflows/invocation.js +12 -4
- package/build/sdk/index.d.ts +2 -0
- package/build/services/controlplane/index.js +3 -2
- package/build/services/controlplane/stream-messages-sql.d.ts +4 -3
- package/build/services/controlplane/stream-messages-sql.js +12 -7
- package/build/services/controlplane/types.d.ts +2 -0
- package/build/services/diagnostics/index.d.ts +53 -7
- package/build/services/diagnostics/index.js +124 -36
- package/build/services/diagnostics/patterns.d.ts +13 -3
- package/build/services/diagnostics/patterns.js +25 -14
- package/build/services/escalation/queries.d.ts +2 -0
- package/build/services/escalation/queries.js +84 -2
- package/build/services/escalation/sql.d.ts +7 -0
- package/build/services/escalation/sql.js +50 -1
- package/build/services/task/resolve.d.ts +5 -3
- package/build/services/task/resolve.js +9 -6
- package/build/services/task/sql.d.ts +6 -1
- package/build/services/task/sql.js +7 -1
- package/build/start/adapters.js +3 -0
- package/build/system/mcp-servers/admin/diagnostics.d.ts +11 -4
- package/build/system/mcp-servers/admin/diagnostics.js +34 -22
- package/build/system/mcp-servers/admin/escalations.d.ts +6 -0
- package/build/system/mcp-servers/admin/escalations.js +182 -54
- package/build/system/mcp-servers/admin/index.js +3 -3
- package/build/system/mcp-servers/admin/schemas.d.ts +101 -6
- package/build/system/mcp-servers/admin/schemas.js +43 -6
- package/build/system/mcp-servers/admin/workflows.js +23 -12
- package/build/system/seed/tool-manifests-admin.d.ts +1324 -562
- package/build/system/seed/tool-manifests-admin.js +10 -2
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/auth.d.ts +1 -1
- package/build/types/startup.d.ts +9 -0
- package/dashboard/dist/assets/{AdminDashboard-0LSslIQ7.js → AdminDashboard-LTiYLuzq.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-0LSslIQ7.js.map → AdminDashboard-LTiYLuzq.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-hqBr1540.js → AgentConfigPage-nlOudfpZ.js} +7 -7
- package/dashboard/dist/assets/{AgentConfigPage-hqBr1540.js.map → AgentConfigPage-nlOudfpZ.js.map} +1 -1
- package/dashboard/dist/assets/AgentDetailPage-BSUnDvGp.js +4 -0
- package/dashboard/dist/assets/AgentDetailPage-BSUnDvGp.js.map +1 -0
- package/dashboard/dist/assets/AgentsPage-DpS0cCYe.js +2 -0
- package/dashboard/dist/assets/AgentsPage-DpS0cCYe.js.map +1 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js.map +1 -0
- package/dashboard/dist/assets/{BotPicker-fFE4GAaO.js → BotPicker-BhKmq7M7.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-fFE4GAaO.js.map → BotPicker-BhKmq7M7.js.map} +1 -1
- package/dashboard/dist/assets/CapabilitiesPage-DLIleX2L.js +2 -0
- package/dashboard/dist/assets/{CapabilitiesPage-DotZ7iQc.js.map → CapabilitiesPage-DLIleX2L.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-D97SYypF.js → CollapsibleSection-D7SVwNVk.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-D97SYypF.js.map → CollapsibleSection-D7SVwNVk.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-C08U6dlv.js → CredentialsPage-fat5Z2U9.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-C08U6dlv.js.map → CredentialsPage-fat5Z2U9.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-C2TuCl4h.js → CronLabel-OOP4P2mJ.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-C2TuCl4h.js.map → CronLabel-OOP4P2mJ.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-C3zGsESx.js → CustomDurationPicker-COacSAXO.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-C3zGsESx.js.map → CustomDurationPicker-COacSAXO.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-Dwpgbueb.js → DropZone-Bfw2YLVS.js} +2 -2
- package/dashboard/dist/assets/{DropZone-Dwpgbueb.js.map → DropZone-Bfw2YLVS.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-DF4m0cv_.js → ElapsedCell-CkfCD-m7.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-DF4m0cv_.js.map → ElapsedCell-CkfCD-m7.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-blGOYKtJ.js → EscalationsOverview-BmI8jxlh.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-blGOYKtJ.js.map → EscalationsOverview-BmI8jxlh.js.map} +1 -1
- package/dashboard/dist/assets/EventTable-CeKfFk3m.js +2 -0
- package/dashboard/dist/assets/EventTable-CeKfFk3m.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-QVAUHmjI.js → GraphInvokePage-BZBPBCSq.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-QVAUHmjI.js.map → GraphInvokePage-BZBPBCSq.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-DvYgvCcO.js → HomePage-dM7U2tl6.js} +2 -2
- package/dashboard/dist/assets/{HomePage-DvYgvCcO.js.map → HomePage-dM7U2tl6.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-D6tQ3ZG2.js → ListToolbar-BJaCzAXS.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-D6tQ3ZG2.js.map → ListToolbar-BJaCzAXS.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-BClbGYb8.js → McpOverview-CvnDG5OB.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-BClbGYb8.js.map → McpOverview-CvnDG5OB.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-DHjfDRdm.js → McpQueryDetailPage-DwmSIcIL.js} +3 -3
- package/dashboard/dist/assets/{McpQueryDetailPage-DHjfDRdm.js.map → McpQueryDetailPage-DwmSIcIL.js.map} +1 -1
- package/dashboard/dist/assets/McpQueryPage-CU6FMEEk.js +2 -0
- package/dashboard/dist/assets/McpQueryPage-CU6FMEEk.js.map +1 -0
- package/dashboard/dist/assets/McpRunDetailPage-8a6lsehv.js +2 -0
- package/dashboard/dist/assets/{McpRunDetailPage-DRbIZMU2.js.map → McpRunDetailPage-8a6lsehv.js.map} +1 -1
- package/dashboard/dist/assets/McpRunsPage-CPMYWgm_.js +2 -0
- package/dashboard/dist/assets/McpRunsPage-CPMYWgm_.js.map +1 -0
- package/dashboard/dist/assets/{NamespacePill-DnJl4Lre.js → NamespacePill-D6btr6e8.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-DnJl4Lre.js.map → NamespacePill-D6btr6e8.js.map} +1 -1
- package/dashboard/dist/assets/OperatorDashboard-g9o7I925.js +2 -0
- package/dashboard/dist/assets/OperatorDashboard-g9o7I925.js.map +1 -0
- package/dashboard/dist/assets/{PageHeader-Bnt2iQQa.js → PageHeader-Dk2Pe44Q.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-Bnt2iQQa.js.map → PageHeader-Dk2Pe44Q.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-BuKc9MKt.js → PageHeaderWithStats-DJQCZ0FR.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-BuKc9MKt.js.map → PageHeaderWithStats-DJQCZ0FR.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-DjQuNERz.js → ProcessDetailPage-ByyWCs2A.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-DjQuNERz.js.map → ProcessDetailPage-ByyWCs2A.js.map} +1 -1
- package/dashboard/dist/assets/ProcessesListPage-BTsXds4l.js +2 -0
- package/dashboard/dist/assets/ProcessesListPage-BTsXds4l.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-DQTT1s9v.js → RolePill-Du-aNnjR.js} +2 -2
- package/dashboard/dist/assets/{RolePill-DQTT1s9v.js.map → RolePill-Du-aNnjR.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-CFCN_mRF.js → RolesPage-B2Kdf-L9.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-CFCN_mRF.js.map → RolesPage-B2Kdf-L9.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-sBxAvlRf.js → RunAsSelector-BzkZXpSX.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-sBxAvlRf.js.map → RunAsSelector-BzkZXpSX.js.map} +1 -1
- package/dashboard/dist/assets/StreamMessageDetail-B6ch-6SY.js +2 -0
- package/dashboard/dist/assets/StreamMessageDetail-B6ch-6SY.js.map +1 -0
- package/dashboard/dist/assets/SwimlaneTimeline-CjvfUkls.js +2 -0
- package/dashboard/dist/assets/SwimlaneTimeline-CjvfUkls.js.map +1 -0
- package/dashboard/dist/assets/{TagInput-B00uqhjX.js → TagInput-CZSeyesi.js} +2 -2
- package/dashboard/dist/assets/{TagInput-B00uqhjX.js.map → TagInput-CZSeyesi.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-CgFN5uWn.js → TaskDetailPage-BjHU6opi.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CgFN5uWn.js.map → TaskDetailPage-BjHU6opi.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-CUsofm0X.js → TaskQueuePill-CQrXrSxk.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-CUsofm0X.js.map → TaskQueuePill-CQrXrSxk.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-qj25rRZL.js → TasksListPage-Bem44zd0.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-qj25rRZL.js.map → TasksListPage-Bem44zd0.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-C7LylVjP.js → TimeAgo-CUIYqSt_.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-C7LylVjP.js.map → TimeAgo-CUIYqSt_.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-CrCtoBlo.js → TimestampCell--tnreI1i.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-CrCtoBlo.js.map → TimestampCell--tnreI1i.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-DjvedZSn.js → ToolPill-O09jsTQM.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-DjvedZSn.js.map → ToolPill-O09jsTQM.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-DYGlB-QW.js → ToolTestPanel-OD99H7Pk.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-DYGlB-QW.js.map → ToolTestPanel-OD99H7Pk.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-DLY6X_R6.js → TopicDetailPage-rh_dsTJI.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-DLY6X_R6.js.map → TopicDetailPage-rh_dsTJI.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-C5nOLsLx.js → TopicsPage-CD_40_v-.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-C5nOLsLx.js.map → TopicsPage-CD_40_v-.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-CgUZyiwm.js → UserName-C__c8BN0.js} +2 -2
- package/dashboard/dist/assets/{UserName-CgUZyiwm.js.map → UserName-C__c8BN0.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-Bw6QdtUO.js +2 -0
- package/dashboard/dist/assets/{WorkflowExecutionPage-CDr5CTM2.js.map → WorkflowExecutionPage-Bw6QdtUO.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-F1oKUzmc.js → WorkflowPill-CS-IsR0U.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-F1oKUzmc.js.map → WorkflowPill-CS-IsR0U.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowsDashboard-BgI-iOP6.js +2 -0
- package/dashboard/dist/assets/WorkflowsDashboard-BgI-iOP6.js.map +1 -0
- package/dashboard/dist/assets/{WorkflowsOverview-CaMeZwde.js → WorkflowsOverview-BRc0QbA7.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CaMeZwde.js.map → WorkflowsOverview-BRc0QbA7.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C3uyCiIX.js → YamlWorkflowDetailPage-DdzspNQR.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C3uyCiIX.js.map → YamlWorkflowDetailPage-DdzspNQR.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-DRnYgsvA.js → YamlWorkflowsPage-Djw5ZbgQ.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-DRnYgsvA.js.map → YamlWorkflowsPage-Djw5ZbgQ.js.map} +1 -1
- package/dashboard/dist/assets/{agents-HMkTc425.js → agents-xstNkF6c.js} +2 -2
- package/dashboard/dist/assets/{agents-HMkTc425.js.map → agents-xstNkF6c.js.map} +1 -1
- package/dashboard/dist/assets/api-path-u2Jf9QXS.js +2 -0
- package/dashboard/dist/assets/api-path-u2Jf9QXS.js.map +1 -0
- package/dashboard/dist/assets/{bots-CDe2jXNx.js → bots-BztAI0s_.js} +2 -2
- package/dashboard/dist/assets/{bots-CDe2jXNx.js.map → bots-BztAI0s_.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-DhnM7Ztb.js → capabilities-CEDwtxNL.js} +2 -2
- package/dashboard/dist/assets/{capabilities-DhnM7Ztb.js.map → capabilities-CEDwtxNL.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-BwIJ9ZC_.js → controlplane-BlGEsRKe.js} +2 -2
- package/dashboard/dist/assets/{controlplane-BwIJ9ZC_.js.map → controlplane-BlGEsRKe.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-BKqSywVA.js → escalation-columns-nVYVt7Gl.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-BKqSywVA.js.map → escalation-columns-nVYVt7Gl.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-Djk5XKmX.js → escalation-iI4QKlP-.js} +2 -2
- package/dashboard/dist/assets/{escalation-Djk5XKmX.js.map → escalation-iI4QKlP-.js.map} +1 -1
- package/dashboard/dist/assets/{index-BW-fIquS.js → index-B5qsp8lb.js} +2 -2
- package/dashboard/dist/assets/{index-BW-fIquS.js.map → index-B5qsp8lb.js.map} +1 -1
- package/dashboard/dist/assets/{index-DBi9Q9Hw.js → index-B7VC0vjf.js} +2 -2
- package/dashboard/dist/assets/{index-DBi9Q9Hw.js.map → index-B7VC0vjf.js.map} +1 -1
- package/dashboard/dist/assets/{index-CPIYW81T.js → index-BUmnJ4eT.js} +2 -2
- package/dashboard/dist/assets/{index-CPIYW81T.js.map → index-BUmnJ4eT.js.map} +1 -1
- package/dashboard/dist/assets/{index-BNkpns_E.js → index-CAqEy2Db.js} +2 -2
- package/dashboard/dist/assets/{index-BNkpns_E.js.map → index-CAqEy2Db.js.map} +1 -1
- package/dashboard/dist/assets/{index-BYpWOUEe.js → index-CMviPjXQ.js} +2 -2
- package/dashboard/dist/assets/{index-BYpWOUEe.js.map → index-CMviPjXQ.js.map} +1 -1
- package/dashboard/dist/assets/index-CP0JEQhZ.js +6 -0
- package/dashboard/dist/assets/{index-E2hQ8DIi.js.map → index-CP0JEQhZ.js.map} +1 -1
- package/dashboard/dist/assets/index-CUfgt3mV.js +2 -0
- package/dashboard/dist/assets/index-CUfgt3mV.js.map +1 -0
- package/dashboard/dist/assets/index-ClvYSNZd.css +1 -0
- package/dashboard/dist/assets/{index-BFQ2t-p1.js → index-D1xVY4JR.js} +27 -27
- package/dashboard/dist/assets/index-D1xVY4JR.js.map +1 -0
- package/dashboard/dist/assets/{index-rUST2YsO.js → index-DPLy-jwO.js} +2 -2
- package/dashboard/dist/assets/{index-rUST2YsO.js.map → index-DPLy-jwO.js.map} +1 -1
- package/dashboard/dist/assets/{index-DRIJM1L-.js → index-DjJ6VCuc.js} +2 -2
- package/dashboard/dist/assets/{index-DRIJM1L-.js.map → index-DjJ6VCuc.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dhy2PVAw.js → index-Dsi5o7dx.js} +2 -2
- package/dashboard/dist/assets/{index-Dhy2PVAw.js.map → index-Dsi5o7dx.js.map} +1 -1
- package/dashboard/dist/assets/{index-mEQ6kCnt.js → index-DzVYjoex.js} +3 -3
- package/dashboard/dist/assets/{index-mEQ6kCnt.js.map → index-DzVYjoex.js.map} +1 -1
- package/dashboard/dist/assets/index-FuhiJuHL.js +2 -0
- package/dashboard/dist/assets/index-FuhiJuHL.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-jQE3dBVi.js → knowledge-BKg0cr39.js} +2 -2
- package/dashboard/dist/assets/{knowledge-jQE3dBVi.js.map → knowledge-BKg0cr39.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-DtEGfZiz.js → mcp-CVdHpKlz.js} +2 -2
- package/dashboard/dist/assets/{mcp-DtEGfZiz.js.map → mcp-CVdHpKlz.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CiTLVuDC.js → mcp-query-CYxZcbwQ.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CiTLVuDC.js.map → mcp-query-CYxZcbwQ.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-ClNdzuUK.js → pipelines-BOEv7P6O.js} +2 -2
- package/dashboard/dist/assets/{pipelines-ClNdzuUK.js.map → pipelines-BOEv7P6O.js.map} +1 -1
- package/dashboard/dist/assets/{roles-BmHbRaPT.js → roles-c5cV2QYQ.js} +2 -2
- package/dashboard/dist/assets/{roles-BmHbRaPT.js.map → roles-c5cV2QYQ.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-BPONxLcq.js → tasks-JV3BjeEF.js} +2 -2
- package/dashboard/dist/assets/{tasks-BPONxLcq.js.map → tasks-JV3BjeEF.js.map} +1 -1
- package/dashboard/dist/assets/{topics-XREPRxwo.js → topics-BDaPnhnQ.js} +2 -2
- package/dashboard/dist/assets/{topics-XREPRxwo.js.map → topics-BDaPnhnQ.js.map} +1 -1
- package/dashboard/dist/assets/useDebouncedValue-BA5S7V54.js +2 -0
- package/dashboard/dist/assets/useDebouncedValue-BA5S7V54.js.map +1 -0
- package/dashboard/dist/assets/{useEventHooks-M7SaEfel.js → useEventHooks-a7Dnh8G4.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-M7SaEfel.js.map → useEventHooks-a7Dnh8G4.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BQHGkdBv.js → useNamespace-BSwEV0e9.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BQHGkdBv.js.map → useNamespace-BSwEV0e9.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-B7JtUGSE.js → useYamlActivityEvents-BuqvL-cn.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-B7JtUGSE.js.map → useYamlActivityEvents-BuqvL-cn.js.map} +1 -1
- package/dashboard/dist/assets/{users-BpjSNhQe.js → users-Pn85K_3C.js} +2 -2
- package/dashboard/dist/assets/{users-BpjSNhQe.js.map → users-Pn85K_3C.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-Dj2F0Jrb.js → vendor-icons-D5ycKPA2.js} +114 -109
- package/dashboard/dist/assets/vendor-icons-D5ycKPA2.js.map +1 -0
- package/dashboard/dist/assets/{workflows-k4jE0ECC.js → workflows-DF-9Voyi.js} +2 -2
- package/dashboard/dist/assets/{workflows-k4jE0ECC.js.map → workflows-DF-9Voyi.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-DpviSLEL.js → yaml-workflows-2roKaSSK.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-DpviSLEL.js.map → yaml-workflows-2roKaSSK.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/mcp/admin.md +242 -5
- package/docs/mcp.md +102 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/AgentDetailPage-bnXR1ezD.js +0 -4
- package/dashboard/dist/assets/AgentDetailPage-bnXR1ezD.js.map +0 -1
- package/dashboard/dist/assets/AgentsPage-tJIlrnfM.js +0 -2
- package/dashboard/dist/assets/AgentsPage-tJIlrnfM.js.map +0 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-B7gEfcw3.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-B7gEfcw3.js.map +0 -1
- package/dashboard/dist/assets/CapabilitiesPage-DotZ7iQc.js +0 -2
- package/dashboard/dist/assets/EventTable-DzrMH-ux.js +0 -2
- package/dashboard/dist/assets/EventTable-DzrMH-ux.js.map +0 -1
- package/dashboard/dist/assets/McpQueryPage-qtEuMDrd.js +0 -2
- package/dashboard/dist/assets/McpQueryPage-qtEuMDrd.js.map +0 -1
- package/dashboard/dist/assets/McpRunDetailPage-DRbIZMU2.js +0 -2
- package/dashboard/dist/assets/McpRunsPage-CMfwXQ4R.js +0 -2
- package/dashboard/dist/assets/McpRunsPage-CMfwXQ4R.js.map +0 -1
- package/dashboard/dist/assets/OperatorDashboard-Dq2pfV68.js +0 -2
- package/dashboard/dist/assets/OperatorDashboard-Dq2pfV68.js.map +0 -1
- package/dashboard/dist/assets/ProcessesListPage-vJimii8-.js +0 -2
- package/dashboard/dist/assets/ProcessesListPage-vJimii8-.js.map +0 -1
- package/dashboard/dist/assets/SwimlaneTimeline-2Eet4OOL.js +0 -2
- package/dashboard/dist/assets/SwimlaneTimeline-2Eet4OOL.js.map +0 -1
- package/dashboard/dist/assets/WorkflowExecutionPage-CDr5CTM2.js +0 -2
- package/dashboard/dist/assets/WorkflowsDashboard-tCBP0i0Y.js +0 -2
- package/dashboard/dist/assets/WorkflowsDashboard-tCBP0i0Y.js.map +0 -1
- package/dashboard/dist/assets/index-BFQ2t-p1.js.map +0 -1
- package/dashboard/dist/assets/index-Cydgj_Co.js +0 -2
- package/dashboard/dist/assets/index-Cydgj_Co.js.map +0 -1
- package/dashboard/dist/assets/index-DSki_Ub6.css +0 -1
- package/dashboard/dist/assets/index-E2hQ8DIi.js +0 -6
- package/dashboard/dist/assets/index-u08vQUaw.js +0 -2
- package/dashboard/dist/assets/index-u08vQUaw.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-Dj2F0Jrb.js.map +0 -1
|
@@ -15,34 +15,56 @@ const ESCALATION_BY_JOB = `
|
|
|
15
15
|
WHERE workflow_id = $1
|
|
16
16
|
ORDER BY created_at DESC LIMIT 5
|
|
17
17
|
`;
|
|
18
|
+
// "No recent status change" is NOT the same as "broken": HotMesh only bumps
|
|
19
|
+
// jobs.updated_at when status changes, so a frozen updated_at is the normal
|
|
20
|
+
// signature of a workflow waiting at a condition()/waitFor()/sleepFor(). We
|
|
21
|
+
// classify each row with `has_open_escalation` (a cheap, workflow_id-indexed
|
|
22
|
+
// EXISTS bounded by the outer LIMIT) so callers can tell a healthy wait
|
|
23
|
+
// (`likely = waiting`) from a candidate worth a closer look (`no_recent_progress`).
|
|
18
24
|
const STALLED_JOBS = (appId) => `
|
|
19
25
|
SELECT
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
q.*,
|
|
27
|
+
CASE WHEN q.has_open_escalation THEN 'waiting' ELSE 'no_recent_progress' END AS likely
|
|
28
|
+
FROM (
|
|
29
|
+
SELECT
|
|
30
|
+
REPLACE(j.key, $1, '') AS workflow_id,
|
|
31
|
+
j.entity AS workflow_type,
|
|
32
|
+
j.status,
|
|
33
|
+
j.created_at,
|
|
34
|
+
j.updated_at,
|
|
35
|
+
EXTRACT(EPOCH FROM (NOW() - j.updated_at)) * 1000 AS idle_ms,
|
|
36
|
+
EXISTS (
|
|
37
|
+
SELECT 1 FROM public.hmsh_escalations e
|
|
38
|
+
WHERE e.workflow_id = REPLACE(j.key, $1, '') AND e.status = 'pending'
|
|
39
|
+
) AS has_open_escalation
|
|
40
|
+
FROM "${appId}".jobs j
|
|
41
|
+
WHERE j.status > 0
|
|
42
|
+
AND j.is_live = TRUE
|
|
43
|
+
AND j.updated_at < NOW() - ($2 || ' minutes')::INTERVAL
|
|
44
|
+
AND ($3::text IS NULL OR j.entity = $3)
|
|
45
|
+
ORDER BY j.updated_at ASC
|
|
46
|
+
LIMIT $4
|
|
47
|
+
) q
|
|
33
48
|
`;
|
|
49
|
+
// Bounded by a recent-time window ($1 = within_hours) so this never degenerates
|
|
50
|
+
// into a full-history scan of the HASH-partitioned worker_streams table. The
|
|
51
|
+
// `aid LIKE '%/waiter'` predicate is a suffix wildcard (not index-backed), so
|
|
52
|
+
// the time window is what keeps the intermediate scan small; widening it trades
|
|
53
|
+
// cost for reach. The NOT EXISTS anti-join on hmsh_escalations (workflow_id
|
|
54
|
+
// indexed) is the high-value signal — a suspended waiter with no escalation row.
|
|
34
55
|
const ORPHANED_SIGNALS = (appId) => `
|
|
35
56
|
WITH committed_waiters AS (
|
|
36
57
|
SELECT
|
|
37
|
-
ws.jid
|
|
38
|
-
ws.message->'data'->>'signalId'
|
|
39
|
-
ws.message->'data'->>'queueConfig'
|
|
58
|
+
ws.jid AS job_id,
|
|
59
|
+
ws.message::jsonb->'data'->>'signalId' AS signal_id,
|
|
60
|
+
ws.message::jsonb->'data'->>'queueConfig' AS queue_config,
|
|
40
61
|
ws.workflow_name,
|
|
41
|
-
ws.created_at
|
|
62
|
+
ws.created_at AS suspended_at
|
|
42
63
|
FROM "${appId}".worker_streams ws
|
|
43
64
|
WHERE ws.aid LIKE '%/waiter'
|
|
44
65
|
AND ws.expired_at IS NOT NULL
|
|
45
66
|
AND ws.dead_lettered_at IS NULL
|
|
67
|
+
AND ws.created_at > NOW() - ($1 || ' hours')::INTERVAL
|
|
46
68
|
),
|
|
47
69
|
live_jobs AS (
|
|
48
70
|
SELECT REPLACE(key, 'hmsh:${appId}:j:', '') AS wid
|
|
@@ -54,7 +76,7 @@ const ORPHANED_SIGNALS = (appId) => `
|
|
|
54
76
|
cw.signal_id,
|
|
55
77
|
cw.workflow_name,
|
|
56
78
|
cw.suspended_at,
|
|
57
|
-
EXTRACT(EPOCH FROM (NOW() - cw.suspended_at)) * 1000 AS
|
|
79
|
+
EXTRACT(EPOCH FROM (NOW() - cw.suspended_at)) * 1000 AS waiting_ms,
|
|
58
80
|
cw.queue_config IS NULL AS missing_queue_config
|
|
59
81
|
FROM committed_waiters cw
|
|
60
82
|
JOIN live_jobs lj ON lj.wid = cw.job_id
|
|
@@ -62,11 +84,43 @@ const ORPHANED_SIGNALS = (appId) => `
|
|
|
62
84
|
SELECT 1 FROM public.hmsh_escalations e WHERE e.workflow_id = cw.job_id
|
|
63
85
|
)
|
|
64
86
|
ORDER BY cw.suspended_at ASC
|
|
65
|
-
LIMIT $
|
|
87
|
+
LIMIT $2
|
|
66
88
|
`;
|
|
89
|
+
/** Default cap on events returned in the payload — keeps a high-activity workflow from returning a huge response. */
|
|
90
|
+
const DEFAULT_MAX_EVENTS = 500;
|
|
91
|
+
/** Strings larger than this (UTF-8 bytes) are summarized rather than returned in full. */
|
|
92
|
+
const LARGE_STRING_BYTES = 1024;
|
|
93
|
+
/** How many leading characters of a summarized string to keep as a preview. */
|
|
94
|
+
const PREVIEW_CHARS = 200;
|
|
95
|
+
/** Recursion depth cap for summarizing nested event attributes. */
|
|
96
|
+
const SUMMARIZE_MAX_DEPTH = 6;
|
|
97
|
+
/** Replace an over-threshold string with a bounded summary; pass small strings through unchanged. */
|
|
98
|
+
function summarizeString(value) {
|
|
99
|
+
const bytes = Buffer.byteLength(value, 'utf8');
|
|
100
|
+
if (bytes <= LARGE_STRING_BYTES)
|
|
101
|
+
return value;
|
|
102
|
+
return { bytes, preview: value.slice(0, PREVIEW_CHARS), truncated: true };
|
|
103
|
+
}
|
|
104
|
+
/** Recursively summarize large string leaves in an event attribute tree (result/output/input/error payloads). */
|
|
105
|
+
function summarizeDeep(value, depth = 0) {
|
|
106
|
+
if (typeof value === 'string')
|
|
107
|
+
return summarizeString(value);
|
|
108
|
+
if (value === null || typeof value !== 'object' || depth >= SUMMARIZE_MAX_DEPTH)
|
|
109
|
+
return value;
|
|
110
|
+
if (Array.isArray(value))
|
|
111
|
+
return value.map((v) => summarizeDeep(v, depth + 1));
|
|
112
|
+
const out = {};
|
|
113
|
+
for (const [k, v] of Object.entries(value)) {
|
|
114
|
+
out[k] = summarizeDeep(v, depth + 1);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
67
118
|
// ── diagnoseJob ──────────────────────────────────────────────────────────────
|
|
68
|
-
async function diagnoseJob(workflowId, appId = 'durable') {
|
|
119
|
+
async function diagnoseJob(workflowId, appId = 'durable', options = {}) {
|
|
69
120
|
const pool = (0, db_1.getPool)();
|
|
121
|
+
const maxEvents = Math.max(options.maxEvents ?? DEFAULT_MAX_EVENTS, 1);
|
|
122
|
+
const includeEvents = options.verbosity === 'full' || !!options.include?.includes('events');
|
|
123
|
+
const includeStreams = options.verbosity === 'full' || !!options.include?.includes('streams');
|
|
70
124
|
let resolved;
|
|
71
125
|
try {
|
|
72
126
|
resolved = await (0, resolve_1.resolveWorkflowHandle)(workflowId);
|
|
@@ -91,9 +145,14 @@ async function diagnoseJob(workflowId, appId = 'durable') {
|
|
|
91
145
|
const isFailed = events.some(e => e.event_type === 'workflow_execution_failed');
|
|
92
146
|
const status = isCompleted ? 'completed' : isFailed ? 'failed' : 'running';
|
|
93
147
|
const lastEventAt = lastEvent?.event_time ?? null;
|
|
94
|
-
const
|
|
148
|
+
const idleMs = status === 'running' && lastEventAt
|
|
95
149
|
? now - new Date(lastEventAt).getTime()
|
|
96
150
|
: null;
|
|
151
|
+
// Cap the events returned in the payload (keep the most recent) so a
|
|
152
|
+
// high-activity workflow can't produce a huge response. Pattern matching
|
|
153
|
+
// still runs over the full in-memory event set.
|
|
154
|
+
const eventsTruncated = events.length > maxEvents;
|
|
155
|
+
const cappedEvents = eventsTruncated ? events.slice(-maxEvents) : events;
|
|
97
156
|
const allMessages = [...workerMessages, ...engineMessages];
|
|
98
157
|
const streamSummary = {
|
|
99
158
|
worker_total: workerMessages.length,
|
|
@@ -104,12 +163,12 @@ async function diagnoseJob(workflowId, appId = 'durable') {
|
|
|
104
163
|
};
|
|
105
164
|
const findings = execution
|
|
106
165
|
? (0, patterns_1.matchPatterns)(execution, workerMessages, engineMessages, escalationRow)
|
|
107
|
-
: [{ condition: 'export_unavailable', confidence: 0.9, severity: 'warning', evidence: ['Execution export unavailable — job may have expired or HotMesh engine is not running'],
|
|
108
|
-
|
|
166
|
+
: [{ condition: 'export_unavailable', confidence: 0.9, severity: 'warning', evidence: ['Execution export unavailable — job may have expired or HotMesh engine is not running'], guidance: [] }];
|
|
167
|
+
const diagnosis = {
|
|
109
168
|
workflow_id: workflowId,
|
|
110
169
|
app_id: appId,
|
|
111
170
|
status,
|
|
112
|
-
|
|
171
|
+
idle_for_ms: idleMs,
|
|
113
172
|
workflow_type: resolved.workflowName,
|
|
114
173
|
last_event_at: lastEventAt,
|
|
115
174
|
stream_summary: streamSummary,
|
|
@@ -123,33 +182,62 @@ async function diagnoseJob(workflowId, appId = 'durable') {
|
|
|
123
182
|
created_at: escalationRow?.created_at ?? null,
|
|
124
183
|
},
|
|
125
184
|
findings,
|
|
126
|
-
|
|
127
|
-
|
|
185
|
+
total_events: events.length,
|
|
186
|
+
events_truncated: eventsTruncated,
|
|
128
187
|
};
|
|
188
|
+
// Heavy sections are opt-in. Large string payloads are summarized either way
|
|
189
|
+
// so an included section can't reintroduce the 100–200KB blow-up.
|
|
190
|
+
if (includeEvents) {
|
|
191
|
+
diagnosis.execution_events = cappedEvents.map((e) => e && typeof e === 'object'
|
|
192
|
+
? { ...e, attributes: summarizeDeep(e.attributes) }
|
|
193
|
+
: e);
|
|
194
|
+
}
|
|
195
|
+
if (includeStreams) {
|
|
196
|
+
diagnosis.stream_messages = {
|
|
197
|
+
worker: summarizeMessages(workerMessages),
|
|
198
|
+
engine: summarizeMessages(engineMessages),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
diagnosis.raw_messages = {
|
|
203
|
+
hint: 'Use list_stream_messages (Control Plane) filtered by jid (and aid/dad) for the full raw message payloads.',
|
|
204
|
+
jid: workflowId,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return diagnosis;
|
|
208
|
+
}
|
|
209
|
+
/** Summarize the large `message` payload of each stream message; everything else passes through. */
|
|
210
|
+
function summarizeMessages(messages) {
|
|
211
|
+
return messages.map((m) => ({ ...m, message: summarizeString(m.message) }));
|
|
129
212
|
}
|
|
130
213
|
// ── findStalledJobs ──────────────────────────────────────────────────────────
|
|
131
214
|
async function findStalledJobs(params) {
|
|
132
215
|
const appId = params.appId ?? 'durable';
|
|
133
|
-
const
|
|
216
|
+
const idleMinutes = params.idleMinutes ?? 5;
|
|
134
217
|
const limit = Math.min(params.limit ?? 50, 200);
|
|
135
218
|
const keyPrefix = `hmsh:${appId}:j:`;
|
|
219
|
+
// Fail loud: let a query error surface (the api layer maps it to a 500). A
|
|
220
|
+
// silent empty result would hide a missing schema or a broken query.
|
|
136
221
|
const pool = (0, db_1.getPool)();
|
|
137
222
|
const { rows } = await pool.query(STALLED_JOBS(appId), [
|
|
138
223
|
keyPrefix,
|
|
139
|
-
String(
|
|
224
|
+
String(idleMinutes),
|
|
140
225
|
params.workflowType ?? null,
|
|
141
226
|
limit,
|
|
142
|
-
])
|
|
227
|
+
]);
|
|
143
228
|
return { jobs: rows, total: rows.length };
|
|
144
229
|
}
|
|
145
230
|
// ── findOrphanedSignals ──────────────────────────────────────────────────────
|
|
146
231
|
async function findOrphanedSignals(params) {
|
|
147
232
|
const appId = params.appId ?? 'durable';
|
|
233
|
+
const withinHours = Math.min(Math.max(params.withinHours ?? 24, 1), 720);
|
|
148
234
|
const limit = Math.min(params.limit ?? 100, 500);
|
|
235
|
+
// Fail loud: let a query error surface (the api layer maps it to a 500). A
|
|
236
|
+
// silent empty result would hide a missing schema or a broken query — exactly
|
|
237
|
+
// what previously masked a JSON-operator bug on the text `message` column.
|
|
149
238
|
const pool = (0, db_1.getPool)();
|
|
150
|
-
const { rows } = await pool.query(ORPHANED_SIGNALS(appId), [limit])
|
|
151
|
-
|
|
152
|
-
return { orphans: rows, total: rows.length };
|
|
239
|
+
const { rows } = await pool.query(ORPHANED_SIGNALS(appId), [String(withinHours), limit]);
|
|
240
|
+
return { orphans: rows, total: rows.length, within_hours: withinHours };
|
|
153
241
|
}
|
|
154
242
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
155
243
|
function notFound(workflowId, appId) {
|
|
@@ -157,13 +245,13 @@ function notFound(workflowId, appId) {
|
|
|
157
245
|
workflow_id: workflowId,
|
|
158
246
|
app_id: appId,
|
|
159
247
|
status: 'not_found',
|
|
160
|
-
|
|
248
|
+
idle_for_ms: null,
|
|
161
249
|
workflow_type: null,
|
|
162
250
|
last_event_at: null,
|
|
163
251
|
stream_summary: { worker_total: 0, engine_total: 0, dead_lettered: 0, pending: 0, in_flight: 0 },
|
|
164
252
|
escalation: { exists: false, id: null, status: null, signal_key: null, role: null, type: null, created_at: null },
|
|
165
|
-
findings: [{ condition: 'not_found', confidence: 1, severity: 'critical', evidence: ['Workflow ID not found — no task record or job entity'],
|
|
166
|
-
|
|
167
|
-
|
|
253
|
+
findings: [{ condition: 'not_found', confidence: 1, severity: 'critical', evidence: ['Workflow ID not found — no task record or job entity'], guidance: [] }],
|
|
254
|
+
total_events: 0,
|
|
255
|
+
events_truncated: false,
|
|
168
256
|
};
|
|
169
257
|
}
|
|
@@ -5,15 +5,25 @@ export interface Finding {
|
|
|
5
5
|
confidence: number;
|
|
6
6
|
severity: 'critical' | 'warning' | 'info';
|
|
7
7
|
evidence: string[];
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Read-only diagnostic guidance: what the condition means and where a human
|
|
10
|
+
* should look next. This is NOT recovery — HotMesh state only moves forward
|
|
11
|
+
* and cannot be unwound, so guidance never prescribes mutating job state.
|
|
12
|
+
*/
|
|
13
|
+
guidance: Guidance[];
|
|
9
14
|
}
|
|
10
|
-
export interface
|
|
15
|
+
export interface Guidance {
|
|
11
16
|
action: string;
|
|
12
17
|
note: string;
|
|
13
18
|
[key: string]: unknown;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
|
-
* Match event stream + stream messages against known
|
|
21
|
+
* Match event stream + stream messages against known workflow conditions.
|
|
17
22
|
* Returns findings ordered by severity (critical first).
|
|
23
|
+
*
|
|
24
|
+
* Healthy long-waits are a first-class outcome here, not an omission: a
|
|
25
|
+
* workflow suspended at `condition()`/`waitFor()`/`sleepFor()` can sit for days
|
|
26
|
+
* legitimately, so we classify those explicitly rather than treating a frozen
|
|
27
|
+
* job as broken.
|
|
18
28
|
*/
|
|
19
29
|
export declare function matchPatterns(execution: WorkflowExecution, workerMessages: StreamMessage[], engineMessages: StreamMessage[], escalationRow: Record<string, unknown> | null): Finding[];
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.matchPatterns = matchPatterns;
|
|
4
4
|
/**
|
|
5
|
-
* Match event stream + stream messages against known
|
|
5
|
+
* Match event stream + stream messages against known workflow conditions.
|
|
6
6
|
* Returns findings ordered by severity (critical first).
|
|
7
|
+
*
|
|
8
|
+
* Healthy long-waits are a first-class outcome here, not an omission: a
|
|
9
|
+
* workflow suspended at `condition()`/`waitFor()`/`sleepFor()` can sit for days
|
|
10
|
+
* legitimately, so we classify those explicitly rather than treating a frozen
|
|
11
|
+
* job as broken.
|
|
7
12
|
*/
|
|
8
13
|
function matchPatterns(execution, workerMessages, engineMessages, escalationRow) {
|
|
9
14
|
const findings = [];
|
|
@@ -36,13 +41,18 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
36
41
|
`${openSignals.length} open signal(s) with no matching resolution`,
|
|
37
42
|
`No escalation row in hmsh_escalations for this workflow`,
|
|
38
43
|
queueConfig == null
|
|
39
|
-
? `Worker result missing queueConfig — escalation
|
|
40
|
-
: `Worker result has queueConfig but escalation
|
|
41
|
-
...(signalId ? [`
|
|
44
|
+
? `Worker result missing queueConfig — no escalation row was written (pre-0.22 SDK)`
|
|
45
|
+
: `Worker result has queueConfig but no escalation row was written`,
|
|
46
|
+
...(signalId ? [`Awaited signal key: ${signalId}`] : []),
|
|
42
47
|
],
|
|
43
|
-
|
|
44
|
-
{
|
|
45
|
-
|
|
48
|
+
guidance: [
|
|
49
|
+
{
|
|
50
|
+
action: 'inspect_worker_result',
|
|
51
|
+
note: signalId
|
|
52
|
+
? `Workflow is parked on signal "${signalId}" with no escalation backing it, so the normal escalation lifecycle will not resume it. Inspect the worker result payload via the stream browser (list_stream_messages filtered by jid) to confirm the missing queueConfig.`
|
|
53
|
+
: 'Workflow is parked on a signal with no escalation backing it. Inspect the worker result payload via the stream browser (list_stream_messages filtered by jid).',
|
|
54
|
+
},
|
|
55
|
+
{ action: 'escalate_to_engineering', note: 'Resolving an orphaned wait requires engineering review of the deploy/SDK version. Do not hand-edit escalation or job state — HotMesh state cannot be unwound.' },
|
|
46
56
|
],
|
|
47
57
|
});
|
|
48
58
|
}
|
|
@@ -59,11 +69,12 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
59
69
|
confidence: 0.99,
|
|
60
70
|
severity: 'info',
|
|
61
71
|
evidence: [
|
|
62
|
-
`
|
|
72
|
+
`Healthy wait — workflow suspended at a condition with a pending escalation`,
|
|
73
|
+
`This is NOT stalled: a workflow can wait here for days legitimately. A frozen updated_at is the normal signature of a wait, not a fault.`,
|
|
63
74
|
...(escalationRow.role ? [`Role: ${escalationRow.role}, Type: ${escalationRow.type}`] : []),
|
|
64
75
|
...(escalationRow.id ? [`Escalation id: ${escalationRow.id}`] : []),
|
|
65
76
|
],
|
|
66
|
-
|
|
77
|
+
guidance: [{ action: 'none', note: 'No action required — waiting for a human to claim and resolve the escalation through the normal lifecycle.' }],
|
|
67
78
|
});
|
|
68
79
|
}
|
|
69
80
|
// ── Pattern: dead_lettered_activity ──────────────────────────────────────
|
|
@@ -77,7 +88,7 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
77
88
|
`${deadWorker.length} worker message(s) dead-lettered, ${deadEngine.length} engine message(s) dead-lettered`,
|
|
78
89
|
...samples.map(m => `aid: ${m.aid ?? 'engine'}, retries: ${m.retry_attempt}/${m.max_retry_attempts}, stream: ${m.stream_name}`),
|
|
79
90
|
],
|
|
80
|
-
|
|
91
|
+
guidance: [{
|
|
81
92
|
action: 'investigate_dead_letter',
|
|
82
93
|
count: deadWorker.length + deadEngine.length,
|
|
83
94
|
note: 'Message exhausted retries — check worker health and DB connectivity',
|
|
@@ -91,7 +102,7 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
91
102
|
confidence: 0.85,
|
|
92
103
|
severity: 'warning',
|
|
93
104
|
evidence: leaks.map(m => `aid: ${m.aid}, claimed by: ${m.reserved_by ?? 'unknown'}, open for ${Math.round((now - new Date(m.reserved_at).getTime()) / 1000)}s`),
|
|
94
|
-
|
|
105
|
+
guidance: [{ action: 'check_worker_health', note: 'Worker claimed message but never ACKd — check for crashed workers' }],
|
|
95
106
|
});
|
|
96
107
|
}
|
|
97
108
|
// ── Pattern: terminal_failure ─────────────────────────────────────────────
|
|
@@ -106,7 +117,7 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
106
117
|
`Workflow terminated with failure`,
|
|
107
118
|
...(attrs?.error ? [`Error: ${attrs.error}`] : []),
|
|
108
119
|
],
|
|
109
|
-
|
|
120
|
+
guidance: [{ action: 'none', note: 'Workflow terminated — review error and restart if needed' }],
|
|
110
121
|
});
|
|
111
122
|
}
|
|
112
123
|
// ── Pattern: never_started ────────────────────────────────────────────────
|
|
@@ -117,7 +128,7 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
117
128
|
confidence: 0.90,
|
|
118
129
|
severity: 'critical',
|
|
119
130
|
evidence: [`Workflow created but no activities have run — engine may not be consuming this stream`],
|
|
120
|
-
|
|
131
|
+
guidance: [{ action: 'check_engine_health', note: 'Check engine health and stream backlog for this workflow type' }],
|
|
121
132
|
});
|
|
122
133
|
}
|
|
123
134
|
// ── Default: healthy ──────────────────────────────────────────────────────
|
|
@@ -128,7 +139,7 @@ function matchPatterns(execution, workerMessages, engineMessages, escalationRow)
|
|
|
128
139
|
confidence: 0.95,
|
|
129
140
|
severity: 'info',
|
|
130
141
|
evidence: [isCompleted ? 'Workflow completed normally' : 'Workflow is running — no anomalies detected'],
|
|
131
|
-
|
|
142
|
+
guidance: [{ action: 'none', note: 'No intervention required' }],
|
|
132
143
|
});
|
|
133
144
|
}
|
|
134
145
|
// Critical first
|
|
@@ -15,6 +15,7 @@ export declare function listEscalations(filters: {
|
|
|
15
15
|
visibleRoles?: string[];
|
|
16
16
|
sort_by?: string;
|
|
17
17
|
order?: string;
|
|
18
|
+
search?: string;
|
|
18
19
|
}): Promise<{
|
|
19
20
|
escalations: LTEscalationRecord[];
|
|
20
21
|
total: number;
|
|
@@ -32,6 +33,7 @@ export declare function listAvailableEscalations(filters: {
|
|
|
32
33
|
visibleRoles?: string[];
|
|
33
34
|
sort_by?: string;
|
|
34
35
|
order?: string;
|
|
36
|
+
search?: string;
|
|
35
37
|
}): Promise<{
|
|
36
38
|
escalations: LTEscalationRecord[];
|
|
37
39
|
total: number;
|
|
@@ -4,9 +4,11 @@ exports.getEscalationStats = getEscalationStats;
|
|
|
4
4
|
exports.listDistinctTypes = listDistinctTypes;
|
|
5
5
|
exports.listEscalations = listEscalations;
|
|
6
6
|
exports.listAvailableEscalations = listAvailableEscalations;
|
|
7
|
+
const db_1 = require("../../lib/db");
|
|
7
8
|
const client_1 = require("./client");
|
|
8
9
|
const map_1 = require("./map");
|
|
9
10
|
const types_1 = require("./types");
|
|
11
|
+
const sql_1 = require("./sql");
|
|
10
12
|
/**
|
|
11
13
|
* Default sort is priority ASC, created_at ASC. A user-chosen `sort_by` maps to
|
|
12
14
|
* a single column (DESC unless `order='asc'`), matching the legacy behavior.
|
|
@@ -20,6 +22,50 @@ function buildOrderBy(sortBy, order) {
|
|
|
20
22
|
{ column: 'created_at', direction: 'asc' },
|
|
21
23
|
];
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* ORDER BY clause for the raw search query. `sortBy` is checked against the
|
|
27
|
+
* SORTABLE_COLUMNS whitelist before interpolation, so it is injection-safe.
|
|
28
|
+
*/
|
|
29
|
+
function buildSearchOrderBy(sortBy, order) {
|
|
30
|
+
const dir = order === 'asc' ? 'ASC' : 'DESC';
|
|
31
|
+
if (sortBy && types_1.SORTABLE_COLUMNS.has(sortBy)) {
|
|
32
|
+
return `${sortBy} ${dir}`;
|
|
33
|
+
}
|
|
34
|
+
return 'priority ASC, created_at ASC';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Server-side free-text search over the `lt_escalations` view. Runs when a
|
|
38
|
+
* caller supplies a non-empty `search` term — the SDK `client.list()` cannot do
|
|
39
|
+
* free-text, so this is raw SQL on the shared table (see ./sql.ts). All other
|
|
40
|
+
* filters combine with the term (AND).
|
|
41
|
+
*/
|
|
42
|
+
async function searchEscalations(params) {
|
|
43
|
+
await (0, client_1.ensureEscalationCompatView)();
|
|
44
|
+
const pool = (0, db_1.getPool)();
|
|
45
|
+
// Coerce empty strings to NULL so an absent filter (e.g. assigned_to='') does
|
|
46
|
+
// not become `column = ''` and match zero rows. Mirrors the SDK path's
|
|
47
|
+
// `if (filters.x)` truthiness guards.
|
|
48
|
+
const filterArgs = [
|
|
49
|
+
params.status || null,
|
|
50
|
+
params.role || null,
|
|
51
|
+
params.roles && params.roles.length ? params.roles : null,
|
|
52
|
+
params.type || null,
|
|
53
|
+
params.subtype || null,
|
|
54
|
+
params.priority ?? null,
|
|
55
|
+
params.assigned_to || null,
|
|
56
|
+
params.available ?? null,
|
|
57
|
+
params.search,
|
|
58
|
+
];
|
|
59
|
+
const orderBy = buildSearchOrderBy(params.sort_by, params.order);
|
|
60
|
+
const [rows, countRows] = await Promise.all([
|
|
61
|
+
pool.query((0, sql_1.searchEscalationsQuery)(orderBy), [...filterArgs, params.limit, params.offset]),
|
|
62
|
+
pool.query(sql_1.COUNT_SEARCH_ESCALATIONS, filterArgs),
|
|
63
|
+
]);
|
|
64
|
+
return {
|
|
65
|
+
escalations: (0, map_1.toEscalationRecords)(rows.rows),
|
|
66
|
+
total: countRows.rows[0]?.total ?? 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
23
69
|
async function getEscalationStats(visibleRoles, period) {
|
|
24
70
|
const client = await (0, client_1.escalations)();
|
|
25
71
|
return client.stats({
|
|
@@ -32,6 +78,26 @@ async function listDistinctTypes() {
|
|
|
32
78
|
return client.listDistinctTypes();
|
|
33
79
|
}
|
|
34
80
|
async function listEscalations(filters) {
|
|
81
|
+
// Active claim semantics: assigned_to-active and `claimed` both mean "held now".
|
|
82
|
+
const heldNow = !!(filters.assigned_to || filters.claimed);
|
|
83
|
+
// Free-text search → server-side SQL path (the SDK list() has no free-text).
|
|
84
|
+
if (filters.search) {
|
|
85
|
+
return searchEscalations({
|
|
86
|
+
status: filters.status,
|
|
87
|
+
role: filters.role,
|
|
88
|
+
roles: filters.visibleRoles,
|
|
89
|
+
type: filters.type,
|
|
90
|
+
subtype: filters.subtype,
|
|
91
|
+
priority: filters.priority,
|
|
92
|
+
assigned_to: filters.assigned_to,
|
|
93
|
+
available: heldNow ? false : undefined,
|
|
94
|
+
search: filters.search,
|
|
95
|
+
limit: filters.limit ?? 50,
|
|
96
|
+
offset: filters.offset ?? 0,
|
|
97
|
+
sort_by: filters.sort_by,
|
|
98
|
+
order: filters.order,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
35
101
|
const client = await (0, client_1.escalations)();
|
|
36
102
|
// Shared filter — passed to both list() and count() so totals stay in sync.
|
|
37
103
|
const where = {
|
|
@@ -44,8 +110,7 @@ async function listEscalations(filters) {
|
|
|
44
110
|
};
|
|
45
111
|
if (filters.assigned_to)
|
|
46
112
|
where.assignedTo = filters.assigned_to;
|
|
47
|
-
|
|
48
|
-
if (filters.assigned_to || filters.claimed)
|
|
113
|
+
if (heldNow)
|
|
49
114
|
where.available = false;
|
|
50
115
|
const [rows, total] = await Promise.all([
|
|
51
116
|
client.list({
|
|
@@ -62,6 +127,23 @@ async function listEscalations(filters) {
|
|
|
62
127
|
* List available escalations: pending AND (unassigned OR expired claim).
|
|
63
128
|
*/
|
|
64
129
|
async function listAvailableEscalations(filters) {
|
|
130
|
+
// Free-text search → server-side SQL path (available = pending + no active claim).
|
|
131
|
+
if (filters.search) {
|
|
132
|
+
return searchEscalations({
|
|
133
|
+
status: 'pending',
|
|
134
|
+
role: filters.role,
|
|
135
|
+
roles: filters.visibleRoles,
|
|
136
|
+
type: filters.type,
|
|
137
|
+
subtype: filters.subtype,
|
|
138
|
+
priority: filters.priority,
|
|
139
|
+
available: true,
|
|
140
|
+
search: filters.search,
|
|
141
|
+
limit: filters.limit ?? 50,
|
|
142
|
+
offset: filters.offset ?? 0,
|
|
143
|
+
sort_by: filters.sort_by,
|
|
144
|
+
order: filters.order,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
65
147
|
const client = await (0, client_1.escalations)();
|
|
66
148
|
const where = {
|
|
67
149
|
status: 'pending',
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* table. Clears both `assigned_until` and `claim_expires_at`.
|
|
9
9
|
*/
|
|
10
10
|
export declare const RELEASE_EXPIRED_CLAIMS = "UPDATE public.hmsh_escalations\nSET assigned_to = NULL,\n assigned_until = NULL,\n claimed_at = NULL,\n claim_expires_at = NULL,\n updated_at = NOW()\nWHERE status = 'pending'\n AND assigned_to IS NOT NULL\n AND assigned_until <= NOW()";
|
|
11
|
+
/** Count matching the search WHERE (params $1–$9). */
|
|
12
|
+
export declare const COUNT_SEARCH_ESCALATIONS = "SELECT COUNT(*)::int AS total\n FROM public.lt_escalations\n WHERE ($1::text IS NULL OR status = $1)\n AND ($2::text IS NULL OR role = $2)\n AND ($3::text[] IS NULL OR role = ANY($3))\n AND ($4::text IS NULL OR type = $4)\n AND ($5::text IS NULL OR subtype = $5)\n AND ($6::int IS NULL OR priority = $6)\n AND ($7::text IS NULL OR assigned_to = $7)\n AND ($8::boolean IS NULL OR available = $8)\n AND ($9::text IS NULL OR (\n description ILIKE '%' || $9 || '%'\n OR type ILIKE '%' || $9 || '%'\n OR subtype ILIKE '%' || $9 || '%'\n OR role ILIKE '%' || $9 || '%'\n OR workflow_id ILIKE '%' || $9 || '%'\n OR origin_id ILIKE '%' || $9 || '%'\n OR id::text ILIKE '%' || $9 || '%'\n OR metadata::text ILIKE '%' || $9 || '%'\n ))";
|
|
13
|
+
/**
|
|
14
|
+
* Build the search SELECT. `orderBy` is composed from a whitelist by the caller
|
|
15
|
+
* (never raw user input), so it is safe to interpolate; everything else is bound.
|
|
16
|
+
*/
|
|
17
|
+
export declare function searchEscalationsQuery(orderBy: string): string;
|
|
11
18
|
/**
|
|
12
19
|
* Atomic resolve by metadata with signal guard.
|
|
13
20
|
*
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// `hmsh_escalations` here rather than composed from generic SDK calls.
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.RESOLVE_BY_METADATA_ATOMIC = exports.RELEASE_EXPIRED_CLAIMS = void 0;
|
|
14
|
+
exports.RESOLVE_BY_METADATA_ATOMIC = exports.COUNT_SEARCH_ESCALATIONS = exports.RELEASE_EXPIRED_CLAIMS = void 0;
|
|
15
|
+
exports.searchEscalationsQuery = searchEscalationsQuery;
|
|
15
16
|
/**
|
|
16
17
|
* Sweep expired claims back to the available pool.
|
|
17
18
|
*
|
|
@@ -31,6 +32,54 @@ SET assigned_to = NULL,
|
|
|
31
32
|
WHERE status = 'pending'
|
|
32
33
|
AND assigned_to IS NOT NULL
|
|
33
34
|
AND assigned_until <= NOW()`;
|
|
35
|
+
/**
|
|
36
|
+
* Free-text escalation search.
|
|
37
|
+
*
|
|
38
|
+
* The SDK's `client.list()` filters by structured columns only — it has no
|
|
39
|
+
* free-text predicate. Long-tail's contract needs a search box that matches a
|
|
40
|
+
* correlation key (order id, ticket id) or any visible field across the WHOLE
|
|
41
|
+
* result set, not just the current page. That is server-side SQL here (over the
|
|
42
|
+
* `lt_escalations` view, which adds the computed `available` flag), following the
|
|
43
|
+
* same raw-SQL-on-the-shared-table pattern as the atomic resolve below — so the
|
|
44
|
+
* filter flows SQL→service→API→route→dashboard rather than being faked client-side.
|
|
45
|
+
*
|
|
46
|
+
* All structured filters are optional and combine with the search term (AND).
|
|
47
|
+
* `metadata::text ILIKE` scans the JSONB as text (the GIN index is containment-only
|
|
48
|
+
* and cannot serve ILIKE) — bounded in practice by the other filters and the page.
|
|
49
|
+
*
|
|
50
|
+
* $1 status, $2 role, $3 roles (text[] visibleRoles), $4 type, $5 subtype,
|
|
51
|
+
* $6 priority, $7 assigned_to, $8 available (bool), $9 search.
|
|
52
|
+
* The list query adds $10 limit, $11 offset.
|
|
53
|
+
*/
|
|
54
|
+
const SEARCH_ESCALATIONS_WHERE = `\
|
|
55
|
+
FROM public.lt_escalations
|
|
56
|
+
WHERE ($1::text IS NULL OR status = $1)
|
|
57
|
+
AND ($2::text IS NULL OR role = $2)
|
|
58
|
+
AND ($3::text[] IS NULL OR role = ANY($3))
|
|
59
|
+
AND ($4::text IS NULL OR type = $4)
|
|
60
|
+
AND ($5::text IS NULL OR subtype = $5)
|
|
61
|
+
AND ($6::int IS NULL OR priority = $6)
|
|
62
|
+
AND ($7::text IS NULL OR assigned_to = $7)
|
|
63
|
+
AND ($8::boolean IS NULL OR available = $8)
|
|
64
|
+
AND ($9::text IS NULL OR (
|
|
65
|
+
description ILIKE '%' || $9 || '%'
|
|
66
|
+
OR type ILIKE '%' || $9 || '%'
|
|
67
|
+
OR subtype ILIKE '%' || $9 || '%'
|
|
68
|
+
OR role ILIKE '%' || $9 || '%'
|
|
69
|
+
OR workflow_id ILIKE '%' || $9 || '%'
|
|
70
|
+
OR origin_id ILIKE '%' || $9 || '%'
|
|
71
|
+
OR id::text ILIKE '%' || $9 || '%'
|
|
72
|
+
OR metadata::text ILIKE '%' || $9 || '%'
|
|
73
|
+
))`;
|
|
74
|
+
/** Count matching the search WHERE (params $1–$9). */
|
|
75
|
+
exports.COUNT_SEARCH_ESCALATIONS = `SELECT COUNT(*)::int AS total\n${SEARCH_ESCALATIONS_WHERE}`;
|
|
76
|
+
/**
|
|
77
|
+
* Build the search SELECT. `orderBy` is composed from a whitelist by the caller
|
|
78
|
+
* (never raw user input), so it is safe to interpolate; everything else is bound.
|
|
79
|
+
*/
|
|
80
|
+
function searchEscalationsQuery(orderBy) {
|
|
81
|
+
return `SELECT *\n${SEARCH_ESCALATIONS_WHERE}\n ORDER BY ${orderBy}\n LIMIT $10 OFFSET $11`;
|
|
82
|
+
}
|
|
34
83
|
/**
|
|
35
84
|
* Atomic resolve by metadata with signal guard.
|
|
36
85
|
*
|
|
@@ -4,9 +4,11 @@ import type { ResolvedHandle } from './types';
|
|
|
4
4
|
* HotMesh needs to get a workflow handle.
|
|
5
5
|
*
|
|
6
6
|
* 1. Look up lt_tasks by workflow_id — returns workflow_type and task_queue.
|
|
7
|
-
* 2. If no task record (e.g., orchestrators/containers
|
|
8
|
-
*
|
|
7
|
+
* 2. If no task record (e.g., orchestrators/containers, or a child running in
|
|
8
|
+
* another app), fall back to `<appId>.jobs` (entity) + lt_config_workflows
|
|
9
|
+
* (task_queue). `appId` selects the HotMesh namespace to scan — children of
|
|
10
|
+
* a workflow live under their own app's schema, not always `durable`.
|
|
9
11
|
* 3. If task_queue is null (pre-migration record), fall back to lt_config_workflows.
|
|
10
12
|
* 4. Throws if the workflow cannot be resolved.
|
|
11
13
|
*/
|
|
12
|
-
export declare function resolveWorkflowHandle(workflowId: string): Promise<ResolvedHandle>;
|
|
14
|
+
export declare function resolveWorkflowHandle(workflowId: string, appId?: string): Promise<ResolvedHandle>;
|
|
@@ -9,12 +9,14 @@ const sql_1 = require("./sql");
|
|
|
9
9
|
* HotMesh needs to get a workflow handle.
|
|
10
10
|
*
|
|
11
11
|
* 1. Look up lt_tasks by workflow_id — returns workflow_type and task_queue.
|
|
12
|
-
* 2. If no task record (e.g., orchestrators/containers
|
|
13
|
-
*
|
|
12
|
+
* 2. If no task record (e.g., orchestrators/containers, or a child running in
|
|
13
|
+
* another app), fall back to `<appId>.jobs` (entity) + lt_config_workflows
|
|
14
|
+
* (task_queue). `appId` selects the HotMesh namespace to scan — children of
|
|
15
|
+
* a workflow live under their own app's schema, not always `durable`.
|
|
14
16
|
* 3. If task_queue is null (pre-migration record), fall back to lt_config_workflows.
|
|
15
17
|
* 4. Throws if the workflow cannot be resolved.
|
|
16
18
|
*/
|
|
17
|
-
async function resolveWorkflowHandle(workflowId) {
|
|
19
|
+
async function resolveWorkflowHandle(workflowId, appId = 'durable') {
|
|
18
20
|
const pool = (0, db_1.getPool)();
|
|
19
21
|
// 1. Try lt_tasks first (leaf workflows)
|
|
20
22
|
const { rows } = await pool.query(sql_1.RESOLVE_TASK_BY_WORKFLOW_ID, [workflowId]);
|
|
@@ -29,9 +31,10 @@ async function resolveWorkflowHandle(workflowId) {
|
|
|
29
31
|
return { taskQueue: configRows[0].task_queue, workflowName: workflow_type };
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
// 2. Fall back to
|
|
33
|
-
// have no lt_tasks record but do have a job with an entity tag
|
|
34
|
-
|
|
34
|
+
// 2. Fall back to <appId>.jobs — handles orchestrators/containers that
|
|
35
|
+
// have no lt_tasks record but do have a job with an entity tag, and
|
|
36
|
+
// children running in a non-default namespace.
|
|
37
|
+
const { rows: jobRows } = await pool.query((0, sql_1.RESOLVE_JOB_ENTITY)(appId), [`hmsh:${appId}:j:${workflowId}`]);
|
|
35
38
|
if (jobRows.length > 0 && jobRows[0].entity) {
|
|
36
39
|
const entity = jobRows[0].entity;
|
|
37
40
|
// 2a. Try lt_config_workflows for task_queue
|