@hotmeshio/long-tail 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/escalations/create.js +45 -0
- package/build/api/escalations/index.d.ts +1 -1
- package/build/api/escalations/index.js +2 -1
- package/build/api/escalations/list.d.ts +3 -0
- package/build/api/escalations/list.js +14 -0
- package/build/api/roles.d.ts +17 -0
- package/build/api/roles.js +46 -0
- package/build/bin/ltc.js +1 -0
- package/build/lib/cli/commands/escalations.d.ts +1 -0
- package/build/lib/cli/commands/escalations.js +2 -0
- package/build/lib/db/migrate.js +22 -2
- package/build/lib/db/schemas/014_roles_v2.sql +19 -0
- package/build/lib/db/schemas/015_role_ops.sql +50 -0
- package/build/lib/db/schemas/016_station_metrics_indexes.sql +13 -0
- package/build/lib/events/system-events.js +3 -0
- package/build/modules/auth.d.ts +9 -0
- package/build/modules/auth.js +38 -1
- package/build/routes/escalations/list.js +10 -0
- package/build/routes/roles.js +16 -5
- package/build/scripts/ortho-populate.d.ts +37 -0
- package/build/scripts/ortho-populate.js +228 -0
- package/build/sdk/index.d.ts +4 -0
- package/build/sdk/index.js +2 -0
- package/build/services/escalation/client.js +93 -2
- package/build/services/escalation/crud.js +18 -9
- package/build/services/escalation/metrics-cache.d.ts +14 -0
- package/build/services/escalation/metrics-cache.js +53 -0
- package/build/services/escalation/queries.d.ts +14 -2
- package/build/services/escalation/queries.js +121 -11
- package/build/services/escalation/sql.d.ts +29 -2
- package/build/services/escalation/sql.js +156 -27
- package/build/services/escalation/types.d.ts +17 -0
- package/build/services/escalation/types.js +1 -0
- package/build/services/interceptor/completion.js +17 -12
- package/build/services/mcp/external-server.js +5 -1
- package/build/services/orchestrator/condition.d.ts +20 -0
- package/build/services/orchestrator/condition.js +20 -0
- package/build/services/role/index.d.ts +22 -3
- package/build/services/role/index.js +50 -2
- package/build/services/role/sql.d.ts +11 -1
- package/build/services/role/sql.js +38 -1
- package/build/services/role/types.d.ts +32 -0
- package/build/system/mcp-servers/admin/escalations.js +8 -7
- package/build/system/mcp-servers/admin/index.js +5 -3
- package/build/system/mcp-servers/admin/ortho.d.ts +10 -0
- package/build/system/mcp-servers/admin/ortho.js +242 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +40 -3
- package/build/system/mcp-servers/admin/schemas.js +17 -4
- package/build/system/mcp-servers/admin/users.js +31 -0
- package/build/system/seed/tool-manifests-admin.d.ts +4016 -712
- package/build/system/seed/tool-manifests-admin.js +14 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/escalation.d.ts +3 -1
- package/dashboard/dist/assets/{AdminDashboard-C5gfVEeL.js → AdminDashboard-Bg1HgukI.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-C5gfVEeL.js.map → AdminDashboard-Bg1HgukI.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-CWEnuiGN.js → AgentConfigPage-HBe_YDU_.js} +6 -6
- package/dashboard/dist/assets/{AgentConfigPage-CWEnuiGN.js.map → AgentConfigPage-HBe_YDU_.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-B6IE27iK.js → AgentDetailPage-BsaiYbwL.js} +4 -4
- package/dashboard/dist/assets/{AgentDetailPage-B6IE27iK.js.map → AgentDetailPage-BsaiYbwL.js.map} +1 -1
- package/dashboard/dist/assets/AgentsPage-DhN2lIBh.js +2 -0
- package/dashboard/dist/assets/{AgentsPage-Blhh2RsK.js.map → AgentsPage-DhN2lIBh.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-_xcWvlIz.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-_xcWvlIz.js.map +1 -0
- package/dashboard/dist/assets/{BotPicker-DSozotGk.js → BotPicker-DY6iUVC9.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-DSozotGk.js.map → BotPicker-DY6iUVC9.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-CrS_DTA1.js → CapabilitiesPage-w1owqsmN.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-CrS_DTA1.js.map → CapabilitiesPage-w1owqsmN.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-1i6bOQ1L.js → CollapsibleSection-DIMGjlJ-.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-1i6bOQ1L.js.map → CollapsibleSection-DIMGjlJ-.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-BxJio06E.js → CredentialsPage-D1OmMHux.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-BxJio06E.js.map → CredentialsPage-D1OmMHux.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-BAfIHHz4.js → CronLabel-CQy3DrUQ.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-BAfIHHz4.js.map → CronLabel-CQy3DrUQ.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-BLF0NFJK.js → CustomDurationPicker-BoBkl9tw.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-BLF0NFJK.js.map → CustomDurationPicker-BoBkl9tw.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-CEx_wk-g.js → DropZone-QXbiuU0b.js} +2 -2
- package/dashboard/dist/assets/{DropZone-CEx_wk-g.js.map → DropZone-QXbiuU0b.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-nKGJvznH.js → ElapsedCell-Donjm9gT.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-nKGJvznH.js.map → ElapsedCell-Donjm9gT.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-bNNVA6R2.js → EscalationsOverview-CvHIJ_GG.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-bNNVA6R2.js.map → EscalationsOverview-CvHIJ_GG.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-BCuDdqlU.js → EventTable-DpAXMmT3.js} +2 -2
- package/dashboard/dist/assets/{EventTable-BCuDdqlU.js.map → EventTable-DpAXMmT3.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-BPAvqHls.js → GraphInvokePage-De1XVx5d.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-BPAvqHls.js.map → GraphInvokePage-De1XVx5d.js.map} +1 -1
- package/dashboard/dist/assets/HomePage-BYR5VXXw.js +2 -0
- package/dashboard/dist/assets/HomePage-BYR5VXXw.js.map +1 -0
- package/dashboard/dist/assets/{ListToolbar-DIJZfEgu.js → ListToolbar-Bx7jGzPA.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-DIJZfEgu.js.map → ListToolbar-Bx7jGzPA.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-DCfSCfPg.js → McpOverview-DXRKcJN0.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-DCfSCfPg.js.map → McpOverview-DXRKcJN0.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-mb7rmtNe.js → McpQueryDetailPage-CmW15w5e.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-mb7rmtNe.js.map → McpQueryDetailPage-CmW15w5e.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-D9b1T0I3.js → McpQueryPage-BE6I011z.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-D9b1T0I3.js.map → McpQueryPage-BE6I011z.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-Bz2UyuNm.js → McpRunDetailPage-Dsa3G3XQ.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-Bz2UyuNm.js.map → McpRunDetailPage-Dsa3G3XQ.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-Dw7w_ZO-.js → McpRunsPage-uTbym66Y.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-Dw7w_ZO-.js.map → McpRunsPage-uTbym66Y.js.map} +1 -1
- package/dashboard/dist/assets/{NamespacePill-D27vqDXy.js → NamespacePill-BGf0jHwE.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill-D27vqDXy.js.map → NamespacePill-BGf0jHwE.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js +2 -0
- package/dashboard/dist/assets/OperationsPage-CAmiFHI6.js.map +1 -0
- package/dashboard/dist/assets/{OperatorDashboard-CTzSDW3N.js → OperatorDashboard-i8cxaDlk.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-CTzSDW3N.js.map → OperatorDashboard-i8cxaDlk.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-B_1zTx2q.js → PageHeader-BZJsEVOC.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-B_1zTx2q.js.map → PageHeader-BZJsEVOC.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-D-cPg7UD.js → PageHeaderWithStats-BWuD7s9c.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-D-cPg7UD.js.map → PageHeaderWithStats-BWuD7s9c.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-olG2CS-h.js → ProcessDetailPage-CP7GZhgs.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-olG2CS-h.js.map → ProcessDetailPage-CP7GZhgs.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-CUZSL06Y.js → ProcessesListPage-btsU6hBR.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-CUZSL06Y.js.map → ProcessesListPage-btsU6hBR.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js +16 -0
- package/dashboard/dist/assets/RoleDetailPage-BB_e330f.js.map +1 -0
- package/dashboard/dist/assets/{RolePill-B-V-31lC.js → RolePill-Pb9TKSQN.js} +2 -2
- package/dashboard/dist/assets/{RolePill-B-V-31lC.js.map → RolePill-Pb9TKSQN.js.map} +1 -1
- package/dashboard/dist/assets/RolesPage-1DBoEMsb.js +2 -0
- package/dashboard/dist/assets/RolesPage-1DBoEMsb.js.map +1 -0
- package/dashboard/dist/assets/{RunAsSelector-BoLHF4fy.js → RunAsSelector-Cp6j3FEY.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-BoLHF4fy.js.map → RunAsSelector-Cp6j3FEY.js.map} +1 -1
- package/dashboard/dist/assets/StatusBadge-D9MeQHrh.js +2 -0
- package/dashboard/dist/assets/StatusBadge-D9MeQHrh.js.map +1 -0
- package/dashboard/dist/assets/{StickyPagination-BEMY1L-H.js → StickyPagination-Pr8kawv8.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-BEMY1L-H.js.map → StickyPagination-Pr8kawv8.js.map} +1 -1
- package/dashboard/dist/assets/StreamMessageDetail-BqmQ-N3y.js +2 -0
- package/dashboard/dist/assets/{StreamMessageDetail-CVRVWXcU.js.map → StreamMessageDetail-BqmQ-N3y.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-DDpU0-Jn.js → SwimlaneTimeline-y1YmXrsj.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-DDpU0-Jn.js.map → SwimlaneTimeline-y1YmXrsj.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-BHVfG307.js → TagInput-Df4myuYF.js} +2 -2
- package/dashboard/dist/assets/{TagInput-BHVfG307.js.map → TagInput-Df4myuYF.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-B_xQtJMg.js → TaskDetailPage-CT5rXHK5.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-B_xQtJMg.js.map → TaskDetailPage-CT5rXHK5.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-CGlxXqWt.js → TaskQueuePill-YalYdCNo.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-CGlxXqWt.js.map → TaskQueuePill-YalYdCNo.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CsrdIOKk.js → TasksListPage-DOy6JlKS.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CsrdIOKk.js.map → TasksListPage-DOy6JlKS.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-DmicaxjT.js → TimeAgo-BAQwZSpq.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-DmicaxjT.js.map → TimeAgo-BAQwZSpq.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-DeK89eRV.js → TimestampCell-BK_jufns.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-DeK89eRV.js.map → TimestampCell-BK_jufns.js.map} +1 -1
- package/dashboard/dist/assets/{ToolPill-NV7PxSpA.js → ToolPill-VGpT6Quy.js} +2 -2
- package/dashboard/dist/assets/{ToolPill-NV7PxSpA.js.map → ToolPill-VGpT6Quy.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-D3s2n9-9.js → ToolTestPanel-5PMQyUCe.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-D3s2n9-9.js.map → ToolTestPanel-5PMQyUCe.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-qyv7UWT_.js → TopicDetailPage-Cn1ShfR8.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-qyv7UWT_.js.map → TopicDetailPage-Cn1ShfR8.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-CEUfMoed.js → TopicsPage-D67tjS8L.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-CEUfMoed.js.map → TopicsPage-D67tjS8L.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-Bn_BxI8K.js → UserName-Dsu1Jk4N.js} +2 -2
- package/dashboard/dist/assets/{UserName-Bn_BxI8K.js.map → UserName-Dsu1Jk4N.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-DAk2RqpB.js → WorkflowExecutionPage-BwQoOmUE.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-DAk2RqpB.js.map → WorkflowExecutionPage-BwQoOmUE.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-DDR0B6DN.js → WorkflowPill-pPUqmx6Q.js} +2 -2
- package/dashboard/dist/assets/{WorkflowPill-DDR0B6DN.js.map → WorkflowPill-pPUqmx6Q.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-DfBgM80t.js → WorkflowsDashboard-D3z7COgH.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-DfBgM80t.js.map → WorkflowsDashboard-D3z7COgH.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview--Dkoo9Jq.js → WorkflowsOverview-C15JcxyY.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview--Dkoo9Jq.js.map → WorkflowsOverview-C15JcxyY.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-B6HYbfFj.js → YamlWorkflowDetailPage-BYq3t7E-.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-B6HYbfFj.js.map → YamlWorkflowDetailPage-BYq3t7E-.js.map} +1 -1
- package/dashboard/dist/assets/YamlWorkflowsPage-B44WgwE_.js +2 -0
- package/dashboard/dist/assets/{YamlWorkflowsPage-CJE-PxC0.js.map → YamlWorkflowsPage-B44WgwE_.js.map} +1 -1
- package/dashboard/dist/assets/{agents-DUciUZpt.js → agents-BRmmK6Y9.js} +2 -2
- package/dashboard/dist/assets/{agents-DUciUZpt.js.map → agents-BRmmK6Y9.js.map} +1 -1
- package/dashboard/dist/assets/{bots-BnyqCfuC.js → bots-BD75mzGG.js} +2 -2
- package/dashboard/dist/assets/{bots-BnyqCfuC.js.map → bots-BD75mzGG.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-CkTWWT0q.js → capabilities-DDgC_Kds.js} +2 -2
- package/dashboard/dist/assets/{capabilities-CkTWWT0q.js.map → capabilities-DDgC_Kds.js.map} +1 -1
- package/dashboard/dist/assets/controlplane-BJh12lMv.js +2 -0
- package/dashboard/dist/assets/controlplane-BJh12lMv.js.map +1 -0
- package/dashboard/dist/assets/{escalation-De9JnTKI.js → escalation-DIJt2sgn.js} +2 -2
- package/dashboard/dist/assets/{escalation-De9JnTKI.js.map → escalation-DIJt2sgn.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-Df7Pluzg.js → escalation-columns-zl6E94Up.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-Df7Pluzg.js.map → escalation-columns-zl6E94Up.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cqjf-yVP.js → index-B0kmiSmt.js} +2 -2
- package/dashboard/dist/assets/{index-Cqjf-yVP.js.map → index-B0kmiSmt.js.map} +1 -1
- package/dashboard/dist/assets/index-BRtKQtQZ.css +1 -0
- package/dashboard/dist/assets/{index-BxdiKgnH.js → index-BUbzfEA9.js} +2 -2
- package/dashboard/dist/assets/{index-BxdiKgnH.js.map → index-BUbzfEA9.js.map} +1 -1
- package/dashboard/dist/assets/{index-DpAS4-DA.js → index-BhznDv-4.js} +2 -2
- package/dashboard/dist/assets/{index-DpAS4-DA.js.map → index-BhznDv-4.js.map} +1 -1
- package/dashboard/dist/assets/{index-BAhkWcOq.js → index-CDPBXB4e.js} +4 -4
- package/dashboard/dist/assets/{index-BAhkWcOq.js.map → index-CDPBXB4e.js.map} +1 -1
- package/dashboard/dist/assets/{index-CgD9fA9P.js → index-CV4lEd4I.js} +2 -2
- package/dashboard/dist/assets/{index-CgD9fA9P.js.map → index-CV4lEd4I.js.map} +1 -1
- package/dashboard/dist/assets/{index-DjxTV3cn.js → index-CknKy4RA.js} +2 -2
- package/dashboard/dist/assets/{index-DjxTV3cn.js.map → index-CknKy4RA.js.map} +1 -1
- package/dashboard/dist/assets/{index-DNugmBs7.js → index-CpgV4Kkp.js} +2 -2
- package/dashboard/dist/assets/{index-DNugmBs7.js.map → index-CpgV4Kkp.js.map} +1 -1
- package/dashboard/dist/assets/{index-B1kc6itM.js → index-CtDf8vvQ.js} +24 -24
- package/dashboard/dist/assets/index-CtDf8vvQ.js.map +1 -0
- package/dashboard/dist/assets/{index-BzMsh6Ui.js → index-DU7YumVJ.js} +2 -2
- package/dashboard/dist/assets/{index-BzMsh6Ui.js.map → index-DU7YumVJ.js.map} +1 -1
- package/dashboard/dist/assets/{index-BsIaUbKD.js → index-DVVIeBBk.js} +2 -2
- package/dashboard/dist/assets/{index-BsIaUbKD.js.map → index-DVVIeBBk.js.map} +1 -1
- package/dashboard/dist/assets/index-DccCwyZl.js +6 -0
- package/dashboard/dist/assets/{index-CN0efCE9.js.map → index-DccCwyZl.js.map} +1 -1
- package/dashboard/dist/assets/{index-7hqtQI43.js → index-DrUkTcn-.js} +2 -2
- package/dashboard/dist/assets/{index-7hqtQI43.js.map → index-DrUkTcn-.js.map} +1 -1
- package/dashboard/dist/assets/{index-B07xLlyz.js → index-j-C7g_Ej.js} +2 -2
- package/dashboard/dist/assets/{index-B07xLlyz.js.map → index-j-C7g_Ej.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-D4SvdNv5.js → knowledge-pNDWACXB.js} +2 -2
- package/dashboard/dist/assets/{knowledge-D4SvdNv5.js.map → knowledge-pNDWACXB.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CLP9EnnU.js → mcp-query-CmNjvxTG.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CLP9EnnU.js.map → mcp-query-CmNjvxTG.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-DVxt4T9J.js → mcp-xddLBwUW.js} +2 -2
- package/dashboard/dist/assets/{mcp-DVxt4T9J.js.map → mcp-xddLBwUW.js.map} +1 -1
- package/dashboard/dist/assets/pipelines-B05sd2nB.js +2 -0
- package/dashboard/dist/assets/pipelines-B05sd2nB.js.map +1 -0
- package/dashboard/dist/assets/roles-bRgaMcdy.js +2 -0
- package/dashboard/dist/assets/roles-bRgaMcdy.js.map +1 -0
- package/dashboard/dist/assets/tasks-Bm1OKrJA.js +2 -0
- package/dashboard/dist/assets/{tasks-1eTuP02d.js.map → tasks-Bm1OKrJA.js.map} +1 -1
- package/dashboard/dist/assets/{topics-B3p8K0PW.js → topics-BVIqtmd4.js} +2 -2
- package/dashboard/dist/assets/{topics-B3p8K0PW.js.map → topics-BVIqtmd4.js.map} +1 -1
- package/dashboard/dist/assets/useEventHooks-_qCzU74s.js +2 -0
- package/dashboard/dist/assets/useEventHooks-_qCzU74s.js.map +1 -0
- package/dashboard/dist/assets/{useNamespace-Dx81_JM0.js → useNamespace-CDnCSFpG.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-Dx81_JM0.js.map → useNamespace-CDnCSFpG.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-D5T3TbE7.js → useYamlActivityEvents-zrHMhaFM.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-D5T3TbE7.js.map → useYamlActivityEvents-zrHMhaFM.js.map} +1 -1
- package/dashboard/dist/assets/{users-BEMysNdR.js → users-BPBMZg27.js} +2 -2
- package/dashboard/dist/assets/{users-BEMysNdR.js.map → users-BPBMZg27.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons--Ivmp-RX.js → vendor-icons-C1zVP_dv.js} +125 -115
- package/dashboard/dist/assets/vendor-icons-C1zVP_dv.js.map +1 -0
- package/dashboard/dist/assets/workflows-CT6nTL1I.js +2 -0
- package/dashboard/dist/assets/workflows-CT6nTL1I.js.map +1 -0
- package/dashboard/dist/assets/{yaml-workflows-4rJ5v5rJ.js → yaml-workflows-CdfjkOKU.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-4rJ5v5rJ.js.map → yaml-workflows-CdfjkOKU.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/api/http/escalations.md +82 -6
- package/docs/api/http/roles.md +82 -3
- package/docs/api/mcp/admin.md +127 -1
- package/docs/api/sdk/escalations.md +65 -4
- package/docs/api/sdk/roles.md +81 -0
- package/docs/cli.md +2 -0
- package/docs/dashboard.md +17 -2
- package/docs/hitl-guide.md +12 -3
- package/docs/operations.md +113 -0
- package/package.json +3 -2
- package/dashboard/dist/assets/AgentsPage-Blhh2RsK.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-ClwAle76.js.map +0 -1
- package/dashboard/dist/assets/HomePage-BZ1YTrjR.js +0 -2
- package/dashboard/dist/assets/HomePage-BZ1YTrjR.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-DLnaW6iD.js +0 -2
- package/dashboard/dist/assets/RolesPage-DLnaW6iD.js.map +0 -1
- package/dashboard/dist/assets/StatusBadge-Bl3FXVmF.js +0 -2
- package/dashboard/dist/assets/StatusBadge-Bl3FXVmF.js.map +0 -1
- package/dashboard/dist/assets/StreamMessageDetail-CVRVWXcU.js +0 -2
- package/dashboard/dist/assets/YamlWorkflowsPage-CJE-PxC0.js +0 -2
- package/dashboard/dist/assets/controlplane-D9HaFZsC.js +0 -2
- package/dashboard/dist/assets/controlplane-D9HaFZsC.js.map +0 -1
- package/dashboard/dist/assets/index-B1kc6itM.js.map +0 -1
- package/dashboard/dist/assets/index-CN0efCE9.js +0 -6
- package/dashboard/dist/assets/index-nO0cim3_.css +0 -1
- package/dashboard/dist/assets/pipelines-C_x0LTBN.js +0 -2
- package/dashboard/dist/assets/pipelines-C_x0LTBN.js.map +0 -1
- package/dashboard/dist/assets/roles-QaGFZQ58.js +0 -2
- package/dashboard/dist/assets/roles-QaGFZQ58.js.map +0 -1
- package/dashboard/dist/assets/tasks-1eTuP02d.js +0 -2
- package/dashboard/dist/assets/useEventHooks-BNZky4xM.js +0 -2
- package/dashboard/dist/assets/useEventHooks-BNZky4xM.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons--Ivmp-RX.js.map +0 -1
- package/dashboard/dist/assets/workflows-C4rjvQbI.js +0 -2
- package/dashboard/dist/assets/workflows-C4rjvQbI.js.map +0 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ortho-populate — Populate the Operations dashboard with ortho pipeline history.
|
|
4
|
+
*
|
|
5
|
+
* Adapted from the boilerplate's 08-day-resolver.ts pattern:
|
|
6
|
+
* - concurrent claim loop + resolve loop + watchdog via Promise.all
|
|
7
|
+
* - hold time read from esc.claimed_at (API), not tracked in memory
|
|
8
|
+
* - simple resolve payload { approved: true, station: role }
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx ts-node scripts/ortho-populate.ts
|
|
12
|
+
* ORDERS=50 HOLD_S=3 npx ts-node scripts/ortho-populate.ts
|
|
13
|
+
* ORDERS=5 HOLD_S=0 POLL_MS=500 npx ts-node scripts/ortho-populate.ts
|
|
14
|
+
*
|
|
15
|
+
* Env vars:
|
|
16
|
+
* ORDERS — number of orders to run (default 20)
|
|
17
|
+
* HOLD_S — seconds to hold each escalation before resolving (default 2)
|
|
18
|
+
* POLL_MS — poll interval ms (default 1000)
|
|
19
|
+
* BASE_URL — server URL (default http://localhost:3000)
|
|
20
|
+
*/
|
|
21
|
+
try {
|
|
22
|
+
require('dotenv/config');
|
|
23
|
+
}
|
|
24
|
+
catch { }
|
|
25
|
+
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
|
26
|
+
const ORDERS = parseInt(process.env.ORDERS || '20', 10);
|
|
27
|
+
const HOLD_S = parseFloat(process.env.HOLD_S || '2');
|
|
28
|
+
const POLL_MS = parseInt(process.env.POLL_MS || '1000', 10);
|
|
29
|
+
const ORTHO_ROLES = ['design', 'review', 'print', 'grind', 'glue', 'finish', 'qa', 'ship'];
|
|
30
|
+
let token = '';
|
|
31
|
+
let userId = '';
|
|
32
|
+
let totalClaimed = 0;
|
|
33
|
+
let totalResolved = 0;
|
|
34
|
+
function ts() { return new Date().toISOString().slice(11, 19); }
|
|
35
|
+
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
36
|
+
function ageSeconds(iso) { return (Date.now() - new Date(iso).getTime()) / 1000; }
|
|
37
|
+
// ── HTTP helper (mirrors boilerplate 07-shared) ───────────────────────────────
|
|
38
|
+
async function api(method, path, body) {
|
|
39
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
40
|
+
method,
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
44
|
+
},
|
|
45
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
46
|
+
});
|
|
47
|
+
const data = await res.json().catch(() => ({}));
|
|
48
|
+
if (!res.ok)
|
|
49
|
+
throw new Error(`${method} ${path} → ${res.status}: ${JSON.stringify(data)}`);
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
// ── Auth ─────────────────────────────────────────────────────────────────────
|
|
53
|
+
async function login() {
|
|
54
|
+
const auth = await api('POST', '/api/auth/login', { username: 'superadmin', password: 'l0ngt@1l' });
|
|
55
|
+
token = auth.token;
|
|
56
|
+
userId = auth.user?.id || '';
|
|
57
|
+
if (!token) {
|
|
58
|
+
console.error('[auth] Login failed');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
console.log(`[${ts()}] Logged in (userId: ${userId.slice(0, 8)}…)`);
|
|
62
|
+
}
|
|
63
|
+
// ── Claim: grab available ortho-stage escalations for this run ────────────────
|
|
64
|
+
async function claimBatch(batchTag) {
|
|
65
|
+
try {
|
|
66
|
+
const resp = await api('GET', '/api/escalations/available?type=ortho-stage&limit=100&sort_by=created_at&order=asc');
|
|
67
|
+
const escalations = resp?.escalations ?? [];
|
|
68
|
+
let claimed = 0;
|
|
69
|
+
for (const esc of escalations) {
|
|
70
|
+
if (!ORTHO_ROLES.includes(esc.role))
|
|
71
|
+
continue;
|
|
72
|
+
if (!String(esc.workflow_id ?? '').includes(batchTag))
|
|
73
|
+
continue;
|
|
74
|
+
try {
|
|
75
|
+
await api('POST', `/api/escalations/${esc.id}/claim`, { durationMinutes: 600 });
|
|
76
|
+
totalClaimed++;
|
|
77
|
+
claimed++;
|
|
78
|
+
console.log(`[${ts()}] [claim] ${esc.role.padEnd(8)} ${esc.id.slice(0, 8)}… [${totalClaimed} total]`);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const msg = String(err.message).slice(0, 80);
|
|
82
|
+
if (!msg.includes('409') && !msg.includes('conflict') && !msg.includes('already')) {
|
|
83
|
+
console.warn(`[${ts()}] [claim] err: ${msg}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return claimed;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.error(`[${ts()}] [claim] poll error: ${err.message.slice(0, 80)}`);
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ── Resolve: drain all claimed+held escalations for this run ─────────────────
|
|
95
|
+
async function resolveBatch(batchTag) {
|
|
96
|
+
try {
|
|
97
|
+
const resp = await api('GET', `/api/escalations?type=ortho-stage&status=pending&assigned_to=${userId}&limit=100&sort_by=created_at&order=asc`);
|
|
98
|
+
const escalations = resp?.escalations ?? [];
|
|
99
|
+
let resolved = 0;
|
|
100
|
+
for (const esc of escalations) {
|
|
101
|
+
if (!ORTHO_ROLES.includes(esc.role))
|
|
102
|
+
continue;
|
|
103
|
+
if (!String(esc.workflow_id ?? '').includes(batchTag))
|
|
104
|
+
continue;
|
|
105
|
+
const heldS = esc.claimed_at ? ageSeconds(esc.claimed_at) : 0;
|
|
106
|
+
if (heldS < HOLD_S)
|
|
107
|
+
continue;
|
|
108
|
+
try {
|
|
109
|
+
await api('POST', `/api/escalations/${esc.id}/resolve`, {
|
|
110
|
+
resolverPayload: { approved: true, station: esc.role, completed_at: new Date().toISOString() },
|
|
111
|
+
});
|
|
112
|
+
totalResolved++;
|
|
113
|
+
resolved++;
|
|
114
|
+
console.log(`[${ts()}] [resolve] ${esc.role.padEnd(8)} ${esc.id.slice(0, 8)}… held ${heldS.toFixed(1)}s [${totalResolved} total]`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error(`[${ts()}] [resolve] ${esc.id.slice(0, 8)}… error: ${err.message.slice(0, 60)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return resolved;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
console.error(`[${ts()}] [resolve] poll error: ${err.message.slice(0, 80)}`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ── Run all orders through all 8 stages ───────────────────────────────────────
|
|
128
|
+
async function runOrders(batchTag, target) {
|
|
129
|
+
let done = false;
|
|
130
|
+
// Claim loop — runs continuously until done.
|
|
131
|
+
const claimLoop = async () => {
|
|
132
|
+
while (!done) {
|
|
133
|
+
await claimBatch(batchTag);
|
|
134
|
+
if (!done)
|
|
135
|
+
await sleep(POLL_MS);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
// Resolve loop — drains all held escalations each poll.
|
|
139
|
+
const resolveLoop = async () => {
|
|
140
|
+
while (!done) {
|
|
141
|
+
await resolveBatch(batchTag);
|
|
142
|
+
if (!done)
|
|
143
|
+
await sleep(POLL_MS);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
// Watchdog — stops when target hit or stalled too long.
|
|
147
|
+
const STALL_S = Math.max(30, HOLD_S * 6);
|
|
148
|
+
const watchdog = async () => {
|
|
149
|
+
let lastResolved = totalResolved;
|
|
150
|
+
let stalledFor = 0;
|
|
151
|
+
while (!done) {
|
|
152
|
+
await sleep(1000);
|
|
153
|
+
if (totalResolved >= target) {
|
|
154
|
+
done = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (totalResolved > lastResolved) {
|
|
158
|
+
stalledFor = 0;
|
|
159
|
+
lastResolved = totalResolved;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
stalledFor++;
|
|
163
|
+
if (stalledFor % 10 === 0) {
|
|
164
|
+
console.log(`[${ts()}] … ${totalResolved}/${target} resolved — waiting (${stalledFor}s quiet)`);
|
|
165
|
+
}
|
|
166
|
+
if (stalledFor >= STALL_S) {
|
|
167
|
+
console.warn(`[${ts()}] ⚠ stalled ${stalledFor}s with ${totalResolved}/${target} — stopping`);
|
|
168
|
+
done = true;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
await Promise.all([claimLoop(), resolveLoop(), watchdog()]);
|
|
175
|
+
}
|
|
176
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
177
|
+
async function main() {
|
|
178
|
+
console.log(`\n Ortho Populate ── ${ORDERS} orders × 8 stages (hold=${HOLD_S}s, poll=${POLL_MS}ms)`);
|
|
179
|
+
console.log(` Server: ${BASE_URL}\n`);
|
|
180
|
+
await login();
|
|
181
|
+
const runId = Math.floor(Date.now() / 1000).toString();
|
|
182
|
+
const batchTag = `ortho-pop-${runId}`;
|
|
183
|
+
const workflowIds = [];
|
|
184
|
+
// Enqueue all orders.
|
|
185
|
+
console.log(`[${ts()}] Enqueuing ${ORDERS} orders…`);
|
|
186
|
+
for (let i = 0; i < ORDERS; i++) {
|
|
187
|
+
const wfId = `${batchTag}-${i}`;
|
|
188
|
+
try {
|
|
189
|
+
await api('POST', '/api/workflows/orthoPipeline/invoke', {
|
|
190
|
+
workflowId: wfId,
|
|
191
|
+
data: { order_id: `ORD-${runId}-${i}`, item_type: 'insole-standard' },
|
|
192
|
+
metadata: { source: 'populate', run_id: runId },
|
|
193
|
+
});
|
|
194
|
+
workflowIds.push(wfId);
|
|
195
|
+
console.log(`[${ts()}] ${i + 1}/${ORDERS} → ${wfId}`);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
console.error(`[${ts()}] ${i + 1}/${ORDERS} FAILED: ${err.message}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (workflowIds.length === 0) {
|
|
202
|
+
console.error('Nothing enqueued — aborting');
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
const target = workflowIds.length * 8;
|
|
206
|
+
console.log(`\n[${ts()}] Running — target: ${target} resolutions (${workflowIds.length} orders × 8 stages)\n`);
|
|
207
|
+
const startMs = Date.now();
|
|
208
|
+
await runOrders(batchTag, target);
|
|
209
|
+
const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
|
|
210
|
+
await sleep(2000);
|
|
211
|
+
// Count completed workflows.
|
|
212
|
+
let completed = 0;
|
|
213
|
+
for (const wfId of workflowIds) {
|
|
214
|
+
try {
|
|
215
|
+
const resp = await api('GET', `/api/workflows/${wfId}/result`);
|
|
216
|
+
if (resp?.result?.data?.results?.length === 8)
|
|
217
|
+
completed++;
|
|
218
|
+
}
|
|
219
|
+
catch { /* still running */ }
|
|
220
|
+
}
|
|
221
|
+
console.log(`\n[${ts()}] Done in ${elapsed}s`);
|
|
222
|
+
console.log(` Orders submitted: ${workflowIds.length}`);
|
|
223
|
+
console.log(` Orders completed: ${completed}`);
|
|
224
|
+
console.log(` Claimed: ${totalClaimed}`);
|
|
225
|
+
console.log(` Resolved: ${totalResolved}`);
|
|
226
|
+
console.log(`\n Dashboard: ${BASE_URL}/operations\n`);
|
|
227
|
+
}
|
|
228
|
+
main().catch((err) => { console.error(err); process.exit(1); });
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -119,6 +119,9 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
119
119
|
getStats: (input: {
|
|
120
120
|
period?: string;
|
|
121
121
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
122
|
+
getStationMetrics: (input: {
|
|
123
|
+
period?: string;
|
|
124
|
+
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
122
125
|
get: (input: {
|
|
123
126
|
id: string;
|
|
124
127
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
@@ -268,6 +271,7 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
268
271
|
list: typeof rolesApi.listRoles;
|
|
269
272
|
listWithDetails: typeof rolesApi.listRolesWithDetails;
|
|
270
273
|
create: typeof rolesApi.createRole;
|
|
274
|
+
update: typeof rolesApi.updateRole;
|
|
271
275
|
delete: typeof rolesApi.deleteRole;
|
|
272
276
|
getEscalationChains: typeof rolesApi.getEscalationChains;
|
|
273
277
|
addEscalationChain: typeof rolesApi.addEscalationChain;
|
package/build/sdk/index.js
CHANGED
|
@@ -119,6 +119,7 @@ function createClient(options = {}) {
|
|
|
119
119
|
listTypes: escalationsApi.listDistinctTypes,
|
|
120
120
|
listFacetKeys: bindAuth(escalationsApi.listFacetKeys, auth),
|
|
121
121
|
getStats: bindAuth(escalationsApi.getEscalationStats, auth),
|
|
122
|
+
getStationMetrics: bindAuth(escalationsApi.getStationMetrics, auth),
|
|
122
123
|
get: bindAuth(escalationsApi.getEscalation, auth),
|
|
123
124
|
getByWorkflowId: escalationsApi.getEscalationsByWorkflowId,
|
|
124
125
|
escalate: bindAuth(escalationsApi.escalateToRole, auth),
|
|
@@ -193,6 +194,7 @@ function createClient(options = {}) {
|
|
|
193
194
|
list: rolesApi.listRoles,
|
|
194
195
|
listWithDetails: rolesApi.listRolesWithDetails,
|
|
195
196
|
create: rolesApi.createRole,
|
|
197
|
+
update: rolesApi.updateRole,
|
|
196
198
|
delete: rolesApi.deleteRole,
|
|
197
199
|
getEscalationChains: rolesApi.getEscalationChains,
|
|
198
200
|
addEscalationChain: rolesApi.addEscalationChain,
|
|
@@ -66,14 +66,69 @@ async function installEscalationCompatView() {
|
|
|
66
66
|
const client = await pool.connect();
|
|
67
67
|
try {
|
|
68
68
|
await client.query('SELECT pg_advisory_lock($1)', [COMPAT_VIEW_LOCK_ID]);
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Atomic table→view swap. The legacy-row migration + RENAME and the
|
|
70
|
+
// CREATE VIEW commit together, so `lt_escalations` is never momentarily
|
|
71
|
+
// absent — a concurrent reader sees either the old table or the new view,
|
|
72
|
+
// never nothing. Postgres DDL is transactional, so a crash mid-swap rolls
|
|
73
|
+
// back cleanly and the next container retries from a consistent state.
|
|
74
|
+
try {
|
|
75
|
+
await client.query('BEGIN');
|
|
76
|
+
await client.query(MIGRATE_AND_RENAME_LEGACY_TABLE);
|
|
77
|
+
await client.query(CREATE_COMPAT_VIEW);
|
|
78
|
+
await client.query('COMMIT');
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
await client.query('ROLLBACK').catch(() => { });
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
// Superseded-index drops run AFTER the swap, in autocommit — instant
|
|
85
|
+
// catalog operations, idempotent across retries and multi-container
|
|
86
|
+
// restarts. hmsh 0.25.0's stats indexes (idx_hmsh_esc_stats_pending /
|
|
87
|
+
// _created / _resolved, shipped by the SDK's own deploy) serve the backlog
|
|
88
|
+
// and window aggregates these earlier app-layer indexes covered, so
|
|
89
|
+
// keeping them would only amplify every insert/claim/resolve write.
|
|
90
|
+
await client.query(DROP_OLD_RESOLVED_INDEX);
|
|
91
|
+
await client.query(DROP_OLD_CLAIMED_INDEX);
|
|
92
|
+
await client.query(DROP_SUPERSEDED_PENDING_INDEX);
|
|
93
|
+
await client.query(DROP_UNCONSUMED_CLAIMED_COVER_INDEX);
|
|
71
94
|
logger_1.loggerRegistry.info('[escalation] lt_escalations compatibility view ensured');
|
|
72
95
|
}
|
|
73
96
|
finally {
|
|
74
97
|
await client.query('SELECT pg_advisory_unlock($1)', [COMPAT_VIEW_LOCK_ID]).catch(() => { });
|
|
75
98
|
client.release();
|
|
76
99
|
}
|
|
100
|
+
// The one index long-tail still owns builds OUTSIDE the advisory lock:
|
|
101
|
+
// CREATE INDEX CONCURRENTLY waits on every transaction holding a snapshot,
|
|
102
|
+
// and sibling containers parked inside `SELECT pg_advisory_lock(...)` hold
|
|
103
|
+
// exactly that — building inside the critical section would stall the build
|
|
104
|
+
// against its own waiters. Out here the build blocks nothing (writes proceed
|
|
105
|
+
// during CONCURRENTLY) and nothing blocks it.
|
|
106
|
+
await ensureResolvedCoverIndex();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Idempotent, concurrent-boot-safe build of idx_hmsh_esc_resolved_cover.
|
|
110
|
+
*
|
|
111
|
+
* A crashed CONCURRENTLY build leaves an INVALID index behind, and
|
|
112
|
+
* `CREATE INDEX CONCURRENTLY IF NOT EXISTS` would then skip it forever — so
|
|
113
|
+
* validity is checked first and an invalid leftover is dropped and rebuilt.
|
|
114
|
+
* A create that loses a cross-container race logs and defers to the next
|
|
115
|
+
* boot, which observes the winner's valid index and no-ops.
|
|
116
|
+
*/
|
|
117
|
+
async function ensureResolvedCoverIndex() {
|
|
118
|
+
const pool = (0, db_1.getPool)();
|
|
119
|
+
try {
|
|
120
|
+
const { rows } = await pool.query(RESOLVED_COVER_INDEX_STATE);
|
|
121
|
+
if (rows[0]?.valid === true)
|
|
122
|
+
return;
|
|
123
|
+
if (rows.length > 0) {
|
|
124
|
+
await pool.query('DROP INDEX IF EXISTS idx_hmsh_esc_resolved_cover');
|
|
125
|
+
}
|
|
126
|
+
await pool.query(ENSURE_RESOLVED_COVER_INDEX);
|
|
127
|
+
logger_1.loggerRegistry.info('[escalation] idx_hmsh_esc_resolved_cover ensured');
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
logger_1.loggerRegistry.warn(`[escalation] resolved-cover index build deferred to next boot: ${err.message}`);
|
|
131
|
+
}
|
|
77
132
|
}
|
|
78
133
|
// Migrate legacy `lt_escalations` rows into `hmsh_escalations` (idempotent),
|
|
79
134
|
// then preserve the original table as `lt_escalations_legacy` rather than
|
|
@@ -139,3 +194,39 @@ CREATE OR REPLACE VIEW public.lt_escalations AS
|
|
|
139
194
|
SELECT *,
|
|
140
195
|
(assigned_to IS NULL OR assigned_until IS NULL OR assigned_until <= NOW()) AS available
|
|
141
196
|
FROM public.hmsh_escalations;`;
|
|
197
|
+
// Index ownership — hmsh 0.25.0 ships the general aggregate indexes on
|
|
198
|
+
// hmsh_escalations (idx_hmsh_esc_stats_pending covers the pending backlog
|
|
199
|
+
// incl. assigned_to/assigned_until; _stats_created and _stats_resolved bound
|
|
200
|
+
// the created/resolved windows). Long-tail keeps exactly ONE app-layer index:
|
|
201
|
+
//
|
|
202
|
+
// idx_hmsh_esc_resolved_cover (role, resolved_at DESC, claimed_at, created_at)
|
|
203
|
+
// WHERE status = 'resolved'
|
|
204
|
+
// Serves STATION_PERIOD_METRICS_SQL: the (role, resolved_at ≥ window)
|
|
205
|
+
// prefix bounds each station's percentile scan to its own window, and the
|
|
206
|
+
// trailing claimed_at/created_at feed PERCENTILE_CONT(resolved_at -
|
|
207
|
+
// claimed_at) and (claimed_at - created_at) from the index.
|
|
208
|
+
//
|
|
209
|
+
// Everything else this branch once created is dropped below: the pending
|
|
210
|
+
// index duplicated _stats_pending, and the claimed cover had no consuming
|
|
211
|
+
// query — both were pure write amplification on the hot claim/resolve path.
|
|
212
|
+
// ── Drops: superseded or branch-era-only names (idempotent no-ops elsewhere) ─
|
|
213
|
+
const DROP_OLD_RESOLVED_INDEX = `
|
|
214
|
+
DROP INDEX IF EXISTS idx_hmsh_escalations_role_resolved_at`;
|
|
215
|
+
const DROP_OLD_CLAIMED_INDEX = `
|
|
216
|
+
DROP INDEX IF EXISTS idx_hmsh_escalations_role_claimed_at`;
|
|
217
|
+
const DROP_SUPERSEDED_PENDING_INDEX = `
|
|
218
|
+
DROP INDEX IF EXISTS idx_hmsh_esc_pending_role_created`;
|
|
219
|
+
const DROP_UNCONSUMED_CLAIMED_COVER_INDEX = `
|
|
220
|
+
DROP INDEX IF EXISTS idx_hmsh_esc_claimed_cover`;
|
|
221
|
+
// ── The station-percentile cover ─────────────────────────────────────────────
|
|
222
|
+
/** Reports existence + validity so a crash-orphaned INVALID build is rebuilt. */
|
|
223
|
+
const RESOLVED_COVER_INDEX_STATE = `
|
|
224
|
+
SELECT i.indisvalid AS valid
|
|
225
|
+
FROM pg_class c
|
|
226
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
227
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
228
|
+
WHERE c.relname = 'idx_hmsh_esc_resolved_cover' AND n.nspname = 'public'`;
|
|
229
|
+
const ENSURE_RESOLVED_COVER_INDEX = `\
|
|
230
|
+
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_hmsh_esc_resolved_cover
|
|
231
|
+
ON public.hmsh_escalations (role, resolved_at DESC, claimed_at, created_at)
|
|
232
|
+
WHERE status = 'resolved'`;
|
|
@@ -28,6 +28,15 @@ const client_1 = require("./client");
|
|
|
28
28
|
const queries_1 = require("./queries");
|
|
29
29
|
const map_1 = require("./map");
|
|
30
30
|
const sql_1 = require("./sql");
|
|
31
|
+
/**
|
|
32
|
+
* Every escalation write publishes an event AND drops this container's cached
|
|
33
|
+
* aggregates — the event triggers a dashboard refetch, which must observe the
|
|
34
|
+
* write rather than a pre-change aggregate held for up to a cache TTL.
|
|
35
|
+
*/
|
|
36
|
+
function publishEscalationChange(...args) {
|
|
37
|
+
(0, queries_1.invalidateEscalationAggregates)();
|
|
38
|
+
return (0, publish_1.publishEscalationEvent)(...args);
|
|
39
|
+
}
|
|
31
40
|
// All escalation state lives in `public.hmsh_escalations` (HotMesh 0.22.3),
|
|
32
41
|
// reached through `client.escalations.*`. The function signatures and return
|
|
33
42
|
// shapes below are the frozen public surface — only the storage path changed.
|
|
@@ -56,7 +65,7 @@ async function createEscalation(input) {
|
|
|
56
65
|
escalationPayload: (0, map_1.toJsonObject)(input.escalation_payload),
|
|
57
66
|
});
|
|
58
67
|
const escalation = (0, map_1.toEscalationRecord)(entry);
|
|
59
|
-
(
|
|
68
|
+
publishEscalationChange({
|
|
60
69
|
type: 'escalation.created',
|
|
61
70
|
source: 'service',
|
|
62
71
|
workflowId: escalation.workflow_id || '',
|
|
@@ -79,7 +88,7 @@ async function claimEscalation(id, userId, durationMinutes = 30) {
|
|
|
79
88
|
if (!result.ok)
|
|
80
89
|
return null;
|
|
81
90
|
const escalation = (0, map_1.toEscalationRecord)(result.entry);
|
|
82
|
-
(
|
|
91
|
+
publishEscalationChange({
|
|
83
92
|
type: 'escalation.claimed',
|
|
84
93
|
source: 'service',
|
|
85
94
|
workflowId: escalation.workflow_id || '',
|
|
@@ -110,7 +119,7 @@ async function resolveEscalation(id, resolverPayload, metadata) {
|
|
|
110
119
|
if (!result.ok)
|
|
111
120
|
return null;
|
|
112
121
|
const escalation = (0, map_1.toEscalationRecord)(result.entry);
|
|
113
|
-
(
|
|
122
|
+
publishEscalationChange({
|
|
114
123
|
type: 'escalation.resolved',
|
|
115
124
|
source: 'service',
|
|
116
125
|
workflowId: escalation.workflow_id || '',
|
|
@@ -142,7 +151,7 @@ async function resolveEscalationsByIds(ids, resolverPayload, metadata) {
|
|
|
142
151
|
const rows = await client.resolveMany({ ids, resolverPayload, metadata });
|
|
143
152
|
const records = (0, map_1.toEscalationRecords)(rows);
|
|
144
153
|
for (const escalation of records) {
|
|
145
|
-
(
|
|
154
|
+
publishEscalationChange({
|
|
146
155
|
type: 'escalation.resolved',
|
|
147
156
|
source: 'service',
|
|
148
157
|
workflowId: escalation.workflow_id || '',
|
|
@@ -197,7 +206,7 @@ async function cancelEscalation(id) {
|
|
|
197
206
|
if (!result.ok)
|
|
198
207
|
return null;
|
|
199
208
|
const cancelled = (0, map_1.toEscalationRecord)(result.entry);
|
|
200
|
-
(
|
|
209
|
+
publishEscalationChange({
|
|
201
210
|
type: 'escalation.cancelled',
|
|
202
211
|
source: 'service',
|
|
203
212
|
workflowId: cancelled.workflow_id || '',
|
|
@@ -256,7 +265,7 @@ async function releaseEscalation(id, userId) {
|
|
|
256
265
|
if (!result.ok)
|
|
257
266
|
return null;
|
|
258
267
|
const released = (0, map_1.toEscalationRecord)(result.entry);
|
|
259
|
-
(
|
|
268
|
+
publishEscalationChange({
|
|
260
269
|
type: 'escalation.released',
|
|
261
270
|
source: 'service',
|
|
262
271
|
workflowId: released.workflow_id || '',
|
|
@@ -332,7 +341,7 @@ async function cancelEscalationsByWorkflowId(workflowId) {
|
|
|
332
341
|
AND status = 'pending'
|
|
333
342
|
RETURNING id, workflow_id, workflow_type, task_queue`, [workflowId]);
|
|
334
343
|
for (const row of rows) {
|
|
335
|
-
(
|
|
344
|
+
publishEscalationChange({
|
|
336
345
|
type: 'escalation.cancelled',
|
|
337
346
|
source: 'service',
|
|
338
347
|
workflowId: row.workflow_id || '',
|
|
@@ -419,7 +428,7 @@ async function claimByMetadata(key, value, userId, durationMinutes = 30, metadat
|
|
|
419
428
|
if (!result.ok)
|
|
420
429
|
return null;
|
|
421
430
|
const escalation = (0, map_1.toEscalationRecord)(result.entry);
|
|
422
|
-
(
|
|
431
|
+
publishEscalationChange({
|
|
423
432
|
type: 'escalation.claimed',
|
|
424
433
|
source: 'service',
|
|
425
434
|
workflowId: escalation.workflow_id || '',
|
|
@@ -461,7 +470,7 @@ async function resolveByMetadataAtomic(key, value, userId, resolverPayload, meta
|
|
|
461
470
|
const row = rows[0];
|
|
462
471
|
if (row.outcome === 'resolved') {
|
|
463
472
|
const escalation = (0, map_1.toEscalationRecord)(row);
|
|
464
|
-
(
|
|
473
|
+
publishEscalationChange({
|
|
465
474
|
type: 'escalation.resolved',
|
|
466
475
|
source: 'service',
|
|
467
476
|
workflowId: escalation.workflow_id || '',
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class TtlCache<T> {
|
|
2
|
+
private readonly ttlMs;
|
|
3
|
+
private readonly store;
|
|
4
|
+
constructor(ttlMs: number);
|
|
5
|
+
/**
|
|
6
|
+
* Return the cached value for `key` when it is younger than the TTL. On a
|
|
7
|
+
* miss, run `compute`, cache the in-flight promise so concurrent callers
|
|
8
|
+
* share one computation (no thundering herd), and return it. A rejected
|
|
9
|
+
* computation is evicted so the next caller retries rather than caching the
|
|
10
|
+
* failure.
|
|
11
|
+
*/
|
|
12
|
+
resolve(key: string, compute: () => Promise<T>): Promise<T>;
|
|
13
|
+
clear(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Single-flight, short-TTL in-process cache.
|
|
4
|
+
//
|
|
5
|
+
// Memoizes the expensive half of the station-metrics query (percentile /
|
|
6
|
+
// throughput aggregates) so a burst of socket-driven dashboard refreshes and
|
|
7
|
+
// all concurrent viewers collapse to one computation per TTL window. Live
|
|
8
|
+
// counts are never cached — only slow-changing historical aggregates, which
|
|
9
|
+
// tolerate 30-60s staleness.
|
|
10
|
+
//
|
|
11
|
+
// Per-container by design: the values are read-only aggregates and cross-
|
|
12
|
+
// container drift within the TTL is acceptable. No cross-node coordination.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.TtlCache = void 0;
|
|
16
|
+
class TtlCache {
|
|
17
|
+
constructor(ttlMs) {
|
|
18
|
+
this.ttlMs = ttlMs;
|
|
19
|
+
this.store = new Map();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Return the cached value for `key` when it is younger than the TTL. On a
|
|
23
|
+
* miss, run `compute`, cache the in-flight promise so concurrent callers
|
|
24
|
+
* share one computation (no thundering herd), and return it. A rejected
|
|
25
|
+
* computation is evicted so the next caller retries rather than caching the
|
|
26
|
+
* failure.
|
|
27
|
+
*/
|
|
28
|
+
async resolve(key, compute) {
|
|
29
|
+
const hit = this.store.get(key);
|
|
30
|
+
if (hit && Date.now() - hit.at < this.ttlMs)
|
|
31
|
+
return hit.value;
|
|
32
|
+
// Sweep every expired entry on each miss. Keys embed the caller's role
|
|
33
|
+
// set × period, so without eviction the map accretes one row-array per
|
|
34
|
+
// distinct viewer cohort for the life of the process.
|
|
35
|
+
for (const [k, entry] of this.store) {
|
|
36
|
+
if (Date.now() - entry.at >= this.ttlMs)
|
|
37
|
+
this.store.delete(k);
|
|
38
|
+
}
|
|
39
|
+
const value = compute();
|
|
40
|
+
this.store.set(key, { at: Date.now(), value });
|
|
41
|
+
try {
|
|
42
|
+
return await value;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
this.store.delete(key);
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
this.store.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.TtlCache = TtlCache;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { LTEscalationRecord, LTEscalationStatus } from '../../types';
|
|
2
|
-
import type { EscalationStats } from './types';
|
|
2
|
+
import type { EscalationStats, StationMetric } from './types';
|
|
3
3
|
import type { FacetQuery } from '../../types';
|
|
4
4
|
/**
|
|
5
5
|
* Scoped faceted search — the HUMAN operations query. Composes the read-scope
|
|
6
6
|
* predicate (global → none; else `role ∈ allRoles OR (role ∈ selfRoles AND
|
|
7
7
|
* assigned_to = me)`) with the full FacetQuery language (`buildFacetWhere`:
|
|
8
8
|
* facets `@>`, block, numeric range, exists, status, available) plus the extra
|
|
9
|
-
* top-level filters (type/subtype/priority/assigned_to) and
|
|
9
|
+
* top-level filters (type/subtype/priority/assigned_to) and an exact correlation-id
|
|
10
|
+
* match (id/workflow_id/origin_id), then orders
|
|
10
11
|
* by `buildFacetOrder` (columns + `metadata.<key>`). Every value is bound; the
|
|
11
12
|
* COUNT shares the WHERE so totals stay correct across pages — no client-side
|
|
12
13
|
* filtering. Reads go through the `public.lt_escalations` view.
|
|
@@ -83,3 +84,14 @@ export declare function listAvailableEscalations(filters: {
|
|
|
83
84
|
escalations: LTEscalationRecord[];
|
|
84
85
|
total: number;
|
|
85
86
|
}>;
|
|
87
|
+
/**
|
|
88
|
+
* Drop cached aggregates (period metrics + escalation stats). Called by the
|
|
89
|
+
* write paths in crud.ts alongside each escalation event publish: the event
|
|
90
|
+
* tells dashboards "data changed", so the very next fetch on this container
|
|
91
|
+
* must recompute rather than serve the pre-change aggregate for up to a TTL.
|
|
92
|
+
* Cross-container staleness within the TTL remains and is acceptable.
|
|
93
|
+
*/
|
|
94
|
+
export declare function invalidateEscalationAggregates(): void;
|
|
95
|
+
/** Test hook: drop cached period metrics AND escalation stats so tests observe fresh rows. */
|
|
96
|
+
export declare function resetStationMetricsCache(): void;
|
|
97
|
+
export declare function getStationMetrics(visibleRoles: string[] | undefined, period?: string): Promise<StationMetric[]>;
|