@hotmeshio/long-tail 0.8.3 → 0.8.5
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 +19 -60
- package/build/api/escalations/index.d.ts +1 -1
- package/build/api/escalations/index.js +2 -1
- package/build/api/escalations/resolve.d.ts +28 -0
- package/build/api/escalations/resolve.js +110 -4
- package/build/api/escalations/single.js +8 -4
- package/build/api/roles.d.ts +1 -0
- package/build/lib/db/index.js +12 -1
- package/build/lib/db/migrate.js +27 -0
- package/build/modules/defaults.d.ts +2 -0
- package/build/modules/defaults.js +3 -1
- package/build/routes/escalations/resolve.js +18 -0
- package/build/scripts/twin-farm.d.ts +12 -13
- package/build/scripts/twin-farm.js +61 -75
- package/build/sdk/index.d.ts +8 -0
- package/build/sdk/index.js +1 -0
- package/build/services/escalation/crud.d.ts +36 -0
- package/build/services/escalation/crud.js +53 -0
- package/build/services/escalation/queries.d.ts +13 -0
- package/build/services/escalation/queries.js +27 -0
- package/build/services/maintenance/index.js +22 -0
- package/build/services/orchestrator/condition.d.ts +18 -12
- package/build/services/orchestrator/condition.js +11 -8
- package/build/services/role/index.d.ts +1 -2
- package/build/services/role/index.js +1 -2
- package/build/services/role/schema-validator.d.ts +9 -0
- package/build/services/role/schema-validator.js +42 -0
- package/build/services/role/sql.d.ts +1 -1
- package/build/services/role/sql.js +2 -2
- package/build/services/role/types.d.ts +8 -6
- package/build/system/mcp-servers/admin/schemas.js +4 -2
- package/build/system/mcp-servers/human-queue-schemas.js +6 -2
- package/build/system/mcp-servers/human-queue.js +11 -3
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/config.d.ts +2 -0
- package/build/types/maintenance.d.ts +6 -1
- package/build/types/startup.d.ts +5 -1
- package/dashboard/dist/assets/{AdminDashboard-Dhr7sUq6.js → AdminDashboard-RrSvb8d-.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-Dhr7sUq6.js.map → AdminDashboard-RrSvb8d-.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-CUPm8FjH.js → AgentConfigPage-5RMdI0ku.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-CUPm8FjH.js.map → AgentConfigPage-5RMdI0ku.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-C9Tmmhhc.js → AgentDetailPage-C_-sIBcr.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-C9Tmmhhc.js.map → AgentDetailPage-C_-sIBcr.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-Pj47ZJ2U.js → AgentsPage-CxNNtSsf.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-Pj47ZJ2U.js.map → AgentsPage-CxNNtSsf.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-BxakNqha.js → AvailableEscalationsPage-BcbhA6NE.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-BxakNqha.js.map → AvailableEscalationsPage-BcbhA6NE.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-CCIo5ic0.js → BotPicker-BQVz6bye.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-CCIo5ic0.js.map → BotPicker-BQVz6bye.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-XFVq4iPX.js → CapabilitiesPage-DlZ8SgO3.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-XFVq4iPX.js.map → CapabilitiesPage-DlZ8SgO3.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-CBKICsCu.js → CollapsibleSection-lo8Qx4zb.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-CBKICsCu.js.map → CollapsibleSection-lo8Qx4zb.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-BN_d7ybc.js → CredentialsPage-DJ9DyXsO.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-BN_d7ybc.js.map → CredentialsPage-DJ9DyXsO.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-4V_EXMUg.js → CronLabel-o0dbisw1.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-4V_EXMUg.js.map → CronLabel-o0dbisw1.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-BdurQj-B.js → CustomDurationPicker-CGiwKSVJ.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-BdurQj-B.js.map → CustomDurationPicker-CGiwKSVJ.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-CO8XMSJX.js → ElapsedCell-N22STQcg.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-CO8XMSJX.js.map → ElapsedCell-N22STQcg.js.map} +1 -1
- package/dashboard/dist/assets/EscalationSchemaPage-Zsmewko2.js +9 -0
- package/dashboard/dist/assets/EscalationSchemaPage-Zsmewko2.js.map +1 -0
- package/dashboard/dist/assets/{EscalationsOverview-qlo0mxxa.js → EscalationsOverview-CE2p70Ct.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-qlo0mxxa.js.map → EscalationsOverview-CE2p70Ct.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-D9RjAT_a.js → EventTable-B3Gj7Ayg.js} +2 -2
- package/dashboard/dist/assets/{EventTable-D9RjAT_a.js.map → EventTable-B3Gj7Ayg.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-CU3r2tzN.js → GraphInvokePage-DywWVSdj.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-CU3r2tzN.js.map → GraphInvokePage-DywWVSdj.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-B7pb4-Gx.js → HomePage-CxtBiULd.js} +2 -2
- package/dashboard/dist/assets/{HomePage-B7pb4-Gx.js.map → HomePage-CxtBiULd.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-C_64Y22Z.js → ListToolbar-Ok2O0wmC.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-C_64Y22Z.js.map → ListToolbar-Ok2O0wmC.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-D-4eSOTc.js → McpOverview-CTCYZXIp.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-D-4eSOTc.js.map → McpOverview-CTCYZXIp.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-BYwDb8LC.js → McpQueryDetailPage-BOaXf0bZ.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-BYwDb8LC.js.map → McpQueryDetailPage-BOaXf0bZ.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-CIijB5t2.js → McpQueryPage-BbYojoHu.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-CIijB5t2.js.map → McpQueryPage-BbYojoHu.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-CQmhfgju.js → McpRunDetailPage-CXGCVmPq.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-CQmhfgju.js.map → McpRunDetailPage-CXGCVmPq.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CdDyeOdi.js → McpRunsPage-Df1GqxUg.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CdDyeOdi.js.map → McpRunsPage-Df1GqxUg.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-DgZYi-BC.js +2 -0
- package/dashboard/dist/assets/OperationsPage-DgZYi-BC.js.map +1 -0
- package/dashboard/dist/assets/{OperatorDashboard-DFORmS46.js → OperatorDashboard-DIR7XDeD.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-DFORmS46.js.map → OperatorDashboard-DIR7XDeD.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-DwypVFE2.js → ProcessDetailPage-BuSi654J.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-DwypVFE2.js.map → ProcessDetailPage-BuSi654J.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-tNLvALwT.js → ProcessesListPage-Dhik7XpW.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-tNLvALwT.js.map → ProcessesListPage-Dhik7XpW.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-BfzqQxMU.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-BfzqQxMU.js.map +1 -0
- package/dashboard/dist/assets/{RolesPage-txueySPI.js → RolesPage-BRicD5mx.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-txueySPI.js.map → RolesPage-BRicD5mx.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-Bvefon9B.js → RunAsSelector-DipyBf_T.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-Bvefon9B.js.map → RunAsSelector-DipyBf_T.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-CeQCzVIA.js → StickyPagination-CD4W450h.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-CeQCzVIA.js.map → StickyPagination-CD4W450h.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-6wEm6o6t.js → StreamMessageDetail-BbDdWDUY.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-6wEm6o6t.js.map → StreamMessageDetail-BbDdWDUY.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-C97Rldf5.js → SwimlaneTimeline-Bktv2vi5.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-C97Rldf5.js.map → SwimlaneTimeline-Bktv2vi5.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-B09mLOCE.js → TaskDetailPage-pSU3xuG9.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-B09mLOCE.js.map → TaskDetailPage-pSU3xuG9.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CQhiSGkN.js → TasksListPage-M6ok-Nfd.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CQhiSGkN.js.map → TasksListPage-M6ok-Nfd.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-BkyeBcMF.js → TimeAgo-DVWhRNbi.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-BkyeBcMF.js.map → TimeAgo-DVWhRNbi.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-DVp_UdEB.js → TimestampCell-rG7pmIkW.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-DVp_UdEB.js.map → TimestampCell-rG7pmIkW.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-CZOuRP_i.js → ToolTestPanel-CLcMA8OE.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-CZOuRP_i.js.map → ToolTestPanel-CLcMA8OE.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-0mvQde8R.js → TopicDetailPage-C9gvoWYu.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-0mvQde8R.js.map → TopicDetailPage-C9gvoWYu.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-DkvsHLLe.js → TopicsPage-CIGi3TdT.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-DkvsHLLe.js.map → TopicsPage-CIGi3TdT.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-EaD_Tz9y.js → UserName-CifbOahk.js} +2 -2
- package/dashboard/dist/assets/{UserName-EaD_Tz9y.js.map → UserName-CifbOahk.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-CjMSQ9Bb.js → WorkflowExecutionPage-BR0oVx_p.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-CjMSQ9Bb.js.map → WorkflowExecutionPage-BR0oVx_p.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-BgUz_jO6.js → WorkflowsDashboard-CSYrYQsO.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-BgUz_jO6.js.map → WorkflowsDashboard-CSYrYQsO.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-BzUjUfAz.js → WorkflowsOverview-CaXWYZuw.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-BzUjUfAz.js.map → WorkflowsOverview-CaXWYZuw.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C48Ui513.js → YamlWorkflowDetailPage-CRBXahFJ.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C48Ui513.js.map → YamlWorkflowDetailPage-CRBXahFJ.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-CQ-dk84N.js → YamlWorkflowsPage-D5zc8Lta.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-CQ-dk84N.js.map → YamlWorkflowsPage-D5zc8Lta.js.map} +1 -1
- package/dashboard/dist/assets/{agents-O1GlQE9p.js → agents-D7vYXeLj.js} +2 -2
- package/dashboard/dist/assets/{agents-O1GlQE9p.js.map → agents-D7vYXeLj.js.map} +1 -1
- package/dashboard/dist/assets/{bots-DB5Z7MEk.js → bots-CKaYN2fL.js} +2 -2
- package/dashboard/dist/assets/{bots-DB5Z7MEk.js.map → bots-CKaYN2fL.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-Du9EfNdT.js → capabilities-BknM6oFm.js} +2 -2
- package/dashboard/dist/assets/{capabilities-Du9EfNdT.js.map → capabilities-BknM6oFm.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-ku4V2nVk.js → controlplane-BodNGCr-.js} +2 -2
- package/dashboard/dist/assets/{controlplane-ku4V2nVk.js.map → controlplane-BodNGCr-.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-oVVz7Lw2.js → escalation-CH_XK2XM.js} +2 -2
- package/dashboard/dist/assets/{escalation-oVVz7Lw2.js.map → escalation-CH_XK2XM.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-C-xaR_Qg.js → escalation-columns-CcbLKRI4.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-C-xaR_Qg.js.map → escalation-columns-CcbLKRI4.js.map} +1 -1
- package/dashboard/dist/assets/index--xNp5rx6.css +1 -0
- package/dashboard/dist/assets/{index-aHhMmZ0W.js → index-B-LQ8fsJ.js} +2 -2
- package/dashboard/dist/assets/{index-aHhMmZ0W.js.map → index-B-LQ8fsJ.js.map} +1 -1
- package/dashboard/dist/assets/{index-CyXilW90.js → index-BH67e1Ah.js} +2 -2
- package/dashboard/dist/assets/{index-CyXilW90.js.map → index-BH67e1Ah.js.map} +1 -1
- package/dashboard/dist/assets/{index-MbEPoyTe.js → index-B_aFcGv9.js} +4 -4
- package/dashboard/dist/assets/{index-MbEPoyTe.js.map → index-B_aFcGv9.js.map} +1 -1
- package/dashboard/dist/assets/{index-DsKK1fCc.js → index-Bwofyfi8.js} +2 -2
- package/dashboard/dist/assets/{index-DsKK1fCc.js.map → index-Bwofyfi8.js.map} +1 -1
- package/dashboard/dist/assets/{index-61yKjTkk.js → index-C3QKGY5C.js} +2 -2
- package/dashboard/dist/assets/{index-61yKjTkk.js.map → index-C3QKGY5C.js.map} +1 -1
- package/dashboard/dist/assets/{index-BwXtXaw8.js → index-CJpywoA8.js} +2 -2
- package/dashboard/dist/assets/{index-BwXtXaw8.js.map → index-CJpywoA8.js.map} +1 -1
- package/dashboard/dist/assets/index-CV8soXIM.js +5 -0
- package/dashboard/dist/assets/index-CV8soXIM.js.map +1 -0
- package/dashboard/dist/assets/{index-CxlsLEr1.js → index-CdN-Z8-r.js} +2 -2
- package/dashboard/dist/assets/{index-CxlsLEr1.js.map → index-CdN-Z8-r.js.map} +1 -1
- package/dashboard/dist/assets/{index-DcZDyLHC.js → index-D7NHjvA4.js} +2 -2
- package/dashboard/dist/assets/{index-DcZDyLHC.js.map → index-D7NHjvA4.js.map} +1 -1
- package/dashboard/dist/assets/{index-DkZnXWTA.js → index-DHhz71yv.js} +2 -2
- package/dashboard/dist/assets/{index-DkZnXWTA.js.map → index-DHhz71yv.js.map} +1 -1
- package/dashboard/dist/assets/{index-DgAmkmtv.js → index-Gt2Rebs9.js} +2 -2
- package/dashboard/dist/assets/{index-DgAmkmtv.js.map → index-Gt2Rebs9.js.map} +1 -1
- package/dashboard/dist/assets/index-IIq5Tkqs.js +6 -0
- package/dashboard/dist/assets/index-IIq5Tkqs.js.map +1 -0
- package/dashboard/dist/assets/{index-Bcgsf2yD.js → index-_gh2K9DU.js} +2 -2
- package/dashboard/dist/assets/{index-Bcgsf2yD.js.map → index-_gh2K9DU.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-CTjJVvIG.js → knowledge-CE6gjN3G.js} +2 -2
- package/dashboard/dist/assets/{knowledge-CTjJVvIG.js.map → knowledge-CE6gjN3G.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-CsHkygRS.js → mcp-B4F-T-7R.js} +2 -2
- package/dashboard/dist/assets/{mcp-CsHkygRS.js.map → mcp-B4F-T-7R.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-BCt4tpjO.js → mcp-query-CYOarL1K.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-BCt4tpjO.js.map → mcp-query-CYOarL1K.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-BpGmuS5V.js → pipelines-B4gB9qMx.js} +2 -2
- package/dashboard/dist/assets/{pipelines-BpGmuS5V.js.map → pipelines-B4gB9qMx.js.map} +1 -1
- package/dashboard/dist/assets/{roles-lHLgLwOY.js → roles-LZf98ePg.js} +2 -2
- package/dashboard/dist/assets/{roles-lHLgLwOY.js.map → roles-LZf98ePg.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-BHfimh-E.js → tasks-GG8c_U9w.js} +2 -2
- package/dashboard/dist/assets/{tasks-BHfimh-E.js.map → tasks-GG8c_U9w.js.map} +1 -1
- package/dashboard/dist/assets/{topics-BW2cS79q.js → topics-C7-6oOgA.js} +2 -2
- package/dashboard/dist/assets/{topics-BW2cS79q.js.map → topics-C7-6oOgA.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-CeOIM1Ix.js → useEventHooks-82IiMmOc.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-CeOIM1Ix.js.map → useEventHooks-82IiMmOc.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-CEVLPGGY.js → useNamespace-DQEqD2LA.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-CEVLPGGY.js.map → useNamespace-DQEqD2LA.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-DdRNcy7s.js → useYamlActivityEvents-Bm0-ewLx.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-DdRNcy7s.js.map → useYamlActivityEvents-Bm0-ewLx.js.map} +1 -1
- package/dashboard/dist/assets/{users-CAih5Fty.js → users-DHx2ium7.js} +2 -2
- package/dashboard/dist/assets/{users-CAih5Fty.js.map → users-DHx2ium7.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-C_gCwB9q.js → workflows-qIbkeQ_5.js} +2 -2
- package/dashboard/dist/assets/{workflows-C_gCwB9q.js.map → workflows-qIbkeQ_5.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-CPabf8Sf.js → yaml-workflows-FibyeHpo.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-CPabf8Sf.js.map → yaml-workflows-FibyeHpo.js.map} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/docs/api/http/escalations.md +28 -4
- package/docs/api/http/roles.md +1 -1
- package/docs/api/http/workflows.md +1 -1
- package/docs/api/sdk/escalations.md +40 -5
- package/docs/api/sdk/workflows.md +1 -1
- package/docs/dashboard.md +3 -3
- package/docs/data.md +2 -2
- package/docs/hitl-guide.md +114 -6
- package/docs/maintenance.md +6 -3
- package/docs/oauth-and-delegation.md +1 -1
- package/package.json +2 -2
- package/dashboard/dist/assets/EscalationSchemaPage-hzFtuyha.js +0 -9
- package/dashboard/dist/assets/EscalationSchemaPage-hzFtuyha.js.map +0 -1
- package/dashboard/dist/assets/OperationsPage-Cz_0K8tj.js +0 -2
- package/dashboard/dist/assets/OperationsPage-Cz_0K8tj.js.map +0 -1
- package/dashboard/dist/assets/RoleDetailPage-BPsrUqIa.js +0 -11
- package/dashboard/dist/assets/RoleDetailPage-BPsrUqIa.js.map +0 -1
- package/dashboard/dist/assets/index-BdqKh0UK.css +0 -1
- package/dashboard/dist/assets/index-CAzFU1rn.js +0 -9
- package/dashboard/dist/assets/index-CAzFU1rn.js.map +0 -1
- package/dashboard/dist/assets/index-D0bDVU2a.js +0 -6
- package/dashboard/dist/assets/index-D0bDVU2a.js.map +0 -1
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Spins up the whole DIGITAL side of the physical/digital twin farm from one
|
|
6
6
|
* command and plays every human part automatically, so a single run exercises
|
|
7
|
-
* the full lifecycle: register → advertise → dispatch → print →
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* the full lifecycle: onboard (register + bind) → advertise → dispatch → print →
|
|
8
|
+
* reconcile → settle, plus the failure detours (autonomous fail → inspect, and
|
|
9
|
+
* filament runout → change → resume). The PHYSICAL side is the app's Bambu
|
|
10
|
+
* backend — `BAMBU_BACKEND=mock` by default (a deterministic in-repo simulation),
|
|
11
|
+
* or `http` against a real Farm Manager Server once the office link is wired.
|
|
12
|
+
*
|
|
13
|
+
* Faults are injected deterministically via each order unit's `simOutcome` (the
|
|
14
|
+
* mock honors it), so the service escalations fire on demand — no racing.
|
|
11
15
|
*
|
|
12
16
|
* Usage:
|
|
13
17
|
* npm run twin:farm
|
|
14
18
|
* FLEET=6 ORDERS=10 UNITS=2 npm run twin:farm
|
|
15
|
-
* FAIL_PCT=
|
|
16
|
-
* FARM_MANAGER_URL=http://192.168.1.50:4000 npm run twin:farm # preflight the office link
|
|
19
|
+
* FAIL_PCT=30 FAIL_MODE=filament_runout npm run twin:farm # fault → service → recover
|
|
17
20
|
* FLEET=4 ORDERS=4 KEEP=1 npm run twin:farm # leave twins running to poke in the UI
|
|
18
21
|
*
|
|
19
22
|
* Env vars:
|
|
@@ -25,17 +28,13 @@
|
|
|
25
28
|
* FILAMENTS comma-separated filament pool the fleet + orders draw from
|
|
26
29
|
* (default "pla,petg")
|
|
27
30
|
* ARRIVAL_S average seconds between order arrivals (default 2)
|
|
28
|
-
* FAIL_PCT percent of
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* a wider window (see README).
|
|
32
|
-
* POLL_MS poll interval ms (default 750)
|
|
31
|
+
* FAIL_PCT percent of orders with one faulted unit (default 0)
|
|
32
|
+
* FAIL_MODE the injected fault: filament_runout | failed (default filament_runout)
|
|
33
|
+
* POLL_MS servicer/registrar poll interval ms (default 750)
|
|
33
34
|
* BROKER_IDLE idle broker ticks before it self-terminates (default 40)
|
|
34
35
|
* RUN_BROKER launch a broker (default 1; set 0 if one already runs)
|
|
35
36
|
* KEEP 1 = leave twins advertising at the end (default 0 = retire
|
|
36
37
|
* them with a power-down so nothing lingers between runs)
|
|
37
|
-
* FARM_MANAGER_URL optional: GET this before running and report reachability,
|
|
38
|
-
* a preflight for the office laptop → farm-manager-host link
|
|
39
38
|
*
|
|
40
39
|
* Capabilities: each twin is registered with a capability profile (xl/pdac/soft).
|
|
41
40
|
* By default the fleet is fully capable and only filament routes; set FLEET_CAPS
|
|
@@ -55,17 +54,17 @@ const UNITS = parseInt(process.env.UNITS || '2', 10);
|
|
|
55
54
|
const FILAMENTS = (process.env.FILAMENTS || 'pla,petg').split(',').map((f) => f.trim()).filter(Boolean);
|
|
56
55
|
const ARRIVAL_S = parseFloat(process.env.ARRIVAL_S || '2');
|
|
57
56
|
const FAIL_PCT = parseInt(process.env.FAIL_PCT || '0', 10);
|
|
57
|
+
const FAIL_MODE = (process.env.FAIL_MODE || 'filament_runout');
|
|
58
58
|
const POLL_MS = parseInt(process.env.POLL_MS || '750', 10);
|
|
59
59
|
const BROKER_IDLE = parseInt(process.env.BROKER_IDLE || '40', 10);
|
|
60
60
|
const RUN_BROKER = process.env.RUN_BROKER !== '0';
|
|
61
61
|
const KEEP = process.env.KEEP === '1';
|
|
62
|
-
const FARM_MANAGER_URL = process.env.FARM_MANAGER_URL || '';
|
|
63
62
|
const CAPABILITY_KEYS = ['xl', 'pdac', 'soft'];
|
|
64
63
|
const MODELS = ['Fab-X1', 'Fab-X1C', 'Fab-P2', 'Fab-Mini'];
|
|
65
64
|
let token = '';
|
|
66
65
|
let userId = '';
|
|
67
66
|
let done = false;
|
|
68
|
-
const stats = { registered: 0, dispatched: 0, printed: 0,
|
|
67
|
+
const stats = { registered: 0, dispatched: 0, printed: 0, faulted: 0, serviced: 0, settled: 0 };
|
|
69
68
|
function ts() { return new Date().toISOString().slice(11, 19); }
|
|
70
69
|
function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); }
|
|
71
70
|
function ageSeconds(iso) { return (Date.now() - new Date(iso).getTime()) / 1000; }
|
|
@@ -115,8 +114,9 @@ async function login() {
|
|
|
115
114
|
*/
|
|
116
115
|
const TWIN_ROLE_CONFIG = [
|
|
117
116
|
{ role: 'print-jobs', title: 'Print Jobs', parent_role: null, sla_minutes: 5, target_per_hour: 30, priority_threshold_minutes: 5, description: 'Order demand — one print-job escalation per unit, claimed as a set.' },
|
|
118
|
-
{ role: 'printer-fleet', title: 'Printer Fleet', parent_role: 'print-jobs', sla_minutes: 5, target_per_hour: 30, priority_threshold_minutes: 5, description: 'Twin availability adverts and in-flight print rows — the machines at work.', upstream_roles: ['print-servicer'] },
|
|
119
|
-
{ role: 'print-
|
|
117
|
+
{ role: 'printer-fleet', title: 'Printer Fleet', parent_role: 'print-jobs', sla_minutes: 5, target_per_hour: 30, priority_threshold_minutes: 5, description: 'Twin availability adverts and in-flight print rows — the machines at work.', upstream_roles: ['print-onboarder', 'print-servicer'] },
|
|
118
|
+
{ role: 'print-onboarder', title: 'Print Onboarder', parent_role: null, sla_minutes: 15, target_per_hour: 5, priority_threshold_minutes: 15, description: 'Register + bind a newly unboxed machine.' },
|
|
119
|
+
{ role: 'print-servicer', title: 'Print Servicer', parent_role: null, sla_minutes: 10, target_per_hour: 10, priority_threshold_minutes: 10, description: 'Reload filament, inspect failures, restore offline machines.' },
|
|
120
120
|
];
|
|
121
121
|
async function ensureTwinRoles() {
|
|
122
122
|
const details = await api('GET', '/api/roles/details').catch(() => ({ roles: [] }));
|
|
@@ -160,6 +160,10 @@ function planFleet(batchTag) {
|
|
|
160
160
|
return { printerId, wfId: printerId, filament, caps, model: MODELS[i % MODELS.length], serialNumber: `SN-${batchTag.slice(-6)}-${i}` };
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
+
/** Evenly distribute FAIL_PCT across the order sequence (deterministic). */
|
|
164
|
+
function hasFault(i) {
|
|
165
|
+
return Math.floor(((i + 1) * FAIL_PCT) / 100) > Math.floor((i * FAIL_PCT) / 100);
|
|
166
|
+
}
|
|
163
167
|
function planOrders(batchTag, fleet) {
|
|
164
168
|
return Array.from({ length: ORDERS }, (_, i) => {
|
|
165
169
|
const filament = FILAMENTS[i % FILAMENTS.length];
|
|
@@ -171,7 +175,8 @@ function planOrders(batchTag, fleet) {
|
|
|
171
175
|
const cap = CAPABILITY_KEYS[i % CAPABILITY_KEYS.length];
|
|
172
176
|
const capable = pool.filter((t) => t.caps[cap]).length;
|
|
173
177
|
const require = (i % 2 === 0 && capable >= units) ? { [cap]: true } : {};
|
|
174
|
-
|
|
178
|
+
const fault = hasFault(i) ? { unit: 0, mode: FAIL_MODE } : undefined;
|
|
179
|
+
return { index: i, wfId: `${batchTag}-o${i}`, orderId: `TWIN-${batchTag.slice(-6)}-${i}`, filament, require, units, fault };
|
|
175
180
|
});
|
|
176
181
|
}
|
|
177
182
|
// ── Launch twins + broker + orders ────────────────────────────────────────────
|
|
@@ -198,7 +203,10 @@ async function placeOrder(spec) {
|
|
|
198
203
|
orderId: spec.orderId,
|
|
199
204
|
filament: spec.filament,
|
|
200
205
|
require: spec.require,
|
|
201
|
-
units: Array.from({ length: spec.units }, (_, u) => ({
|
|
206
|
+
units: Array.from({ length: spec.units }, (_, u) => ({
|
|
207
|
+
gcodeUrl: `https://example.com/${spec.orderId}-u${u}.gcode`,
|
|
208
|
+
...(spec.fault && spec.fault.unit === u ? { simOutcome: spec.fault.mode } : {}),
|
|
209
|
+
})),
|
|
202
210
|
operatorId: userId,
|
|
203
211
|
},
|
|
204
212
|
metadata: { source: 'twin-farm' },
|
|
@@ -209,7 +217,7 @@ async function placeOrder(spec) {
|
|
|
209
217
|
async function registerLoop(fleet) {
|
|
210
218
|
while (!done) {
|
|
211
219
|
try {
|
|
212
|
-
const resp = await api('GET', '/api/escalations/available?role=print-
|
|
220
|
+
const resp = await api('GET', '/api/escalations/available?role=print-onboarder&subtype=registering&limit=100');
|
|
213
221
|
for (const esc of resp?.escalations ?? []) {
|
|
214
222
|
const spec = fleet.get(String(esc.metadata?.printerId ?? ''));
|
|
215
223
|
if (!spec)
|
|
@@ -238,17 +246,31 @@ async function registerLoop(fleet) {
|
|
|
238
246
|
await sleep(POLL_MS);
|
|
239
247
|
}
|
|
240
248
|
}
|
|
241
|
-
/**
|
|
249
|
+
/** The servicer's resolution for each escalation subtype the twin raises. Reloads
|
|
250
|
+
* the SAME filament the machine had (from the escalation metadata) so a service
|
|
251
|
+
* visit restores the machine rather than repurposing it. */
|
|
252
|
+
function servicePayload(esc) {
|
|
253
|
+
const subtype = esc.subtype;
|
|
254
|
+
if (subtype === 'filament_change')
|
|
255
|
+
return { filamentLoaded: esc.metadata?.filament || 'pla', notes: 'Reloaded by twin-farm' };
|
|
256
|
+
if (subtype === 'failure_inspect')
|
|
257
|
+
return { action: 'reset', notes: 'Inspected by twin-farm' };
|
|
258
|
+
if (subtype === 'offline_investigate')
|
|
259
|
+
return { action: 'checked', notes: 'Reconnected by twin-farm' };
|
|
260
|
+
return { action: 'restored', notes: 'Auto-serviced by twin-farm' };
|
|
261
|
+
}
|
|
262
|
+
/** Resolve every servicer escalation (except registration) — filament changes,
|
|
263
|
+
* failure inspections, offline investigations — the way a technician would. */
|
|
242
264
|
async function serviceLoop() {
|
|
243
265
|
while (!done) {
|
|
244
266
|
try {
|
|
245
|
-
const resp = await api('GET', '/api/escalations/available?role=print-servicer&
|
|
267
|
+
const resp = await api('GET', '/api/escalations/available?role=print-servicer&limit=100');
|
|
246
268
|
for (const esc of resp?.escalations ?? []) {
|
|
247
269
|
await api('POST', `/api/escalations/${esc.id}/resolve`, {
|
|
248
|
-
resolverPayload:
|
|
270
|
+
resolverPayload: servicePayload(esc),
|
|
249
271
|
}).then(() => {
|
|
250
272
|
stats.serviced++;
|
|
251
|
-
console.log(`[${ts()}] [service] ${esc.metadata?.printerId ?? esc.id.slice(0, 8)}
|
|
273
|
+
console.log(`[${ts()}] [service] ${esc.subtype.padEnd(18)} ${esc.metadata?.printerId ?? esc.id.slice(0, 8)} [${stats.serviced}]`);
|
|
252
274
|
}).catch(() => { });
|
|
253
275
|
}
|
|
254
276
|
}
|
|
@@ -259,35 +281,6 @@ async function serviceLoop() {
|
|
|
259
281
|
await sleep(POLL_MS);
|
|
260
282
|
}
|
|
261
283
|
}
|
|
262
|
-
/** Cancel a deterministic fraction of in-flight prints — the power-outage /
|
|
263
|
-
* dead-machine drill. Best-effort: it races the physical side's resolve. */
|
|
264
|
-
async function failLoop() {
|
|
265
|
-
if (FAIL_PCT <= 0)
|
|
266
|
-
return;
|
|
267
|
-
const acted = new Set();
|
|
268
|
-
while (!done) {
|
|
269
|
-
try {
|
|
270
|
-
const resp = await api('GET', '/api/escalations/available?role=printer-fleet&subtype=printing&limit=100');
|
|
271
|
-
for (const esc of resp?.escalations ?? []) {
|
|
272
|
-
const jobId = String(esc.metadata?.jobId ?? esc.id);
|
|
273
|
-
if (acted.has(jobId))
|
|
274
|
-
continue;
|
|
275
|
-
if (jitter(jobId, 'fail') * 100 >= FAIL_PCT)
|
|
276
|
-
continue;
|
|
277
|
-
acted.add(jobId);
|
|
278
|
-
await api('POST', `/api/escalations/${esc.id}/cancel`).then(() => {
|
|
279
|
-
stats.cancelled++;
|
|
280
|
-
console.log(`[${ts()}] [FAIL] cancelled print ${jobId} — machine went dark [${stats.cancelled}]`);
|
|
281
|
-
}).catch(() => { });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
catch (err) {
|
|
285
|
-
logPollErr('fail', err);
|
|
286
|
-
}
|
|
287
|
-
if (!done)
|
|
288
|
-
await sleep(Math.min(POLL_MS, 300));
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
284
|
let lastPollErr = '';
|
|
292
285
|
function logPollErr(loop, err) {
|
|
293
286
|
const msg = `[${loop}] ${String(err?.message).slice(0, 80)}`;
|
|
@@ -316,7 +309,11 @@ async function orderLoop(orders) {
|
|
|
316
309
|
async function settlementWatch(orders) {
|
|
317
310
|
const settled = new Set();
|
|
318
311
|
let stalled = 0;
|
|
319
|
-
|
|
312
|
+
// Poll-driven twins add latency per order (poll pickup + print + report), and
|
|
313
|
+
// a fault adds a service round-trip, so the broker needs generous time to
|
|
314
|
+
// converge (it retries placement every tick via release-on-skip). This is a
|
|
315
|
+
// no-progress watchdog, not a deadline — steady settling keeps resetting it.
|
|
316
|
+
const STALL_S = Math.max(60, ORDERS * (ARRIVAL_S + 14));
|
|
320
317
|
while (!done) {
|
|
321
318
|
await sleep(1000);
|
|
322
319
|
let progressed = false;
|
|
@@ -334,8 +331,9 @@ async function settlementWatch(orders) {
|
|
|
334
331
|
const ok = outcomes.filter((o) => o.outcome === 'success').length;
|
|
335
332
|
const bad = outcomes.length - ok;
|
|
336
333
|
stats.printed += ok;
|
|
334
|
+
stats.faulted += bad;
|
|
337
335
|
stats.dispatched += outcomes.length;
|
|
338
|
-
console.log(`[${ts()}] [settle] ${order.orderId} — ${ok} printed${bad ? `, ${bad}
|
|
336
|
+
console.log(`[${ts()}] [settle] ${order.orderId} — ${ok} printed${bad ? `, ${bad} faulted` : ''} [${stats.settled}/${ORDERS}]`);
|
|
339
337
|
}
|
|
340
338
|
}
|
|
341
339
|
catch { /* 202 still running */ }
|
|
@@ -360,6 +358,10 @@ async function retireTwins(fleet) {
|
|
|
360
358
|
console.log(`[${ts()}] KEEP=1 — leaving ${fleet.length} twins advertising`);
|
|
361
359
|
return;
|
|
362
360
|
}
|
|
361
|
+
if (stats.settled < ORDERS) {
|
|
362
|
+
console.log(`[${ts()}] ${stats.settled}/${ORDERS} settled — leaving twins up so the broker can finish (rerun with KEEP=1 to keep them)`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
363
365
|
await sleep(2000); // let any last twin re-advertise after a service visit
|
|
364
366
|
let retired = 0;
|
|
365
367
|
for (let sweep = 0; sweep < 4 && retired < fleet.length; sweep++) {
|
|
@@ -379,21 +381,6 @@ async function retireTwins(fleet) {
|
|
|
379
381
|
}
|
|
380
382
|
console.log(`[${ts()}] Retired ${retired} idle twin${retired === 1 ? '' : 's'}`);
|
|
381
383
|
}
|
|
382
|
-
// ── Optional office-link preflight ────────────────────────────────────────────
|
|
383
|
-
async function preflightFarmManager() {
|
|
384
|
-
if (!FARM_MANAGER_URL)
|
|
385
|
-
return;
|
|
386
|
-
process.stdout.write(`[${ts()}] Preflight → ${FARM_MANAGER_URL} … `);
|
|
387
|
-
try {
|
|
388
|
-
const ctrl = new AbortController();
|
|
389
|
-
const t = setTimeout(() => ctrl.abort(), 4000);
|
|
390
|
-
const res = await fetch(FARM_MANAGER_URL, { signal: ctrl.signal }).finally(() => clearTimeout(t));
|
|
391
|
-
console.log(`reachable (HTTP ${res.status}). Set FARM_MANAGER_BACKEND=http + FARM_MANAGER_BASE_URL on the app to dispatch for real.`);
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
console.log(`UNREACHABLE (${String(err?.message).slice(0, 60)}). Check the office wifi link — see the README connectivity walkthrough.`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
384
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
398
385
|
async function main() {
|
|
399
386
|
const runId = Math.floor(Date.now() / 1000).toString();
|
|
@@ -401,17 +388,16 @@ async function main() {
|
|
|
401
388
|
const fleet = planFleet(batchTag);
|
|
402
389
|
const fleetById = new Map(fleet.map((t) => [t.printerId, t]));
|
|
403
390
|
console.log(`\n Twin Farm ── ${FLEET} printers, ${ORDERS} orders (≤${UNITS} units), filaments [${FILAMENTS.join(', ')}]`);
|
|
404
|
-
console.log(` arrival=${ARRIVAL_S}s
|
|
391
|
+
console.log(` arrival=${ARRIVAL_S}s fail=${FAIL_PCT}% (${FAIL_MODE}) broker=${RUN_BROKER ? `on (idle≤${BROKER_IDLE})` : 'external'} keep=${KEEP}`);
|
|
405
392
|
console.log(` Server: ${BASE_URL}\n`);
|
|
406
393
|
await login();
|
|
407
394
|
await ensureTwinRoles();
|
|
408
|
-
|
|
409
|
-
// 1. Launch the fleet — each twin raises a registration escalation and parks.
|
|
395
|
+
// 1. Launch the fleet — each twin onboards (register → bind) then advertises.
|
|
410
396
|
console.log(`\n[${ts()}] Launching ${FLEET} twins …`);
|
|
411
397
|
for (const spec of fleet)
|
|
412
398
|
await startTwin(spec);
|
|
413
399
|
// 2. Register + service loops start now so twins come online as they appear.
|
|
414
|
-
const humanLoops = [registerLoop(fleetById), serviceLoop()
|
|
400
|
+
const humanLoops = [registerLoop(fleetById), serviceLoop()];
|
|
415
401
|
// 3. Broker (once the humans are watching).
|
|
416
402
|
if (RUN_BROKER) {
|
|
417
403
|
await startBroker(batchTag);
|
|
@@ -433,7 +419,7 @@ async function main() {
|
|
|
433
419
|
console.log(`\n[${ts()}] Done in ${elapsed}s`);
|
|
434
420
|
console.log(` Registered: ${stats.registered}/${FLEET}`);
|
|
435
421
|
console.log(` Orders: ${stats.settled}/${ORDERS} settled`);
|
|
436
|
-
console.log(` Units: ${stats.printed} printed${stats.
|
|
422
|
+
console.log(` Units: ${stats.printed} printed${stats.faulted ? `, ${stats.faulted} faulted` : ''} (${stats.dispatched} dispatched)`);
|
|
437
423
|
console.log(` Serviced: ${stats.serviced}`);
|
|
438
424
|
console.log(`\n Dashboard: ${BASE_URL}/escalations\n`);
|
|
439
425
|
process.exit(0);
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -153,6 +153,14 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
153
153
|
resolverPayload: Record<string, any>;
|
|
154
154
|
metadata?: Record<string, any>;
|
|
155
155
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
156
|
+
resolveAllOrNone: (input: {
|
|
157
|
+
items: Array<{
|
|
158
|
+
id: string;
|
|
159
|
+
resolverPayload: Record<string, any>;
|
|
160
|
+
}>;
|
|
161
|
+
metadata?: Record<string, any>;
|
|
162
|
+
requireClaimed?: boolean;
|
|
163
|
+
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
156
164
|
releaseExpired: typeof escalationsApi.releaseExpiredClaims;
|
|
157
165
|
updatePriority: (input: {
|
|
158
166
|
ids: string[];
|
package/build/sdk/index.js
CHANGED
|
@@ -128,6 +128,7 @@ function createClient(options = {}) {
|
|
|
128
128
|
resolve: bindAuth(escalationsApi.resolveEscalation, auth),
|
|
129
129
|
resolveBySignalKey: bindAuth(escalationsApi.resolveBySignalKey, auth),
|
|
130
130
|
resolveByIds: bindAuth(escalationsApi.resolveByIds, auth),
|
|
131
|
+
resolveAllOrNone: bindAuth(escalationsApi.resolveAllOrNone, auth),
|
|
131
132
|
releaseExpired: escalationsApi.releaseExpiredClaims,
|
|
132
133
|
updatePriority: bindAuth(escalationsApi.updatePriority, auth),
|
|
133
134
|
bulkClaim: bindAuth(escalationsApi.bulkClaim, auth),
|
|
@@ -36,6 +36,42 @@ export declare function resolveEscalation(id: string, resolverPayload: Record<st
|
|
|
36
36
|
* `resolveEscalation` (which delivers the signal).
|
|
37
37
|
*/
|
|
38
38
|
export declare function resolveEscalationsByIds(ids: string[], resolverPayload: Record<string, any>, metadata?: Record<string, any>): Promise<LTEscalationRecord[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a SET of escalations atomically — every listed row resolves, each
|
|
41
|
+
* with its OWN resolverPayload, or none do. One SQL statement locks the rows
|
|
42
|
+
* in deterministic id order, applies per-row payloads, and commits each
|
|
43
|
+
* waiter's wake WITH its resolve. Unlike {@link resolveEscalationsByIds},
|
|
44
|
+
* rows backing a live `condition()` waiter are first-class here: each is
|
|
45
|
+
* woken with its own payload as the condition's return value.
|
|
46
|
+
*
|
|
47
|
+
* On failure NOTHING is written and `failed` names exactly the rows that
|
|
48
|
+
* blocked the batch (`not-found`, `already-resolved`, `already-cancelled`,
|
|
49
|
+
* `already-expired`, `assignee-mismatch`) — rows that were themselves
|
|
50
|
+
* resolvable stay pending and are not listed.
|
|
51
|
+
*
|
|
52
|
+
* `assertAssignee` (optional) additionally requires every row to be assigned
|
|
53
|
+
* to that principal, asserted inside the same guarded statement — closing the
|
|
54
|
+
* claim-race window for claim-then-resolve flows.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveEscalationsAllOrNone(items: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
resolverPayload: Record<string, any>;
|
|
59
|
+
}>, metadata?: Record<string, any>, assertAssignee?: string): Promise<{
|
|
60
|
+
ok: true;
|
|
61
|
+
escalations: LTEscalationRecord[];
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
failed: Array<{
|
|
65
|
+
id: string;
|
|
66
|
+
reason: string;
|
|
67
|
+
}>;
|
|
68
|
+
}>;
|
|
69
|
+
/**
|
|
70
|
+
* Fetch full records for a set of escalation IDs in one indexed query.
|
|
71
|
+
* Used by bulk orchestrators that need per-row context beyond the scope
|
|
72
|
+
* fields — signal routing shape, `metadata.form_schema` for redaction.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getEscalationsByIds(ids: string[]): Promise<LTEscalationRecord[]>;
|
|
39
75
|
/**
|
|
40
76
|
* Look up an efficient (atomic) escalation by its `signal_key` — the signal id
|
|
41
77
|
* passed to `conditionLT(signalId, config)` / `condition(signalId, config)`.
|
|
@@ -4,6 +4,8 @@ exports.createEscalation = createEscalation;
|
|
|
4
4
|
exports.claimEscalation = claimEscalation;
|
|
5
5
|
exports.resolveEscalation = resolveEscalation;
|
|
6
6
|
exports.resolveEscalationsByIds = resolveEscalationsByIds;
|
|
7
|
+
exports.resolveEscalationsAllOrNone = resolveEscalationsAllOrNone;
|
|
8
|
+
exports.getEscalationsByIds = getEscalationsByIds;
|
|
7
9
|
exports.getEscalationBySignalKey = getEscalationBySignalKey;
|
|
8
10
|
exports.resolveEscalationBySignalKey = resolveEscalationBySignalKey;
|
|
9
11
|
exports.cancelEscalation = cancelEscalation;
|
|
@@ -165,6 +167,57 @@ async function resolveEscalationsByIds(ids, resolverPayload, metadata) {
|
|
|
165
167
|
}
|
|
166
168
|
return records;
|
|
167
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a SET of escalations atomically — every listed row resolves, each
|
|
172
|
+
* with its OWN resolverPayload, or none do. One SQL statement locks the rows
|
|
173
|
+
* in deterministic id order, applies per-row payloads, and commits each
|
|
174
|
+
* waiter's wake WITH its resolve. Unlike {@link resolveEscalationsByIds},
|
|
175
|
+
* rows backing a live `condition()` waiter are first-class here: each is
|
|
176
|
+
* woken with its own payload as the condition's return value.
|
|
177
|
+
*
|
|
178
|
+
* On failure NOTHING is written and `failed` names exactly the rows that
|
|
179
|
+
* blocked the batch (`not-found`, `already-resolved`, `already-cancelled`,
|
|
180
|
+
* `already-expired`, `assignee-mismatch`) — rows that were themselves
|
|
181
|
+
* resolvable stay pending and are not listed.
|
|
182
|
+
*
|
|
183
|
+
* `assertAssignee` (optional) additionally requires every row to be assigned
|
|
184
|
+
* to that principal, asserted inside the same guarded statement — closing the
|
|
185
|
+
* claim-race window for claim-then-resolve flows.
|
|
186
|
+
*/
|
|
187
|
+
async function resolveEscalationsAllOrNone(items, metadata, assertAssignee) {
|
|
188
|
+
if (items.length === 0)
|
|
189
|
+
return { ok: true, escalations: [] };
|
|
190
|
+
const client = await (0, client_1.escalations)();
|
|
191
|
+
const result = await client.resolveAllOrNone({ items, metadata, assertAssignee });
|
|
192
|
+
if (!result.ok)
|
|
193
|
+
return { ok: false, failed: result.failed };
|
|
194
|
+
const records = (0, map_1.toEscalationRecords)(result.entries);
|
|
195
|
+
for (const escalation of records) {
|
|
196
|
+
publishEscalationChange({
|
|
197
|
+
type: 'escalation.resolved',
|
|
198
|
+
source: 'service',
|
|
199
|
+
workflowId: escalation.workflow_id || '',
|
|
200
|
+
workflowName: escalation.workflow_type || '',
|
|
201
|
+
taskQueue: escalation.task_queue || '',
|
|
202
|
+
escalationId: escalation.id,
|
|
203
|
+
status: 'resolved',
|
|
204
|
+
data: {},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return { ok: true, escalations: records };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Fetch full records for a set of escalation IDs in one indexed query.
|
|
211
|
+
* Used by bulk orchestrators that need per-row context beyond the scope
|
|
212
|
+
* fields — signal routing shape, `metadata.form_schema` for redaction.
|
|
213
|
+
*/
|
|
214
|
+
async function getEscalationsByIds(ids) {
|
|
215
|
+
if (ids.length === 0)
|
|
216
|
+
return [];
|
|
217
|
+
const client = await (0, client_1.escalations)();
|
|
218
|
+
const rows = await client.list({ ids, limit: LOOKUP_LIMIT });
|
|
219
|
+
return (0, map_1.toEscalationRecords)(rows);
|
|
220
|
+
}
|
|
168
221
|
/**
|
|
169
222
|
* Look up an efficient (atomic) escalation by its `signal_key` — the signal id
|
|
170
223
|
* passed to `conditionLT(signalId, config)` / `condition(signalId, config)`.
|
|
@@ -29,6 +29,19 @@ export declare function searchEscalationsFaceted(opts: {
|
|
|
29
29
|
escalations: LTEscalationRecord[];
|
|
30
30
|
total: number;
|
|
31
31
|
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Single-query escalation read for the resolve UI: the escalation row PLUS the
|
|
34
|
+
* role's escalation form, resolved to the version the escalation pinned
|
|
35
|
+
* (`metadata.schema_version`) or the role's latest when unpinned, JOINed from the
|
|
36
|
+
* roles tables and returned as `form_schema`. The form is NOT stored on the
|
|
37
|
+
* escalation — it rides in from lt_role_schemas/lt_roles on this one query, so a
|
|
38
|
+
* versioned JIT UI costs no per-row space and one SQL call to render. The version
|
|
39
|
+
* cast is guarded, so a non-numeric pin simply falls through to the latest form.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getEscalationWithFormSchema(id: string): Promise<{
|
|
42
|
+
escalation: LTEscalationRecord;
|
|
43
|
+
form_schema: Record<string, any> | null;
|
|
44
|
+
} | null>;
|
|
32
45
|
/**
|
|
33
46
|
* Distinct top-level metadata facet KEYS visible to the caller — feeds the faceted
|
|
34
47
|
* UI's key autocomplete so it only ever offers facets that ACTUALLY exist in the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.searchEscalationsFaceted = searchEscalationsFaceted;
|
|
4
|
+
exports.getEscalationWithFormSchema = getEscalationWithFormSchema;
|
|
4
5
|
exports.listFacetKeys = listFacetKeys;
|
|
5
6
|
exports.getEscalationStats = getEscalationStats;
|
|
6
7
|
exports.listDistinctTypes = listDistinctTypes;
|
|
@@ -143,6 +144,32 @@ async function searchEscalationsFaceted(opts) {
|
|
|
143
144
|
]);
|
|
144
145
|
return { escalations: (0, map_1.toEscalationRecords)(rows.rows), total: countRows.rows[0]?.total ?? 0 };
|
|
145
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Single-query escalation read for the resolve UI: the escalation row PLUS the
|
|
149
|
+
* role's escalation form, resolved to the version the escalation pinned
|
|
150
|
+
* (`metadata.schema_version`) or the role's latest when unpinned, JOINed from the
|
|
151
|
+
* roles tables and returned as `form_schema`. The form is NOT stored on the
|
|
152
|
+
* escalation — it rides in from lt_role_schemas/lt_roles on this one query, so a
|
|
153
|
+
* versioned JIT UI costs no per-row space and one SQL call to render. The version
|
|
154
|
+
* cast is guarded, so a non-numeric pin simply falls through to the latest form.
|
|
155
|
+
*/
|
|
156
|
+
async function getEscalationWithFormSchema(id) {
|
|
157
|
+
await (0, client_1.ensureEscalationCompatView)();
|
|
158
|
+
const pool = (0, db_1.getPool)();
|
|
159
|
+
const { rows } = await pool.query(`SELECT e.*, COALESCE(ps.form_schema, r.form_schema) AS resolved_form_schema
|
|
160
|
+
FROM public.lt_escalations e
|
|
161
|
+
LEFT JOIN public.lt_roles r ON r.role = e.role
|
|
162
|
+
LEFT JOIN public.lt_role_schemas ps
|
|
163
|
+
ON ps.role = e.role
|
|
164
|
+
AND ps.version = CASE WHEN e.metadata->>'schema_version' ~ '^[0-9]+$'
|
|
165
|
+
THEN (e.metadata->>'schema_version')::int END
|
|
166
|
+
WHERE e.id = $1`, [id]);
|
|
167
|
+
if (!rows[0])
|
|
168
|
+
return null;
|
|
169
|
+
const { resolved_form_schema, ...row } = rows[0];
|
|
170
|
+
const [escalation] = (0, map_1.toEscalationRecords)([row]);
|
|
171
|
+
return { escalation, form_schema: resolved_form_schema ?? null };
|
|
172
|
+
}
|
|
146
173
|
/**
|
|
147
174
|
* Distinct top-level metadata facet KEYS visible to the caller — feeds the faceted
|
|
148
175
|
* UI's key autocomplete so it only ever offers facets that ACTUALLY exist in the
|
|
@@ -38,6 +38,7 @@ const hotmesh_1 = require("@hotmeshio/hotmesh");
|
|
|
38
38
|
const db_1 = require("../../lib/db");
|
|
39
39
|
const logger_1 = require("../../lib/logger");
|
|
40
40
|
const dbaService = __importStar(require("../dba"));
|
|
41
|
+
const client_1 = require("../escalation/client");
|
|
41
42
|
const CRON_TOPIC = 'lt.maintenance.prune';
|
|
42
43
|
const CRON_ID = 'lt-maintenance-nightly';
|
|
43
44
|
/**
|
|
@@ -84,6 +85,27 @@ async function executeRule(appId, rule) {
|
|
|
84
85
|
streams: false,
|
|
85
86
|
});
|
|
86
87
|
break;
|
|
88
|
+
// Age out terminal escalation rows (resolved/cancelled/expired). The
|
|
89
|
+
// engine-owned prune deletes at most one batch per call in one atomic
|
|
90
|
+
// statement; loop until the horizon is drained. Pending rows and live
|
|
91
|
+
// condition() waiters are never touched — every engine state transition
|
|
92
|
+
// guards on status='pending', so terminal rows are inert audit records.
|
|
93
|
+
case rule.target === 'escalations' && rule.action === 'delete': {
|
|
94
|
+
const client = await (0, client_1.escalations)();
|
|
95
|
+
let deleted = 0;
|
|
96
|
+
let batch;
|
|
97
|
+
do {
|
|
98
|
+
({ deleted: batch } = await client.prune({
|
|
99
|
+
olderThan: rule.olderThan,
|
|
100
|
+
statuses: rule.statuses,
|
|
101
|
+
}));
|
|
102
|
+
deleted += batch;
|
|
103
|
+
} while (batch > 0);
|
|
104
|
+
if (deleted > 0) {
|
|
105
|
+
logger_1.loggerRegistry.info(`[lt-maintenance] escalations pruned: ${deleted} (olderThan: ${rule.olderThan})`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
87
109
|
default:
|
|
88
110
|
logger_1.loggerRegistry.warn(`[lt-maintenance] unknown rule, skipping: ${JSON.stringify(rule)}`);
|
|
89
111
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Types } from '@hotmeshio/hotmesh';
|
|
2
2
|
/**
|
|
3
3
|
* HotMesh's escalation config plus long-tail sugar. `schemaVersion` pins the
|
|
4
|
-
* role
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* role form version (lt_role_schemas) the resolve UI renders; it is a
|
|
5
|
+
* compile-time LITERAL the workflow author sets, folded into
|
|
6
|
+
* `metadata.schema_version` before the config reaches the engine, so the pin
|
|
7
|
+
* rides the row's GIN-indexed metadata like any other facet. Folding is a pure
|
|
8
|
+
* transform — no query, no activity — so a pinned `conditionLT` costs exactly
|
|
9
|
+
* what an unpinned one does. Omit it and the resolve UI renders the role's latest
|
|
10
|
+
* form when the escalation is fetched.
|
|
8
11
|
*/
|
|
9
12
|
export type ConditionEscalationConfig = Types.ConditionQueueConfig & {
|
|
10
13
|
schemaVersion?: number;
|
|
@@ -33,17 +36,20 @@ export type ConditionEscalationConfig = Types.ConditionQueueConfig & {
|
|
|
33
36
|
* });
|
|
34
37
|
* ```
|
|
35
38
|
*
|
|
36
|
-
* **Pin the role
|
|
37
|
-
* versioned (lt_role_schemas)
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* **Pin the role form version — a compile-time literal.** A role's escalation
|
|
40
|
+
* form is versioned (lt_role_schemas); its first form is version 1. Pass the
|
|
41
|
+
* version as a literal, paired with the generic `<T>` payload type, both bumped
|
|
42
|
+
* together when the form evolves. The literal folds into metadata for the same
|
|
43
|
+
* cost as an unpinned wait. Omit it and the resolve UI renders the role's latest
|
|
44
|
+
* form when the escalation is fetched.
|
|
41
45
|
*
|
|
42
46
|
* ```typescript
|
|
43
|
-
*
|
|
44
|
-
*
|
|
47
|
+
* import { INTAKE_SCHEMA_VERSION, type IntakeResolverV1 } from './forms';
|
|
48
|
+
*
|
|
49
|
+
* const decision = await conditionLT<IntakeResolverV1>(signalId, {
|
|
50
|
+
* role: INTAKE_ROLE,
|
|
45
51
|
* description: instructions,
|
|
46
|
-
* schemaVersion:
|
|
52
|
+
* schemaVersion: INTAKE_SCHEMA_VERSION, // the form version this code is written for
|
|
47
53
|
* });
|
|
48
54
|
* ```
|
|
49
55
|
*
|
|
@@ -75,17 +75,20 @@ function toEngineConfig(escalation) {
|
|
|
75
75
|
* });
|
|
76
76
|
* ```
|
|
77
77
|
*
|
|
78
|
-
* **Pin the role
|
|
79
|
-
* versioned (lt_role_schemas)
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
78
|
+
* **Pin the role form version — a compile-time literal.** A role's escalation
|
|
79
|
+
* form is versioned (lt_role_schemas); its first form is version 1. Pass the
|
|
80
|
+
* version as a literal, paired with the generic `<T>` payload type, both bumped
|
|
81
|
+
* together when the form evolves. The literal folds into metadata for the same
|
|
82
|
+
* cost as an unpinned wait. Omit it and the resolve UI renders the role's latest
|
|
83
|
+
* form when the escalation is fetched.
|
|
83
84
|
*
|
|
84
85
|
* ```typescript
|
|
85
|
-
*
|
|
86
|
-
*
|
|
86
|
+
* import { INTAKE_SCHEMA_VERSION, type IntakeResolverV1 } from './forms';
|
|
87
|
+
*
|
|
88
|
+
* const decision = await conditionLT<IntakeResolverV1>(signalId, {
|
|
89
|
+
* role: INTAKE_ROLE,
|
|
87
90
|
* description: instructions,
|
|
88
|
-
* schemaVersion:
|
|
91
|
+
* schemaVersion: INTAKE_SCHEMA_VERSION, // the form version this code is written for
|
|
89
92
|
* });
|
|
90
93
|
* ```
|
|
91
94
|
*
|
|
@@ -61,8 +61,7 @@ export declare function listRoleSchemaVersions(role: string): Promise<RoleSchema
|
|
|
61
61
|
*/
|
|
62
62
|
export declare function getRoleSchema(role: string, version?: number): Promise<RoleSchemaVersion | null>;
|
|
63
63
|
/**
|
|
64
|
-
* Fetch
|
|
65
|
-
* config does not specify a resolver_schema).
|
|
64
|
+
* Fetch a role's live form_schema — the escalation form the dashboard renders.
|
|
66
65
|
*/
|
|
67
66
|
export declare function getRoleFormSchema(role: string): Promise<Record<string, any> | null>;
|
|
68
67
|
/**
|
|
@@ -185,8 +185,7 @@ async function getRoleSchema(role, version) {
|
|
|
185
185
|
return { ...rows[0], role };
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
|
-
* Fetch
|
|
189
|
-
* config does not specify a resolver_schema).
|
|
188
|
+
* Fetch a role's live form_schema — the escalation form the dashboard renders.
|
|
190
189
|
*/
|
|
191
190
|
async function getRoleFormSchema(role) {
|
|
192
191
|
const pool = (0, db_1.getPool)();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ValidateFunction } from 'ajv';
|
|
2
|
+
/**
|
|
3
|
+
* Compile (and cache) a validator for a role schema. Throws if the stored schema
|
|
4
|
+
* is not a valid JSON Schema — callers surface that as a 422 (the role admin's
|
|
5
|
+
* bug), distinct from a 400 (the submitter's payload does not match).
|
|
6
|
+
*/
|
|
7
|
+
export declare function compileSchemaValidator(cacheId: string, schema: Record<string, any>): ValidateFunction;
|
|
8
|
+
/** Join a validator's errors into one human-readable line (instancePath + message). */
|
|
9
|
+
export declare function formatValidationErrors(validate: ValidateFunction): string;
|