@hotmeshio/long-tail 0.10.6 → 0.10.7
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/bulk.d.ts +16 -4
- package/build/api/escalations/bulk.js +42 -7
- package/build/api/escalations/resolve.js +57 -15
- package/build/routes/escalations/bulk.js +1 -0
- package/build/sdk/index.d.ts +5 -1
- package/build/services/escalation/bulk.d.ts +14 -0
- package/build/services/escalation/bulk.js +17 -0
- package/build/services/escalation/crud.d.ts +13 -2
- package/build/services/escalation/crud.js +9 -4
- package/build/services/orchestrator/condition.d.ts +20 -0
- package/build/services/orchestrator/condition.js +30 -6
- package/build/types/escalation.d.ts +8 -0
- package/dashboard/dist/assets/{AdminDashboard-B5B8cf1A.js → AdminDashboard-CYlm0_J0.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-B5B8cf1A.js.map → AdminDashboard-CYlm0_J0.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-BxuHbkDF.js → AgentConfigPage-BttOMcj_.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-BxuHbkDF.js.map → AgentConfigPage-BttOMcj_.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-Bv89ria_.js → AgentDetailPage-D9JdkWV8.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-Bv89ria_.js.map → AgentDetailPage-D9JdkWV8.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-BPgki44D.js → AgentsPage-AqGtEOL1.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-BPgki44D.js.map → AgentsPage-AqGtEOL1.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-Dq_FyUcB.js → AvailableEscalationsPage-rKbrcY8Y.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-Dq_FyUcB.js.map → AvailableEscalationsPage-rKbrcY8Y.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-D-WegFKH.js → BotPicker-Bj4MB_so.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-D-WegFKH.js.map → BotPicker-Bj4MB_so.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-C8Zxzh1x.js → CapabilitiesPage-DzLP5-ad.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-C8Zxzh1x.js.map → CapabilitiesPage-DzLP5-ad.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-Ca2iifr4.js → CollapsibleSection-CReMhtzK.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-Ca2iifr4.js.map → CollapsibleSection-CReMhtzK.js.map} +1 -1
- package/dashboard/dist/assets/{CountdownTimer-CQSukTT4.js → CountdownTimer-Z8N8ccJ1.js} +2 -2
- package/dashboard/dist/assets/{CountdownTimer-CQSukTT4.js.map → CountdownTimer-Z8N8ccJ1.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-BFf0p9CE.js → CredentialsPage-D_2T6vSb.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-BFf0p9CE.js.map → CredentialsPage-D_2T6vSb.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-CvjUA6jF.js → CronLabel-DBmKrLvL.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-CvjUA6jF.js.map → CronLabel-DBmKrLvL.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-znQCEiPe.js → CustomDurationPicker-BTmpAQOL.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-znQCEiPe.js.map → CustomDurationPicker-BTmpAQOL.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-C0pd3pO8.js → ElapsedCell-C0LbM8o2.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-C0pd3pO8.js.map → ElapsedCell-C0LbM8o2.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-BRrrftTq.js → EscalationListSchemaPage-CkEJnFKd.js} +2 -2
- package/dashboard/dist/assets/{EscalationListSchemaPage-BRrrftTq.js.map → EscalationListSchemaPage-CkEJnFKd.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-ChawQh5H.js → EscalationSchemaPage-CnlwGAIE.js} +2 -2
- package/dashboard/dist/assets/{EscalationSchemaPage-ChawQh5H.js.map → EscalationSchemaPage-CnlwGAIE.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-D3dFDffZ.js → EscalationsOverview-DFM-f7do.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-D3dFDffZ.js.map → EscalationsOverview-DFM-f7do.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-C6vkJJcQ.js → EventTable-Q3iICRf_.js} +2 -2
- package/dashboard/dist/assets/{EventTable-C6vkJJcQ.js.map → EventTable-Q3iICRf_.js.map} +1 -1
- package/dashboard/dist/assets/{FilterBar-OqpCEYW2.js → FilterBar-B0taDH7I.js} +2 -2
- package/dashboard/dist/assets/{FilterBar-OqpCEYW2.js.map → FilterBar-B0taDH7I.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-CJOyaU4g.js → GraphInvokePage-B_P3pJeA.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-CJOyaU4g.js.map → GraphInvokePage-B_P3pJeA.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-DxJz7AQ7.js → HomePage-lu88PzQH.js} +2 -2
- package/dashboard/dist/assets/{HomePage-DxJz7AQ7.js.map → HomePage-lu88PzQH.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-B6ZoJ4Rd.js → ListToolbar-ueCQtu5C.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-B6ZoJ4Rd.js.map → ListToolbar-ueCQtu5C.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-YPY-lX3M.js → McpOverview-DMaRUXnA.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-YPY-lX3M.js.map → McpOverview-DMaRUXnA.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-Ct3Mb1R2.js → McpQueryDetailPage-D6YSD94n.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-Ct3Mb1R2.js.map → McpQueryDetailPage-D6YSD94n.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-C5-AQvnp.js → McpQueryPage-CNGNB59o.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-C5-AQvnp.js.map → McpQueryPage-CNGNB59o.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-Bl12usuN.js → McpRunDetailPage-CJ9hDTNs.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-Bl12usuN.js.map → McpRunDetailPage-CJ9hDTNs.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-PRAQPR-H.js → McpRunsPage-_UaoIwzX.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-PRAQPR-H.js.map → McpRunsPage-_UaoIwzX.js.map} +1 -1
- package/dashboard/dist/assets/{OperationsPage-DLGNcE-b.js → OperationsPage-CTfwuWXR.js} +2 -2
- package/dashboard/dist/assets/{OperationsPage-DLGNcE-b.js.map → OperationsPage-CTfwuWXR.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-353PWYOD.js → OperatorDashboard-DMPJmgP_.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-353PWYOD.js.map → OperatorDashboard-DMPJmgP_.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-DyB979ge.js → ProcessDetailPage-CxJ5BSDk.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-DyB979ge.js.map → ProcessDetailPage-CxJ5BSDk.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-BPi9CNxQ.js → ProcessesListPage-GQMOo5Wo.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-BPi9CNxQ.js.map → ProcessesListPage-GQMOo5Wo.js.map} +1 -1
- package/dashboard/dist/assets/{RoleDetailPage-D6K3tdNO.js → RoleDetailPage-Bh5qdI0w.js} +2 -2
- package/dashboard/dist/assets/{RoleDetailPage-D6K3tdNO.js.map → RoleDetailPage-Bh5qdI0w.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-C_i2RLwv.js → RolesPage-BIg3_Em6.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-C_i2RLwv.js.map → RolesPage-BIg3_Em6.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-CWBQWKM0.js → RunAsSelector-DQp_keSD.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-CWBQWKM0.js.map → RunAsSelector-DQp_keSD.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-4cY0OXWj.js → StickyPagination-qLaLa33X.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-4cY0OXWj.js.map → StickyPagination-qLaLa33X.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-DxA7AzOR.js → StreamMessageDetail-Bi37x95H.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-DxA7AzOR.js.map → StreamMessageDetail-Bi37x95H.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-BlinxMjc.js → SwimlaneTimeline-BdNhoiW8.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-BlinxMjc.js.map → SwimlaneTimeline-BdNhoiW8.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-Cbr6Bzx3.js → TaskDetailPage-ngdXRgQp.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-Cbr6Bzx3.js.map → TaskDetailPage-ngdXRgQp.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-DNjZbo42.js → TasksListPage-BIE7nrdE.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-DNjZbo42.js.map → TasksListPage-BIE7nrdE.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-BEOuisPG.js → TimeAgo-C6FZSNZp.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-BEOuisPG.js.map → TimeAgo-C6FZSNZp.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell--CLqQOJa.js → TimestampCell-DI5zPUgV.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell--CLqQOJa.js.map → TimestampCell-DI5zPUgV.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-x7po38Fn.js → ToolTestPanel-Dli94RPI.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-x7po38Fn.js.map → ToolTestPanel-Dli94RPI.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-Bay2lUOW.js → TopicDetailPage-vPtXb7gD.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-Bay2lUOW.js.map → TopicDetailPage-vPtXb7gD.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-Ckh6ln0h.js → TopicsPage-BwiEX0pU.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-Ckh6ln0h.js.map → TopicsPage-BwiEX0pU.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-Doe8DDXc.js → UserName-YozoXJA1.js} +2 -2
- package/dashboard/dist/assets/{UserName-Doe8DDXc.js.map → UserName-YozoXJA1.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-DPymyMAM.js → WorkflowExecutionPage-Bce8g08l.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-DPymyMAM.js.map → WorkflowExecutionPage-Bce8g08l.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-CrwdULL7.js → WorkflowsDashboard-DpVAH35I.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-CrwdULL7.js.map → WorkflowsDashboard-DpVAH35I.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-YoPFHjOb.js → WorkflowsOverview-Bp2pxXJO.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-YoPFHjOb.js.map → WorkflowsOverview-Bp2pxXJO.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-B4H8WvSt.js → YamlWorkflowDetailPage-CL_kD0mX.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-B4H8WvSt.js.map → YamlWorkflowDetailPage-CL_kD0mX.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-gburzX11.js → YamlWorkflowsPage-Dtt3x3_T.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-gburzX11.js.map → YamlWorkflowsPage-Dtt3x3_T.js.map} +1 -1
- package/dashboard/dist/assets/{agents-BwrYw_S8.js → agents-xhtLVBwq.js} +2 -2
- package/dashboard/dist/assets/{agents-BwrYw_S8.js.map → agents-xhtLVBwq.js.map} +1 -1
- package/dashboard/dist/assets/{bots-C0vU7IG9.js → bots-DZ1MQUms.js} +2 -2
- package/dashboard/dist/assets/{bots-C0vU7IG9.js.map → bots-DZ1MQUms.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-DOE8jqWc.js → capabilities-B8j2h7Jn.js} +2 -2
- package/dashboard/dist/assets/{capabilities-DOE8jqWc.js.map → capabilities-B8j2h7Jn.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-TtPp8V9E.js → controlplane-DKijNA6h.js} +2 -2
- package/dashboard/dist/assets/{controlplane-TtPp8V9E.js.map → controlplane-DKijNA6h.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-Cyp-K89u.js → escalation-columns-DjoZ4dDb.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-Cyp-K89u.js.map → escalation-columns-DjoZ4dDb.js.map} +1 -1
- package/dashboard/dist/assets/{index-zuK6pSyZ.js → index-0SzWFShg.js} +2 -2
- package/dashboard/dist/assets/{index-zuK6pSyZ.js.map → index-0SzWFShg.js.map} +1 -1
- package/dashboard/dist/assets/{index-j-X71wT4.js → index-B0ktIiOY.js} +2 -2
- package/dashboard/dist/assets/{index-j-X71wT4.js.map → index-B0ktIiOY.js.map} +1 -1
- package/dashboard/dist/assets/{index-CkwNuQzx.js → index-BCIJE--_.js} +2 -2
- package/dashboard/dist/assets/{index-CkwNuQzx.js.map → index-BCIJE--_.js.map} +1 -1
- package/dashboard/dist/assets/{index-DFJm742Y.js → index-BqeQGQ4K.js} +2 -2
- package/dashboard/dist/assets/{index-DFJm742Y.js.map → index-BqeQGQ4K.js.map} +1 -1
- package/dashboard/dist/assets/{index-BmsZnbW1.js → index-C6UuVkz7.js} +3 -3
- package/dashboard/dist/assets/{index-BmsZnbW1.js.map → index-C6UuVkz7.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dh6F3Wbx.js → index-CMv6YMJY.js} +2 -2
- package/dashboard/dist/assets/{index-Dh6F3Wbx.js.map → index-CMv6YMJY.js.map} +1 -1
- package/dashboard/dist/assets/{index-CVZOAQVo.js → index-ChHD9p_I.js} +2 -2
- package/dashboard/dist/assets/{index-CVZOAQVo.js.map → index-ChHD9p_I.js.map} +1 -1
- package/dashboard/dist/assets/{index-CR1_qhQf.js → index-Cp1jJHm1.js} +2 -2
- package/dashboard/dist/assets/{index-CR1_qhQf.js.map → index-Cp1jJHm1.js.map} +1 -1
- package/dashboard/dist/assets/{index-Bl-09ECD.js → index-DWDEa8jS.js} +2 -2
- package/dashboard/dist/assets/{index-Bl-09ECD.js.map → index-DWDEa8jS.js.map} +1 -1
- package/dashboard/dist/assets/{index-stu9QXmW.js → index-DYCCUmSM.js} +2 -2
- package/dashboard/dist/assets/{index-stu9QXmW.js.map → index-DYCCUmSM.js.map} +1 -1
- package/dashboard/dist/assets/{index-b7P6Hjd3.js → index-DcDEJ6b_.js} +2 -2
- package/dashboard/dist/assets/{index-b7P6Hjd3.js.map → index-DcDEJ6b_.js.map} +1 -1
- package/dashboard/dist/assets/index-Dh5Y3hq7.js +5 -0
- package/dashboard/dist/assets/index-Dh5Y3hq7.js.map +1 -0
- package/dashboard/dist/assets/{index-BTsTdaRt.js → index-DrPWIFSv.js} +2 -2
- package/dashboard/dist/assets/{index-BTsTdaRt.js.map → index-DrPWIFSv.js.map} +1 -1
- package/dashboard/dist/assets/index-WSGppNeK.css +1 -0
- package/dashboard/dist/assets/{knowledge-DD9NIG9d.js → knowledge-Dl3cOfFR.js} +2 -2
- package/dashboard/dist/assets/{knowledge-DD9NIG9d.js.map → knowledge-Dl3cOfFR.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-C4upVuuP.js → mcp-BwjeqkHn.js} +2 -2
- package/dashboard/dist/assets/{mcp-C4upVuuP.js.map → mcp-BwjeqkHn.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-JEk3cZyH.js → mcp-query-C1Rlt4az.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-JEk3cZyH.js.map → mcp-query-C1Rlt4az.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-BkBpa2Y7.js → pipelines-BGIUgIH_.js} +2 -2
- package/dashboard/dist/assets/{pipelines-BkBpa2Y7.js.map → pipelines-BGIUgIH_.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-B7QVagGw.js → tasks-CnC1xEaD.js} +2 -2
- package/dashboard/dist/assets/{tasks-B7QVagGw.js.map → tasks-CnC1xEaD.js.map} +1 -1
- package/dashboard/dist/assets/{topics-C55xydgR.js → topics-C6EcHi07.js} +2 -2
- package/dashboard/dist/assets/{topics-C55xydgR.js.map → topics-C6EcHi07.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-CKpFrpjc.js → useEventHooks-Cyq4DiQA.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-CKpFrpjc.js.map → useEventHooks-Cyq4DiQA.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BJbjZubz.js → useNamespace-Ci_oA5Tq.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BJbjZubz.js.map → useNamespace-Ci_oA5Tq.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-C80oooy6.js → useYamlActivityEvents-u8RUiMma.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-C80oooy6.js.map → useYamlActivityEvents-u8RUiMma.js.map} +1 -1
- package/dashboard/dist/assets/{users-BdvakxB1.js → users-DtiYvg8z.js} +2 -2
- package/dashboard/dist/assets/{users-BdvakxB1.js.map → users-DtiYvg8z.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-DYTqfgom.js → workflows-BysPDsh9.js} +2 -2
- package/dashboard/dist/assets/{workflows-DYTqfgom.js.map → workflows-BysPDsh9.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-D3o7rHzN.js → yaml-workflows-31qtxg5W.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-D3o7rHzN.js.map → yaml-workflows-31qtxg5W.js.map} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/docs/api/http/escalations.md +12 -3
- package/docs/hitl/escalation.md +21 -0
- package/docs/hitl/x-lt-embed.md +60 -0
- package/package.json +2 -2
- package/dashboard/dist/assets/index-Cm42dgjx.css +0 -1
- package/dashboard/dist/assets/index-Wu6GCvUq.js +0 -5
- package/dashboard/dist/assets/index-Wu6GCvUq.js.map +0 -1
|
@@ -33,19 +33,31 @@ export declare function bulkClaim(input: {
|
|
|
33
33
|
durationMinutes?: number;
|
|
34
34
|
}, auth: LTApiAuth): Promise<LTApiResult>;
|
|
35
35
|
/**
|
|
36
|
-
* Assign multiple escalations to a specific user
|
|
36
|
+
* Assign multiple escalations to a specific user — by id-set OR by query
|
|
37
|
+
* (exactly one).
|
|
38
|
+
*
|
|
39
|
+
* The query form (`{ role, facets? }`) is one atomic statement: selection and
|
|
40
|
+
* claim happen in the same UPDATE, so a row that re-parks between a search and
|
|
41
|
+
* an ids-assign is still captured. Use it whenever the population is
|
|
42
|
+
* describable by filter (e.g. a walk's plates by `walkId` facet).
|
|
37
43
|
*
|
|
38
44
|
* Non-superadmin callers must verify the target user holds each
|
|
39
|
-
* escalation's role
|
|
45
|
+
* escalation's role (the query form requires exactly the declared role).
|
|
46
|
+
* Publishes claim events for assigned items.
|
|
40
47
|
*
|
|
41
|
-
* @param input.ids — array of escalation UUIDs
|
|
48
|
+
* @param input.ids — array of escalation UUIDs (ids form)
|
|
49
|
+
* @param input.query — `{ role, facets? }` selector (query form)
|
|
42
50
|
* @param input.targetUserId — user to assign to
|
|
43
51
|
* @param input.durationMinutes — assignment duration (default: 30)
|
|
44
52
|
* @param auth — authenticated user context
|
|
45
53
|
* @returns `{ status: 200, data: { assigned, skipped } }`
|
|
46
54
|
*/
|
|
47
55
|
export declare function bulkAssign(input: {
|
|
48
|
-
ids
|
|
56
|
+
ids?: string[];
|
|
57
|
+
query?: {
|
|
58
|
+
role: string;
|
|
59
|
+
facets?: Record<string, unknown>;
|
|
60
|
+
};
|
|
49
61
|
targetUserId: string;
|
|
50
62
|
durationMinutes?: number;
|
|
51
63
|
}, auth: LTApiAuth): Promise<LTApiResult>;
|
|
@@ -117,12 +117,20 @@ async function bulkClaim(input, auth) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
* Assign multiple escalations to a specific user
|
|
120
|
+
* Assign multiple escalations to a specific user — by id-set OR by query
|
|
121
|
+
* (exactly one).
|
|
122
|
+
*
|
|
123
|
+
* The query form (`{ role, facets? }`) is one atomic statement: selection and
|
|
124
|
+
* claim happen in the same UPDATE, so a row that re-parks between a search and
|
|
125
|
+
* an ids-assign is still captured. Use it whenever the population is
|
|
126
|
+
* describable by filter (e.g. a walk's plates by `walkId` facet).
|
|
121
127
|
*
|
|
122
128
|
* Non-superadmin callers must verify the target user holds each
|
|
123
|
-
* escalation's role
|
|
129
|
+
* escalation's role (the query form requires exactly the declared role).
|
|
130
|
+
* Publishes claim events for assigned items.
|
|
124
131
|
*
|
|
125
|
-
* @param input.ids — array of escalation UUIDs
|
|
132
|
+
* @param input.ids — array of escalation UUIDs (ids form)
|
|
133
|
+
* @param input.query — `{ role, facets? }` selector (query form)
|
|
126
134
|
* @param input.targetUserId — user to assign to
|
|
127
135
|
* @param input.durationMinutes — assignment duration (default: 30)
|
|
128
136
|
* @param auth — authenticated user context
|
|
@@ -130,13 +138,40 @@ async function bulkClaim(input, auth) {
|
|
|
130
138
|
*/
|
|
131
139
|
async function bulkAssign(input, auth) {
|
|
132
140
|
try {
|
|
133
|
-
const { ids, targetUserId, durationMinutes } = input;
|
|
134
|
-
if (!(0, helpers_1.validateIds)(ids)) {
|
|
135
|
-
return { status: 400, error: 'ids must be a non-empty array' };
|
|
136
|
-
}
|
|
141
|
+
const { ids, query, targetUserId, durationMinutes } = input;
|
|
137
142
|
if (!targetUserId || typeof targetUserId !== 'string') {
|
|
138
143
|
return { status: 400, error: 'targetUserId is required' };
|
|
139
144
|
}
|
|
145
|
+
const hasIds = Array.isArray(ids) && ids.length > 0;
|
|
146
|
+
const hasQuery = !!query && typeof query === 'object';
|
|
147
|
+
if (hasIds === hasQuery) {
|
|
148
|
+
return { status: 400, error: 'provide exactly one of ids or query' };
|
|
149
|
+
}
|
|
150
|
+
if (hasQuery) {
|
|
151
|
+
if (!query.role || typeof query.role !== 'string') {
|
|
152
|
+
return { status: 400, error: 'query.role is required' };
|
|
153
|
+
}
|
|
154
|
+
// RBAC, query form: the caller must hold the queried role (or global),
|
|
155
|
+
// and the target user must hold it — one role check instead of per-id.
|
|
156
|
+
const hasGlobal = await (0, helpers_1.hasGlobalEscalationAccess)(auth.userId);
|
|
157
|
+
if (!hasGlobal) {
|
|
158
|
+
const callerHasRole = await userService.hasRole(auth.userId, query.role);
|
|
159
|
+
if (!callerHasRole) {
|
|
160
|
+
return { status: 404, error: 'One or more escalations not found' };
|
|
161
|
+
}
|
|
162
|
+
const targetHasRole = await userService.hasRole(targetUserId, query.role);
|
|
163
|
+
if (!targetHasRole) {
|
|
164
|
+
return { status: 400, error: `Target user does not hold the "${query.role}" role` };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const result = await escalationService.bulkAssignEscalationsByQuery({ role: query.role, facets: query.facets }, targetUserId, durationMinutes ?? 30);
|
|
168
|
+
if (result.assigned > 0)
|
|
169
|
+
(0, helpers_1.publishBulkClaimEvents)(result.ids, targetUserId);
|
|
170
|
+
return { status: 200, data: { assigned: result.assigned, skipped: 0 } };
|
|
171
|
+
}
|
|
172
|
+
if (!(0, helpers_1.validateIds)(ids)) {
|
|
173
|
+
return { status: 400, error: 'ids must be a non-empty array' };
|
|
174
|
+
}
|
|
140
175
|
const perm = await (0, helpers_1.checkBulkPermission)(auth.userId, ids);
|
|
141
176
|
if (!perm.allowed)
|
|
142
177
|
return perm;
|
|
@@ -39,6 +39,7 @@ exports.resolveByIds = resolveByIds;
|
|
|
39
39
|
exports.resolveAllOrNone = resolveAllOrNone;
|
|
40
40
|
exports.validationFailure = validationFailure;
|
|
41
41
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
42
|
+
const userService = __importStar(require("../../services/user"));
|
|
42
43
|
const taskService = __importStar(require("../../services/task"));
|
|
43
44
|
const escalation_strategy_1 = require("../../services/escalation-strategy");
|
|
44
45
|
const ephemeral_1 = require("../../services/iam/ephemeral");
|
|
@@ -103,7 +104,14 @@ async function resolveEscalation(input, auth) {
|
|
|
103
104
|
// The resolver payload is stored exactly as submitted — its shape is the
|
|
104
105
|
// workflow's contract, formed by the caller (the React app maps the form to
|
|
105
106
|
// the payload via x-lt-bind before submitting). No server-side transform.
|
|
106
|
-
//
|
|
107
|
+
// Resolution provenance, both surfaces at once: `resolvedBy` rides the
|
|
108
|
+
// signal to the waiting workflow ($resolution — see conditionLT), and
|
|
109
|
+
// `resolved_by` merges into the row's GIN-indexed metadata inside the same
|
|
110
|
+
// atomic resolve, making "who resolved it" `@>`-queryable without a
|
|
111
|
+
// follow-up read.
|
|
112
|
+
const resolvedBy = await resolverIdentity(auth);
|
|
113
|
+
const outcome = { ...metadata, resolved_by: auth.userId };
|
|
114
|
+
// `outcome` (the outcome patch) is never written separately — it rides WITH the
|
|
107
115
|
// resolverPayload to whichever path performs the resolve, so it merges inside the
|
|
108
116
|
// single status='pending'-guarded UPDATE (HotMesh `resolve(metadata)`). Paths that
|
|
109
117
|
// resolve here pass it as the 3rd arg; paths that resume a workflow carry it on the
|
|
@@ -111,19 +119,19 @@ async function resolveEscalation(input, auth) {
|
|
|
111
119
|
// Path A: conditionLT signal
|
|
112
120
|
const metadataSignalId = escalation.metadata?.signal_id;
|
|
113
121
|
if (metadataSignalId && escalation.workflow_id && escalation.task_queue && escalation.workflow_type) {
|
|
114
|
-
return resolveViaConditionSignal(escalation, resolverPayload,
|
|
122
|
+
return resolveViaConditionSignal(escalation, resolverPayload, outcome, resolvedBy);
|
|
115
123
|
}
|
|
116
124
|
// Path B: waitFor signal routing
|
|
117
125
|
const signalRouting = escalation.metadata?.signal_routing;
|
|
118
126
|
if (signalRouting?.signalId) {
|
|
119
|
-
return resolveViaSignalRouting(escalation, resolverPayload,
|
|
127
|
+
return resolveViaSignalRouting(escalation, resolverPayload, outcome, resolvedBy);
|
|
120
128
|
}
|
|
121
129
|
// Path 0: efficient (atomic) escalation — signal_key resumes in place.
|
|
122
130
|
// The row was written inside the workflow's Leg1 checkpoint via
|
|
123
131
|
// `condition(signalId, config)`. The SDK's resolve marks it resolved AND
|
|
124
132
|
// delivers the signal to `signal_key`, resuming THIS job — no re-run.
|
|
125
133
|
if (escalation.signal_key) {
|
|
126
|
-
return resolveViaSignalKey(escalation, resolverPayload,
|
|
134
|
+
return resolveViaSignalKey(escalation, resolverPayload, outcome, auth.userId, resolvedBy);
|
|
127
135
|
}
|
|
128
136
|
// Path C: escalation strategy may redirect to triage
|
|
129
137
|
const envelope = await reconstructEnvelope(escalation);
|
|
@@ -131,19 +139,19 @@ async function resolveEscalation(input, auth) {
|
|
|
131
139
|
if (strategy) {
|
|
132
140
|
const directive = await strategy.onResolution({ escalation, resolverPayload, envelope });
|
|
133
141
|
if (directive.action === 'triage') {
|
|
134
|
-
return resolveViaTriage(escalation, resolverPayload, directive.triageEnvelope,
|
|
142
|
+
return resolveViaTriage(escalation, resolverPayload, directive.triageEnvelope, outcome);
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
// Path D: notification-only — no workflow to restart. One atomic resolve.
|
|
138
146
|
if (!escalation.workflow_type || !escalation.task_queue) {
|
|
139
|
-
const resolved = await escalationService.resolveEscalation(escalation.id, resolverPayload,
|
|
147
|
+
const resolved = await escalationService.resolveEscalation(escalation.id, resolverPayload, outcome, auth.userId);
|
|
140
148
|
if (!resolved) {
|
|
141
149
|
return { status: 409, error: 'Escalation not available for resolution' };
|
|
142
150
|
}
|
|
143
151
|
return { status: 200, data: { acknowledged: true, escalationId: escalation.id } };
|
|
144
152
|
}
|
|
145
153
|
// Path E: standard re-run
|
|
146
|
-
return resolveViaRerun(escalation, envelope, resolverPayload,
|
|
154
|
+
return resolveViaRerun(escalation, envelope, resolverPayload, outcome);
|
|
147
155
|
}
|
|
148
156
|
catch (err) {
|
|
149
157
|
return { status: 500, error: err.message };
|
|
@@ -178,7 +186,11 @@ async function resolveBySignalKey(input, auth) {
|
|
|
178
186
|
return validationFailure(violation);
|
|
179
187
|
// The payload is delivered as the signal the parked workflow's condition()
|
|
180
188
|
// receives — stored exactly as submitted (the caller formed its shape).
|
|
181
|
-
|
|
189
|
+
// Same provenance contract as the interactive path: identity on the signal,
|
|
190
|
+
// resolved_by in the row metadata.
|
|
191
|
+
const resolvedBy = await resolverIdentity(auth);
|
|
192
|
+
const outcome = { ...metadata, resolved_by: auth.userId };
|
|
193
|
+
return resolveViaSignalKey(escalation, resolverPayload, outcome, undefined, resolvedBy);
|
|
182
194
|
}
|
|
183
195
|
catch (err) {
|
|
184
196
|
return { status: 500, error: err.message };
|
|
@@ -243,7 +255,7 @@ async function resolveByIds(input, auth) {
|
|
|
243
255
|
return bulkValidationFailure(failed);
|
|
244
256
|
}
|
|
245
257
|
}
|
|
246
|
-
const resolved = await escalationService.resolveEscalationsByIds(ids, resolverPayload, metadata);
|
|
258
|
+
const resolved = await escalationService.resolveEscalationsByIds(ids, resolverPayload, { ...metadata, resolved_by: auth.userId });
|
|
247
259
|
return { status: 200, data: { resolved: resolved.length, escalationIds: resolved.map((e) => e.id) } };
|
|
248
260
|
}
|
|
249
261
|
catch (err) {
|
|
@@ -347,7 +359,7 @@ async function resolveAllOrNone(input, auth) {
|
|
|
347
359
|
id: item.id,
|
|
348
360
|
resolverPayload: await redactPasswords(item.resolverPayload, rowById.get(item.id)?.metadata?.form_schema),
|
|
349
361
|
})));
|
|
350
|
-
const result = await escalationService.resolveEscalationsAllOrNone(serviceItems, metadata, requireClaimed ? auth.userId : undefined);
|
|
362
|
+
const result = await escalationService.resolveEscalationsAllOrNone(serviceItems, { ...metadata, resolved_by: auth.userId }, requireClaimed ? auth.userId : undefined, await resolverIdentity(auth));
|
|
351
363
|
if (!result.ok) {
|
|
352
364
|
const error = 'One or more escalations blocked the batch; nothing was resolved';
|
|
353
365
|
return {
|
|
@@ -367,17 +379,20 @@ async function resolveAllOrNone(input, auth) {
|
|
|
367
379
|
}
|
|
368
380
|
// ── Resolution paths ─────────────────────────────────────────────────────
|
|
369
381
|
/** Path A: lightweight conditionLT signal — inject $escalation_id and signal the running workflow. */
|
|
370
|
-
async function resolveViaConditionSignal(escalation, resolverPayload, metadata) {
|
|
382
|
+
async function resolveViaConditionSignal(escalation, resolverPayload, metadata, resolvedBy) {
|
|
371
383
|
const signalId = escalation.metadata.signal_id;
|
|
372
384
|
const client = (0, workers_1.createClient)();
|
|
373
385
|
const handle = await client.workflow.getHandle(escalation.task_queue, escalation.workflow_type, escalation.workflow_id);
|
|
374
386
|
// The row is resolved downstream by the workflow's `conditionLT` → `ltResolveEscalation`.
|
|
375
387
|
// Carry the outcome patch on the signal ($escalation_metadata, symmetric to $escalation_id)
|
|
376
388
|
// so it merges inside that single atomic resolve — never a separate write here.
|
|
389
|
+
// $resolution (provenance) is surfaced to the workflow by conditionLT and stripped
|
|
390
|
+
// from the stored resolver_payload, same as the other $-control keys.
|
|
377
391
|
await handle.signal(signalId, {
|
|
378
392
|
...resolverPayload,
|
|
379
393
|
$escalation_id: escalation.id,
|
|
380
394
|
...(metadata ? { $escalation_metadata: metadata } : {}),
|
|
395
|
+
...(resolvedBy ? { $resolution: resolutionOf(escalation.id, resolvedBy) } : {}),
|
|
381
396
|
});
|
|
382
397
|
// Event published by service layer (services/escalation/crud.ts)
|
|
383
398
|
return signaledResult(escalation, escalation.workflow_id);
|
|
@@ -388,13 +403,14 @@ async function resolveViaConditionSignal(escalation, resolverPayload, metadata)
|
|
|
388
403
|
* transaction, so the original job resumes in place (no re-run, no separate
|
|
389
404
|
* resolve activity). Password fields are redacted before they enter the signal.
|
|
390
405
|
*/
|
|
391
|
-
async function resolveViaSignalKey(escalation, resolverPayload, metadata, assertClaim) {
|
|
406
|
+
async function resolveViaSignalKey(escalation, resolverPayload, metadata, assertClaim, resolvedBy) {
|
|
392
407
|
const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
|
|
393
408
|
// One atomic call: status→resolved, signal delivered, and the outcome patch merged
|
|
394
409
|
// into the GIN-indexed metadata — all inside the single WHERE-guarded UPDATE.
|
|
395
410
|
// `assertClaim` (interactive path only) makes the claim-liveness gate part of
|
|
396
411
|
// that same UPDATE; the webhook path (resolveBySignalKey) stays claim-agnostic.
|
|
397
|
-
|
|
412
|
+
// `resolvedBy` rides the SDK signal as $resolution — never the stored payload.
|
|
413
|
+
const resolved = await escalationService.resolveEscalation(escalation.id, signalPayload, metadata, assertClaim, resolvedBy);
|
|
398
414
|
if (!resolved) {
|
|
399
415
|
return { status: 409, error: 'Escalation not available for resolution' };
|
|
400
416
|
}
|
|
@@ -402,7 +418,7 @@ async function resolveViaSignalKey(escalation, resolverPayload, metadata, assert
|
|
|
402
418
|
return signaledResult(escalation, escalation.workflow_id || '');
|
|
403
419
|
}
|
|
404
420
|
/** Path B: waitFor signal escalation — signal via YAML engine or Durable handle. */
|
|
405
|
-
async function resolveViaSignalRouting(escalation, resolverPayload, metadata) {
|
|
421
|
+
async function resolveViaSignalRouting(escalation, resolverPayload, metadata, resolvedBy) {
|
|
406
422
|
const signalRouting = escalation.metadata.signal_routing;
|
|
407
423
|
const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
|
|
408
424
|
if (signalRouting.engine === 'yaml' && signalRouting.hookTopic && signalRouting.appId) {
|
|
@@ -419,7 +435,11 @@ async function resolveViaSignalRouting(escalation, resolverPayload, metadata) {
|
|
|
419
435
|
else if (signalRouting.workflowId) {
|
|
420
436
|
const client = (0, workers_1.createClient)();
|
|
421
437
|
const handle = await client.workflow.getHandle(signalRouting.taskQueue, signalRouting.workflowType, signalRouting.workflowId);
|
|
422
|
-
|
|
438
|
+
// Provenance rides the SIGNAL only — the resolve below persists the clean payload.
|
|
439
|
+
await handle.signal(signalRouting.signalId, {
|
|
440
|
+
...signalPayload,
|
|
441
|
+
...(resolvedBy ? { $resolution: resolutionOf(escalation.id, resolvedBy) } : {}),
|
|
442
|
+
});
|
|
423
443
|
}
|
|
424
444
|
// Durable resolves here — one atomic call carries the outcome patch.
|
|
425
445
|
// Persist the SAME redacted payload that was delivered to the signal: the raw
|
|
@@ -497,6 +517,28 @@ async function resolveViaRerun(escalation, envelope, resolverPayload, metadata)
|
|
|
497
517
|
};
|
|
498
518
|
}
|
|
499
519
|
// ── Shared helpers ───────────────────────────────────────────────────────
|
|
520
|
+
/**
|
|
521
|
+
* The resolving principal's identity for `$resolution` delivery. The email is
|
|
522
|
+
* best-effort (one indexed read, tolerated failure) — the id alone satisfies
|
|
523
|
+
* the provenance contract.
|
|
524
|
+
*/
|
|
525
|
+
async function resolverIdentity(auth) {
|
|
526
|
+
try {
|
|
527
|
+
const user = await userService.getUser(auth.userId);
|
|
528
|
+
return { id: auth.userId, ...(user?.email ? { email: user.email } : {}) };
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
return { id: auth.userId };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/** The `$resolution` provenance object injected into legacy-path signals. */
|
|
535
|
+
function resolutionOf(escalationId, resolvedBy) {
|
|
536
|
+
return {
|
|
537
|
+
escalationId,
|
|
538
|
+
resolvedBy: resolvedBy.id,
|
|
539
|
+
...(resolvedBy.email ? { resolvedByEmail: resolvedBy.email } : {}),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
500
542
|
function signaledResult(escalation, workflowId) {
|
|
501
543
|
return {
|
|
502
544
|
status: 200,
|
|
@@ -51,6 +51,7 @@ function registerBulkRoutes(router) {
|
|
|
51
51
|
router.post('/bulk-assign', async (req, res) => {
|
|
52
52
|
const result = await api.bulkAssign({
|
|
53
53
|
ids: req.body?.ids,
|
|
54
|
+
query: req.body?.query,
|
|
54
55
|
targetUserId: req.body?.targetUserId,
|
|
55
56
|
durationMinutes: req.body?.durationMinutes,
|
|
56
57
|
}, req.auth);
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -171,7 +171,11 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
171
171
|
durationMinutes?: number;
|
|
172
172
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
173
173
|
bulkAssign: (input: {
|
|
174
|
-
ids
|
|
174
|
+
ids?: string[];
|
|
175
|
+
query?: {
|
|
176
|
+
role: string;
|
|
177
|
+
facets?: Record<string, unknown>;
|
|
178
|
+
};
|
|
175
179
|
targetUserId: string;
|
|
176
180
|
durationMinutes?: number;
|
|
177
181
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
@@ -15,6 +15,20 @@ export declare function bulkAssignEscalations(ids: string[], targetUserId: strin
|
|
|
15
15
|
assigned: number;
|
|
16
16
|
skipped: number;
|
|
17
17
|
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Bulk assign by query: one atomic UPDATE selects and claims every pending,
|
|
20
|
+
* claimable row matching role + facet containment — no search-then-assign
|
|
21
|
+
* window. A row that re-parks between a search and an ids-assign is invisible
|
|
22
|
+
* to the ids form but claimed by this one. Returns the assigned row ids so
|
|
23
|
+
* callers can publish per-row events.
|
|
24
|
+
*/
|
|
25
|
+
export declare function bulkAssignEscalationsByQuery(query: {
|
|
26
|
+
role: string;
|
|
27
|
+
facets?: Record<string, unknown>;
|
|
28
|
+
}, targetUserId: string, durationMinutes?: number): Promise<{
|
|
29
|
+
assigned: number;
|
|
30
|
+
ids: string[];
|
|
31
|
+
}>;
|
|
18
32
|
/**
|
|
19
33
|
* Bulk reassign escalations to a different role.
|
|
20
34
|
* Clears assignment on all affected rows.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.bulkClaimEscalations = bulkClaimEscalations;
|
|
4
4
|
exports.bulkAssignEscalations = bulkAssignEscalations;
|
|
5
|
+
exports.bulkAssignEscalationsByQuery = bulkAssignEscalationsByQuery;
|
|
5
6
|
exports.bulkEscalateToRole = bulkEscalateToRole;
|
|
6
7
|
exports.bulkCancelEscalations = bulkCancelEscalations;
|
|
7
8
|
exports.bulkResolveForTriage = bulkResolveForTriage;
|
|
@@ -32,6 +33,22 @@ async function bulkAssignEscalations(ids, targetUserId, durationMinutes = 30) {
|
|
|
32
33
|
});
|
|
33
34
|
return { assigned: claimed, skipped };
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Bulk assign by query: one atomic UPDATE selects and claims every pending,
|
|
38
|
+
* claimable row matching role + facet containment — no search-then-assign
|
|
39
|
+
* window. A row that re-parks between a search and an ids-assign is invisible
|
|
40
|
+
* to the ids form but claimed by this one. Returns the assigned row ids so
|
|
41
|
+
* callers can publish per-row events.
|
|
42
|
+
*/
|
|
43
|
+
async function bulkAssignEscalationsByQuery(query, targetUserId, durationMinutes = 30) {
|
|
44
|
+
const client = await (0, client_1.escalations)();
|
|
45
|
+
const { claimed, entries } = await client.claimManyByQuery({
|
|
46
|
+
query: { role: query.role, metadata: query.facets },
|
|
47
|
+
assignee: targetUserId,
|
|
48
|
+
durationMinutes,
|
|
49
|
+
});
|
|
50
|
+
return { assigned: claimed, ids: entries.map((e) => e.id) };
|
|
51
|
+
}
|
|
35
52
|
/**
|
|
36
53
|
* Bulk reassign escalations to a different role.
|
|
37
54
|
* Clears assignment on all affected rows.
|
|
@@ -19,8 +19,16 @@ export declare function claimEscalation(id: string, userId: string, durationMinu
|
|
|
19
19
|
* results) into the `@>`-queryable surface alongside the creation metadata
|
|
20
20
|
* ("what was intended"). Distinct from `resolverPayload`, which is delivered to
|
|
21
21
|
* the waiting workflow as `condition()`'s return value and is not GIN-indexed.
|
|
22
|
+
*
|
|
23
|
+
* `resolvedBy` (optional) is delivered to the waiting workflow under the
|
|
24
|
+
* reserved `$resolution` signal key — resolution provenance for the workflow's
|
|
25
|
+
* next step (e.g. assign follow-on work to whoever resolved). Never written to
|
|
26
|
+
* the stored `resolver_payload`.
|
|
22
27
|
*/
|
|
23
|
-
export declare function resolveEscalation(id: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>, assertClaim?: string
|
|
28
|
+
export declare function resolveEscalation(id: string, resolverPayload: Record<string, any>, metadata?: Record<string, any>, assertClaim?: string, resolvedBy?: {
|
|
29
|
+
id: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
}): Promise<LTEscalationRecord | null>;
|
|
24
32
|
/**
|
|
25
33
|
* Resolve a SET of escalations by id in ONE guarded statement
|
|
26
34
|
* (`UPDATE … WHERE id = ANY($ids) AND status='pending'`). Returns the rows that
|
|
@@ -56,7 +64,10 @@ export declare function resolveEscalationsByIds(ids: string[], resolverPayload:
|
|
|
56
64
|
export declare function resolveEscalationsAllOrNone(items: Array<{
|
|
57
65
|
id: string;
|
|
58
66
|
resolverPayload: Record<string, any>;
|
|
59
|
-
}>, metadata?: Record<string, any>, assertAssignee?: string
|
|
67
|
+
}>, metadata?: Record<string, any>, assertAssignee?: string, resolvedBy?: {
|
|
68
|
+
id: string;
|
|
69
|
+
email?: string;
|
|
70
|
+
}): Promise<{
|
|
60
71
|
ok: true;
|
|
61
72
|
escalations: LTEscalationRecord[];
|
|
62
73
|
} | {
|
|
@@ -114,14 +114,19 @@ async function claimEscalation(id, userId, durationMinutes = 30) {
|
|
|
114
114
|
* results) into the `@>`-queryable surface alongside the creation metadata
|
|
115
115
|
* ("what was intended"). Distinct from `resolverPayload`, which is delivered to
|
|
116
116
|
* the waiting workflow as `condition()`'s return value and is not GIN-indexed.
|
|
117
|
+
*
|
|
118
|
+
* `resolvedBy` (optional) is delivered to the waiting workflow under the
|
|
119
|
+
* reserved `$resolution` signal key — resolution provenance for the workflow's
|
|
120
|
+
* next step (e.g. assign follow-on work to whoever resolved). Never written to
|
|
121
|
+
* the stored `resolver_payload`.
|
|
117
122
|
*/
|
|
118
|
-
async function resolveEscalation(id, resolverPayload, metadata, assertClaim) {
|
|
123
|
+
async function resolveEscalation(id, resolverPayload, metadata, assertClaim, resolvedBy) {
|
|
119
124
|
const client = await (0, client_1.escalations)();
|
|
120
125
|
// `assertClaim` rides the SDK's guarded UPDATE: no active claim lock may
|
|
121
126
|
// stand against this assignee, or the resolve blocks (claim-expired /
|
|
122
127
|
// claimed-by-other). The API layer's advisory pre-check (assertLiveClaimant)
|
|
123
128
|
// rejects early; this assertion is the atomic arbiter.
|
|
124
|
-
const result = await client.resolve({ id, resolverPayload, metadata, assertClaim });
|
|
129
|
+
const result = await client.resolve({ id, resolverPayload, metadata, assertClaim, resolvedBy });
|
|
125
130
|
if (!result.ok)
|
|
126
131
|
return null;
|
|
127
132
|
const escalation = (0, map_1.toEscalationRecord)(result.entry);
|
|
@@ -188,11 +193,11 @@ async function resolveEscalationsByIds(ids, resolverPayload, metadata) {
|
|
|
188
193
|
* to that principal, asserted inside the same guarded statement — closing the
|
|
189
194
|
* claim-race window for claim-then-resolve flows.
|
|
190
195
|
*/
|
|
191
|
-
async function resolveEscalationsAllOrNone(items, metadata, assertAssignee) {
|
|
196
|
+
async function resolveEscalationsAllOrNone(items, metadata, assertAssignee, resolvedBy) {
|
|
192
197
|
if (items.length === 0)
|
|
193
198
|
return { ok: true, escalations: [] };
|
|
194
199
|
const client = await (0, client_1.escalations)();
|
|
195
|
-
const result = await client.resolveAllOrNone({ items, metadata, assertAssignee });
|
|
200
|
+
const result = await client.resolveAllOrNone({ items, metadata, assertAssignee, resolvedBy });
|
|
196
201
|
if (!result.ok)
|
|
197
202
|
return { ok: false, failed: result.failed };
|
|
198
203
|
const records = (0, map_1.toEscalationRecords)(result.entries);
|
|
@@ -73,6 +73,26 @@ export type ConditionEscalationConfig = Types.ConditionQueueConfig & {
|
|
|
73
73
|
* }
|
|
74
74
|
* ```
|
|
75
75
|
*
|
|
76
|
+
* **Resolution provenance — the reserved `$resolution` key.** When the resolve
|
|
77
|
+
* carries the resolver's identity (the API layer supplies it for interactive
|
|
78
|
+
* and webhook resolves), the payload delivered here includes
|
|
79
|
+
* `$resolution: { escalationId, resolvedBy, resolvedByEmail? }`. Declare it in
|
|
80
|
+
* the generic to consume it — e.g. assign follow-on work to whoever resolved:
|
|
81
|
+
*
|
|
82
|
+
* ```typescript
|
|
83
|
+
* import type { EscalationResolution } from '@hotmeshio/hotmesh/types';
|
|
84
|
+
*
|
|
85
|
+
* const decision = await conditionLT<{
|
|
86
|
+
* approved: boolean;
|
|
87
|
+
* $resolution?: EscalationResolution;
|
|
88
|
+
* }>(signalId, { role: 'print-operator' });
|
|
89
|
+
* const resolver = decision && decision.$resolution?.resolvedBy;
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* `$resolution` never lands in the stored `resolver_payload` audit column —
|
|
93
|
+
* on the legacy path below it is stripped before the durable resolve and
|
|
94
|
+
* re-attached to the returned payload.
|
|
95
|
+
*
|
|
76
96
|
* **Legacy (two-step) — no config.** Create the escalation first (e.g. via
|
|
77
97
|
* `ltCreateEscalation`) with `signal_id`/`signal_routing` metadata, then wait.
|
|
78
98
|
* On resume the signal payload carries an injected `$escalation_id`; this helper
|
|
@@ -112,6 +112,26 @@ function toEngineConfig(escalation) {
|
|
|
112
112
|
* }
|
|
113
113
|
* ```
|
|
114
114
|
*
|
|
115
|
+
* **Resolution provenance — the reserved `$resolution` key.** When the resolve
|
|
116
|
+
* carries the resolver's identity (the API layer supplies it for interactive
|
|
117
|
+
* and webhook resolves), the payload delivered here includes
|
|
118
|
+
* `$resolution: { escalationId, resolvedBy, resolvedByEmail? }`. Declare it in
|
|
119
|
+
* the generic to consume it — e.g. assign follow-on work to whoever resolved:
|
|
120
|
+
*
|
|
121
|
+
* ```typescript
|
|
122
|
+
* import type { EscalationResolution } from '@hotmeshio/hotmesh/types';
|
|
123
|
+
*
|
|
124
|
+
* const decision = await conditionLT<{
|
|
125
|
+
* approved: boolean;
|
|
126
|
+
* $resolution?: EscalationResolution;
|
|
127
|
+
* }>(signalId, { role: 'print-operator' });
|
|
128
|
+
* const resolver = decision && decision.$resolution?.resolvedBy;
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* `$resolution` never lands in the stored `resolver_payload` audit column —
|
|
132
|
+
* on the legacy path below it is stripped before the durable resolve and
|
|
133
|
+
* re-attached to the returned payload.
|
|
134
|
+
*
|
|
115
135
|
* **Legacy (two-step) — no config.** Create the escalation first (e.g. via
|
|
116
136
|
* `ltCreateEscalation`) with `signal_id`/`signal_routing` metadata, then wait.
|
|
117
137
|
* On resume the signal payload carries an injected `$escalation_id`; this helper
|
|
@@ -138,17 +158,21 @@ async function conditionLT(signalId, escalation) {
|
|
|
138
158
|
taskQueue: LT_ACTIVITY_QUEUE,
|
|
139
159
|
retry: { maximumAttempts: 3 },
|
|
140
160
|
});
|
|
141
|
-
// Strip the injected control keys ($escalation_id, $escalation_metadata
|
|
142
|
-
//
|
|
143
|
-
// the resolve orchestrator's signal paths)
|
|
144
|
-
//
|
|
145
|
-
|
|
161
|
+
// Strip the injected control keys ($escalation_id, $escalation_metadata,
|
|
162
|
+
// $resolution) before the payload is stored. The outcome patch
|
|
163
|
+
// ($escalation_metadata, set by the resolve orchestrator's signal paths)
|
|
164
|
+
// rides INTO the single atomic resolve below so it merges in the same
|
|
165
|
+
// guarded UPDATE — never a separate write. $resolution is provenance for
|
|
166
|
+
// the CALLER, not the audit column: it re-attaches to the returned payload.
|
|
167
|
+
const { $escalation_id: _id, $escalation_metadata: metadata, $resolution: resolution, ...resolverPayload } = raw;
|
|
146
168
|
await ltResolveEscalation({
|
|
147
169
|
escalationId,
|
|
148
170
|
resolverPayload: resolverPayload,
|
|
149
171
|
metadata: metadata,
|
|
150
172
|
});
|
|
151
|
-
return
|
|
173
|
+
return (resolution
|
|
174
|
+
? { ...resolverPayload, $resolution: resolution }
|
|
175
|
+
: resolverPayload);
|
|
152
176
|
}
|
|
153
177
|
return raw;
|
|
154
178
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import type { Types } from '@hotmeshio/hotmesh';
|
|
2
|
+
/**
|
|
3
|
+
* Resolution provenance delivered to a waiting workflow under the reserved
|
|
4
|
+
* `$resolution` signal key — who resolved the escalation and which row
|
|
5
|
+
* delivered it. Declare it in the `conditionLT` payload generic to consume it.
|
|
6
|
+
* Re-exported from the HotMesh SDK so workflow authors import one package.
|
|
7
|
+
*/
|
|
8
|
+
export type EscalationResolution = Types.EscalationResolution;
|
|
1
9
|
/**
|
|
2
10
|
* Reserved keys inside `lt_escalations.metadata`. Everything else in the bag
|
|
3
11
|
* is caller-owned. These ride the GIN-indexed surface so they survive the
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as o}from"./vendor-query-B2UbickB.js";import{c as n}from"./tasks-
|
|
2
|
-
//# sourceMappingURL=AdminDashboard-
|
|
1
|
+
import{j as o}from"./vendor-query-B2UbickB.js";import{c as n}from"./tasks-CnC1xEaD.js";import{K as m}from"./index-C6UuVkz7.js";import{f as c}from"./useEventHooks-Cyq4DiQA.js";import{u}from"./mcp-BwjeqkHn.js";import{P as d}from"./PageHeaderWithStats-BMhnR3-9.js";import"./vendor-react-BpdAFEQc.js";import"./vendor-icons-BE_WrH2z.js";function j(){var a;c();const{data:s}=n({limit:1}),{data:e}=m({status:"pending",limit:1}),{data:t}=u(),r=((a=t==null?void 0:t.servers)==null?void 0:a.filter(i=>i.status==="connected").length)??0,l=t?`${r}/${t.total}`:"—";return o.jsx("div",{children:o.jsx(d,{title:"Admin",stats:[{label:"Tasks",value:(s==null?void 0:s.total)??"—"},{label:"Pending Escalations",value:(e==null?void 0:e.total)??"—",dotClass:"bg-status-pending"},{label:"MCP Servers",value:l},{label:"System",value:"Healthy",dotClass:"bg-status-success"}]})})}export{j as AdminDashboard};
|
|
2
|
+
//# sourceMappingURL=AdminDashboard-CYlm0_J0.js.map
|
package/dashboard/dist/assets/{AdminDashboard-B5B8cf1A.js.map → AdminDashboard-CYlm0_J0.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboard-
|
|
1
|
+
{"version":3,"file":"AdminDashboard-CYlm0_J0.js","sources":["../../src/pages/admin/AdminDashboard.tsx"],"sourcesContent":["import { useTasks } from '../../api/tasks';\nimport { useEscalations } from '../../api/escalations';\nimport { useEscalationListEvents } from '../../hooks/useEventHooks';\nimport { useMcpServers } from '../../api/mcp';\nimport { PageHeaderWithStats } from '../../components/common/layout/PageHeaderWithStats';\n\nexport function AdminDashboard() {\n useEscalationListEvents();\n const { data: taskData } = useTasks({ limit: 1 });\n const { data: pendingEsc } = useEscalations({ status: 'pending', limit: 1 });\n const { data: mcpData } = useMcpServers();\n\n const connectedServers =\n mcpData?.servers?.filter((s) => s.status === 'connected').length ?? 0;\n const mcpLabel = mcpData ? `${connectedServers}/${mcpData.total}` : '—';\n\n return (\n <div>\n <PageHeaderWithStats\n title=\"Admin\"\n stats={[\n { label: 'Tasks', value: taskData?.total ?? '—' },\n { label: 'Pending Escalations', value: pendingEsc?.total ?? '—', dotClass: 'bg-status-pending' },\n { label: 'MCP Servers', value: mcpLabel },\n { label: 'System', value: 'Healthy', dotClass: 'bg-status-success' },\n ]}\n />\n </div>\n );\n}\n"],"names":["AdminDashboard","useEscalationListEvents","taskData","useTasks","pendingEsc","useEscalations","mcpData","useMcpServers","connectedServers","_a","s","mcpLabel","jsx","PageHeaderWithStats"],"mappings":"4UAMO,SAASA,GAAiB,OAC/BC,EAAA,EACA,KAAM,CAAE,KAAMC,CAAA,EAAaC,EAAS,CAAE,MAAO,EAAG,EAC1C,CAAE,KAAMC,CAAA,EAAeC,EAAe,CAAE,OAAQ,UAAW,MAAO,EAAG,EACrE,CAAE,KAAMC,CAAA,EAAYC,EAAA,EAEpBC,IACJC,EAAAH,GAAA,YAAAA,EAAS,UAAT,YAAAG,EAAkB,OAAQC,GAAMA,EAAE,SAAW,aAAa,SAAU,EAChEC,EAAWL,EAAU,GAAGE,CAAgB,IAAIF,EAAQ,KAAK,GAAK,IAEpE,aACG,MAAA,CACC,SAAAM,EAAAA,IAACC,EAAA,CACC,MAAM,QACN,MAAO,CACL,CAAE,MAAO,QAAS,OAAOX,GAAA,YAAAA,EAAU,QAAS,GAAA,EAC5C,CAAE,MAAO,sBAAuB,OAAOE,GAAA,YAAAA,EAAY,QAAS,IAAK,SAAU,mBAAA,EAC3E,CAAE,MAAO,cAAe,MAAOO,CAAA,EAC/B,CAAE,MAAO,SAAU,MAAO,UAAW,SAAU,mBAAA,CAAoB,CACrE,CAAA,EAEJ,CAEJ"}
|