@hotmeshio/long-tail 0.9.5 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -93
- package/build/api/escalations/helpers.d.ts +27 -0
- package/build/api/escalations/helpers.js +34 -0
- package/build/api/escalations/list.d.ts +2 -0
- package/build/api/escalations/list.js +13 -3
- package/build/api/escalations/resolve.js +16 -4
- package/build/routes/escalations/list.js +2 -0
- package/build/services/escalation/crud.d.ts +1 -1
- package/build/services/escalation/crud.js +6 -2
- package/build/services/escalation/facet-sql.d.ts +8 -1
- package/build/services/escalation/facet-sql.js +29 -1
- package/build/types/facets.d.ts +9 -0
- package/dashboard/dist/assets/{AdminDashboard-PvD1IObg.js → AdminDashboard-DQS74RL0.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-PvD1IObg.js.map → AdminDashboard-DQS74RL0.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-BB9NCIiK.js → AgentConfigPage-CSMCrFR4.js} +7 -7
- package/dashboard/dist/assets/{AgentConfigPage-BB9NCIiK.js.map → AgentConfigPage-CSMCrFR4.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-B570UVLx.js → AgentDetailPage-CDiopQ7D.js} +3 -3
- package/dashboard/dist/assets/{AgentDetailPage-B570UVLx.js.map → AgentDetailPage-CDiopQ7D.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-CgUucHar.js → AgentsPage-CyUXe9n4.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-CgUucHar.js.map → AgentsPage-CyUXe9n4.js.map} +1 -1
- package/dashboard/dist/assets/AvailableEscalationsPage-AiuiLcz-.js +2 -0
- package/dashboard/dist/assets/AvailableEscalationsPage-AiuiLcz-.js.map +1 -0
- package/dashboard/dist/assets/BotPicker-BuCGvEed.js +2 -0
- package/dashboard/dist/assets/BotPicker-BuCGvEed.js.map +1 -0
- package/dashboard/dist/assets/CapabilitiesPage-nTv4STcG.js +2 -0
- package/dashboard/dist/assets/{CapabilitiesPage-C8DL3cDA.js.map → CapabilitiesPage-nTv4STcG.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-CDdlmdb6.js → CollapsibleSection-CkOks9Cf.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-CDdlmdb6.js.map → CollapsibleSection-CkOks9Cf.js.map} +1 -1
- package/dashboard/dist/assets/{ConfirmDeleteModal-D9_1b4MW.js → ConfirmDeleteModal-C0x0HcHX.js} +2 -2
- package/dashboard/dist/assets/{ConfirmDeleteModal-D9_1b4MW.js.map → ConfirmDeleteModal-C0x0HcHX.js.map} +1 -1
- package/dashboard/dist/assets/CountdownTimer-DdKL8Ryt.js +2 -0
- package/dashboard/dist/assets/CountdownTimer-DdKL8Ryt.js.map +1 -0
- package/dashboard/dist/assets/CredentialsPage-BwA7k9Cb.js +2 -0
- package/dashboard/dist/assets/{CredentialsPage-Bt7tQHlp.js.map → CredentialsPage-BwA7k9Cb.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DbwuLMO0.js → CronLabel-jC_i-Lh9.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DbwuLMO0.js.map → CronLabel-jC_i-Lh9.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-CKfgf650.js → CustomDurationPicker-F6bR2Q5A.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-CKfgf650.js.map → CustomDurationPicker-F6bR2Q5A.js.map} +1 -1
- package/dashboard/dist/assets/{DropZone-C1TpkVQG.js → DropZone-zxH-9B8c.js} +2 -2
- package/dashboard/dist/assets/{DropZone-C1TpkVQG.js.map → DropZone-zxH-9B8c.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-CgpTWk8N.js → ElapsedCell-DZ3RTZFc.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-CgpTWk8N.js.map → ElapsedCell-DZ3RTZFc.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationListSchemaPage-CxFxPz4C.js → EscalationListSchemaPage-CaMydHIO.js} +3 -3
- package/dashboard/dist/assets/{EscalationListSchemaPage-CxFxPz4C.js.map → EscalationListSchemaPage-CaMydHIO.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-CXvjwJ_I.js → EscalationSchemaPage-BlMx2WE6.js} +3 -3
- package/dashboard/dist/assets/{EscalationSchemaPage-CXvjwJ_I.js.map → EscalationSchemaPage-BlMx2WE6.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-BY4dLou7.js → EscalationsOverview-DcMkbiSo.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-BY4dLou7.js.map → EscalationsOverview-DcMkbiSo.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-MFAHeFTY.js → EventTable-CuZ2Q_wH.js} +2 -2
- package/dashboard/dist/assets/{EventTable-MFAHeFTY.js.map → EventTable-CuZ2Q_wH.js.map} +1 -1
- package/dashboard/dist/assets/FilterBar-B5Vk5tBF.js +2 -0
- package/dashboard/dist/assets/FilterBar-B5Vk5tBF.js.map +1 -0
- package/dashboard/dist/assets/{GraphInvokePage-7T-6qC2N.js → GraphInvokePage-CXElQqcV.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-7T-6qC2N.js.map → GraphInvokePage-CXElQqcV.js.map} +1 -1
- package/dashboard/dist/assets/HomePage-DcWfjZA-.js +2 -0
- package/dashboard/dist/assets/HomePage-DcWfjZA-.js.map +1 -0
- package/dashboard/dist/assets/ListToolbar-Dmd1UiM-.js +2 -0
- package/dashboard/dist/assets/{ListToolbar-DmRE3_dX.js.map → ListToolbar-Dmd1UiM-.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-BukcrbtX.js → McpOverview-Cul2LFEw.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-BukcrbtX.js.map → McpOverview-Cul2LFEw.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-BVx-cGFZ.js → McpQueryDetailPage-BVlQ0WBa.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-BVx-cGFZ.js.map → McpQueryDetailPage-BVlQ0WBa.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-DPXnA6Wa.js → McpQueryPage-B7PmO_V-.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-DPXnA6Wa.js.map → McpQueryPage-B7PmO_V-.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-D3dcICPJ.js → McpRunDetailPage-B1RhxxlU.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-D3dcICPJ.js.map → McpRunDetailPage-B1RhxxlU.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-CzISyj7e.js → McpRunsPage-DlSrpxiv.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-CzISyj7e.js.map → McpRunsPage-DlSrpxiv.js.map} +1 -1
- package/dashboard/dist/assets/Modal-D4afnAjc.js +2 -0
- package/dashboard/dist/assets/Modal-D4afnAjc.js.map +1 -0
- package/dashboard/dist/assets/{NamespacePill--ikKtMvx.js → NamespacePill-BQNMfY0m.js} +2 -2
- package/dashboard/dist/assets/{NamespacePill--ikKtMvx.js.map → NamespacePill-BQNMfY0m.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-vm_ByX41.js +2 -0
- package/dashboard/dist/assets/OperationsPage-vm_ByX41.js.map +1 -0
- package/dashboard/dist/assets/OperatorDashboard-zviIxH-0.js +2 -0
- package/dashboard/dist/assets/{OperatorDashboard-C_bMBGyy.js.map → OperatorDashboard-zviIxH-0.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeader-CiFhzGc0.js → PageHeader-BIY1YvKR.js} +2 -2
- package/dashboard/dist/assets/{PageHeader-CiFhzGc0.js.map → PageHeader-BIY1YvKR.js.map} +1 -1
- package/dashboard/dist/assets/{PageHeaderWithStats-_zOnWAhq.js → PageHeaderWithStats-TUyglVW0.js} +2 -2
- package/dashboard/dist/assets/{PageHeaderWithStats-_zOnWAhq.js.map → PageHeaderWithStats-TUyglVW0.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-BVR41sYI.js → ProcessDetailPage-txiMHbMI.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-BVR41sYI.js.map → ProcessDetailPage-txiMHbMI.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-DAgFK_ln.js → ProcessesListPage-CRttlU3L.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-DAgFK_ln.js.map → ProcessesListPage-CRttlU3L.js.map} +1 -1
- package/dashboard/dist/assets/RoleDetailPage-DBIl_mhK.js +8 -0
- package/dashboard/dist/assets/RoleDetailPage-DBIl_mhK.js.map +1 -0
- package/dashboard/dist/assets/RolePill-4LO_CuO3.js +2 -0
- package/dashboard/dist/assets/RolePill-4LO_CuO3.js.map +1 -0
- package/dashboard/dist/assets/RolesPage-DzrSv6m-.js +2 -0
- package/dashboard/dist/assets/{RolesPage-dRSC725H.js.map → RolesPage-DzrSv6m-.js.map} +1 -1
- package/dashboard/dist/assets/RunAsSelector-CiLjlmIE.js +2 -0
- package/dashboard/dist/assets/{RunAsSelector-C36xqdKj.js.map → RunAsSelector-CiLjlmIE.js.map} +1 -1
- package/dashboard/dist/assets/{SlidePanel-BcWKelpn.js → SlidePanel-DK4sWXmp.js} +2 -2
- package/dashboard/dist/assets/{SlidePanel-BcWKelpn.js.map → SlidePanel-DK4sWXmp.js.map} +1 -1
- package/dashboard/dist/assets/StickyPagination-Di6b2GDE.js +2 -0
- package/dashboard/dist/assets/{StickyPagination-D_HhyJ5V.js.map → StickyPagination-Di6b2GDE.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-C39lLYKC.js → StreamMessageDetail-BbkU45X_.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-C39lLYKC.js.map → StreamMessageDetail-BbkU45X_.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-dlaJS-DC.js → SwimlaneTimeline-BzsvdstG.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-dlaJS-DC.js.map → SwimlaneTimeline-BzsvdstG.js.map} +1 -1
- package/dashboard/dist/assets/{TagInput-BaFJCq9A.js → TagInput-CK-JhBG2.js} +2 -2
- package/dashboard/dist/assets/{TagInput-BaFJCq9A.js.map → TagInput-CK-JhBG2.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-DAnT9c0v.js → TaskDetailPage-Biikk9o_.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-DAnT9c0v.js.map → TaskDetailPage-Biikk9o_.js.map} +1 -1
- package/dashboard/dist/assets/{TaskQueuePill-D9e16qkU.js → TaskQueuePill-C_Vo2Clc.js} +2 -2
- package/dashboard/dist/assets/{TaskQueuePill-D9e16qkU.js.map → TaskQueuePill-C_Vo2Clc.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-CkbK3vdj.js → TasksListPage-t9WAft9-.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-CkbK3vdj.js.map → TasksListPage-t9WAft9-.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-drImakaN.js → TimeAgo-I7X6jdne.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-drImakaN.js.map → TimeAgo-I7X6jdne.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-CpWhUW0W.js → TimestampCell-Cex9nr0t.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-CpWhUW0W.js.map → TimestampCell-Cex9nr0t.js.map} +1 -1
- package/dashboard/dist/assets/ToolPill-BCi101Jh.js +2 -0
- package/dashboard/dist/assets/{ToolPill-Enc3nyic.js.map → ToolPill-BCi101Jh.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-PvMw4dIw.js → ToolTestPanel--MGaDY-1.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-PvMw4dIw.js.map → ToolTestPanel--MGaDY-1.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-DQa0EuM9.js → TopicDetailPage-B8pHHweA.js} +3 -3
- package/dashboard/dist/assets/{TopicDetailPage-DQa0EuM9.js.map → TopicDetailPage-B8pHHweA.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-OzE8IWZf.js → TopicsPage-tiOEQz2m.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-OzE8IWZf.js.map → TopicsPage-tiOEQz2m.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-B-5Cnap4.js → UserName-Cdhyuv5K.js} +2 -2
- package/dashboard/dist/assets/{UserName-B-5Cnap4.js.map → UserName-Cdhyuv5K.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-s4H5TnRv.js → WorkflowExecutionPage-DD1BKj5k.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-s4H5TnRv.js.map → WorkflowExecutionPage-DD1BKj5k.js.map} +1 -1
- package/dashboard/dist/assets/WorkflowPill-DHxrAkp3.js +2 -0
- package/dashboard/dist/assets/{WorkflowPill-S6VV2Eax.js.map → WorkflowPill-DHxrAkp3.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-BqMJDhpp.js → WorkflowsDashboard-BeWjDHYW.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-BqMJDhpp.js.map → WorkflowsDashboard-BeWjDHYW.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-CVd-TybI.js → WorkflowsOverview-CkI-Pmj7.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-CVd-TybI.js.map → WorkflowsOverview-CkI-Pmj7.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-BshuXnvM.js → YamlWorkflowDetailPage-BQnO8-PQ.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-BshuXnvM.js.map → YamlWorkflowDetailPage-BQnO8-PQ.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-D2LRoAki.js → YamlWorkflowsPage-DjmNTQ5r.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-D2LRoAki.js.map → YamlWorkflowsPage-DjmNTQ5r.js.map} +1 -1
- package/dashboard/dist/assets/{agents-CqeRO052.js → agents-2DaoprdB.js} +2 -2
- package/dashboard/dist/assets/{agents-CqeRO052.js.map → agents-2DaoprdB.js.map} +1 -1
- package/dashboard/dist/assets/{bots-yo56kQZI.js → bots-BdwLwMUg.js} +2 -2
- package/dashboard/dist/assets/{bots-yo56kQZI.js.map → bots-BdwLwMUg.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-C-LyQnOQ.js → capabilities-BsuFeK9J.js} +2 -2
- package/dashboard/dist/assets/{capabilities-C-LyQnOQ.js.map → capabilities-BsuFeK9J.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-cpRK9tBf.js → controlplane-CxKOrcFO.js} +2 -2
- package/dashboard/dist/assets/{controlplane-cpRK9tBf.js.map → controlplane-CxKOrcFO.js.map} +1 -1
- package/dashboard/dist/assets/escalation-columns-y5SQ2Ulh.js +2 -0
- package/dashboard/dist/assets/escalation-columns-y5SQ2Ulh.js.map +1 -0
- package/dashboard/dist/assets/index-BC1NxetR.js +2 -0
- package/dashboard/dist/assets/{index-JMRKuqah.js.map → index-BC1NxetR.js.map} +1 -1
- package/dashboard/dist/assets/index-BVZINC_p.js +2 -0
- package/dashboard/dist/assets/{index-D3_Kc337.js.map → index-BVZINC_p.js.map} +1 -1
- package/dashboard/dist/assets/{index-DQgRu4cd.js → index-BXbt5NHG.js} +2 -2
- package/dashboard/dist/assets/{index-DQgRu4cd.js.map → index-BXbt5NHG.js.map} +1 -1
- package/dashboard/dist/assets/{index-C15QKc-V.js → index-BYEkfaUq.js} +2 -2
- package/dashboard/dist/assets/{index-C15QKc-V.js.map → index-BYEkfaUq.js.map} +1 -1
- package/dashboard/dist/assets/index-B_68lp-B.js +5 -0
- package/dashboard/dist/assets/index-B_68lp-B.js.map +1 -0
- package/dashboard/dist/assets/index-BeivyVtT.css +1 -0
- package/dashboard/dist/assets/{index-DNKzmRA3.js → index-CRBXgMsh.js} +2 -2
- package/dashboard/dist/assets/{index-DNKzmRA3.js.map → index-CRBXgMsh.js.map} +1 -1
- package/dashboard/dist/assets/{index-D_FpjN49.js → index-Cfq3UKd8.js} +2 -2
- package/dashboard/dist/assets/{index-D_FpjN49.js.map → index-Cfq3UKd8.js.map} +1 -1
- package/dashboard/dist/assets/{index-D9GgDI2q.js → index-DJ5nhcGB.js} +2 -2
- package/dashboard/dist/assets/{index-D9GgDI2q.js.map → index-DJ5nhcGB.js.map} +1 -1
- package/dashboard/dist/assets/{index-B9YOp7_Z.js → index-DVLBkcoD.js} +4 -4
- package/dashboard/dist/assets/index-DVLBkcoD.js.map +1 -0
- package/dashboard/dist/assets/index-DjI2AUbn.js +5 -0
- package/dashboard/dist/assets/{index-DpYsAAsj.js.map → index-DjI2AUbn.js.map} +1 -1
- package/dashboard/dist/assets/{index-d8dDpRIs.js → index-DyS2DHqy.js} +2 -2
- package/dashboard/dist/assets/{index-d8dDpRIs.js.map → index-DyS2DHqy.js.map} +1 -1
- package/dashboard/dist/assets/index-H6Vp-gPG.js +2 -0
- package/dashboard/dist/assets/{index-CvyZWk2N.js.map → index-H6Vp-gPG.js.map} +1 -1
- package/dashboard/dist/assets/index-YPLTjeli.js +63 -0
- package/dashboard/dist/assets/index-YPLTjeli.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-DSTPZRsM.js → knowledge-CLnYcq0Y.js} +2 -2
- package/dashboard/dist/assets/{knowledge-DSTPZRsM.js.map → knowledge-CLnYcq0Y.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-DPzcauBf.js → mcp-C1-iXkg7.js} +2 -2
- package/dashboard/dist/assets/{mcp-DPzcauBf.js.map → mcp-C1-iXkg7.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-CYLlRjwY.js → mcp-query-DcyDxuGY.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-CYLlRjwY.js.map → mcp-query-DcyDxuGY.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-BtfaUf7f.js → pipelines-BvG5D6bV.js} +2 -2
- package/dashboard/dist/assets/{pipelines-BtfaUf7f.js.map → pipelines-BvG5D6bV.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-bMka9A7P.js → tasks-qwh8q5q7.js} +2 -2
- package/dashboard/dist/assets/{tasks-bMka9A7P.js.map → tasks-qwh8q5q7.js.map} +1 -1
- package/dashboard/dist/assets/{topics-CFcuDwn2.js → topics-MLKKA-Ql.js} +2 -2
- package/dashboard/dist/assets/{topics-CFcuDwn2.js.map → topics-MLKKA-Ql.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-DrtLEQzK.js → useEventHooks-B8uNFB-l.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-DrtLEQzK.js.map → useEventHooks-B8uNFB-l.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BUb0LZ9l.js → useNamespace-DjZmztJM.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BUb0LZ9l.js.map → useNamespace-DjZmztJM.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-6DPlPdg0.js → useYamlActivityEvents-DIvK20uI.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-6DPlPdg0.js.map → useYamlActivityEvents-DIvK20uI.js.map} +1 -1
- package/dashboard/dist/assets/{users-FTnxEv_t.js → users-CVjajeDg.js} +2 -2
- package/dashboard/dist/assets/{users-FTnxEv_t.js.map → users-CVjajeDg.js.map} +1 -1
- package/dashboard/dist/assets/{vendor-icons-DPmIjhZB.js → vendor-icons-Qk9qLjF_.js} +131 -126
- package/dashboard/dist/assets/vendor-icons-Qk9qLjF_.js.map +1 -0
- package/dashboard/dist/assets/{workflows-DgeQWLWK.js → workflows-BziTcJNq.js} +2 -2
- package/dashboard/dist/assets/{workflows-DgeQWLWK.js.map → workflows-BziTcJNq.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-BjatB1t-.js → yaml-workflows-Dr-ZcE_0.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-BjatB1t-.js.map → yaml-workflows-Dr-ZcE_0.js.map} +1 -1
- package/dashboard/dist/index.html +3 -3
- package/docs/dashboard.md +2 -2
- package/docs/faceted-routing.md +9 -0
- package/docs/hitl/escalation.md +206 -0
- package/docs/hitl/form.md +162 -0
- package/docs/hitl/iframe.md +170 -0
- package/docs/hitl/resolution.md +166 -0
- package/docs/hitl/roles.md +72 -0
- package/docs/hitl/x-lt-layout.md +167 -0
- package/docs/hitl/x-lt-list-schema.md +75 -0
- package/docs/hitl/x-lt-show-if.md +128 -0
- package/docs/hitl/x-lt-validation.md +168 -0
- package/docs/hitl/x-lt-widget.md +152 -0
- package/docs/hitl-guide.md +84 -1144
- package/docs/iam.md +1 -1
- package/package.json +2 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-B1cCs4jO.js +0 -2
- package/dashboard/dist/assets/AvailableEscalationsPage-B1cCs4jO.js.map +0 -1
- package/dashboard/dist/assets/BotPicker-CYw4ZeAh.js +0 -2
- package/dashboard/dist/assets/BotPicker-CYw4ZeAh.js.map +0 -1
- package/dashboard/dist/assets/CapabilitiesPage-C8DL3cDA.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-BrQl3JWm.js +0 -2
- package/dashboard/dist/assets/CountdownTimer-BrQl3JWm.js.map +0 -1
- package/dashboard/dist/assets/CredentialsPage-Bt7tQHlp.js +0 -2
- package/dashboard/dist/assets/FilterBar-DDIn8TOs.js +0 -2
- package/dashboard/dist/assets/FilterBar-DDIn8TOs.js.map +0 -1
- package/dashboard/dist/assets/HomePage-1PI7E3sw.js +0 -2
- package/dashboard/dist/assets/HomePage-1PI7E3sw.js.map +0 -1
- package/dashboard/dist/assets/ListToolbar-DmRE3_dX.js +0 -2
- package/dashboard/dist/assets/Modal-CSrxpXeM.js +0 -2
- package/dashboard/dist/assets/Modal-CSrxpXeM.js.map +0 -1
- package/dashboard/dist/assets/OperationsPage-BYth5Dxg.js +0 -2
- package/dashboard/dist/assets/OperationsPage-BYth5Dxg.js.map +0 -1
- package/dashboard/dist/assets/OperatorDashboard-C_bMBGyy.js +0 -2
- package/dashboard/dist/assets/RoleDetailPage-NxT8GaWV.js +0 -8
- package/dashboard/dist/assets/RoleDetailPage-NxT8GaWV.js.map +0 -1
- package/dashboard/dist/assets/RolePill-CUIJSgRq.js +0 -2
- package/dashboard/dist/assets/RolePill-CUIJSgRq.js.map +0 -1
- package/dashboard/dist/assets/RolesPage-dRSC725H.js +0 -2
- package/dashboard/dist/assets/RunAsSelector-C36xqdKj.js +0 -2
- package/dashboard/dist/assets/StickyPagination-D_HhyJ5V.js +0 -2
- package/dashboard/dist/assets/ToolPill-Enc3nyic.js +0 -2
- package/dashboard/dist/assets/WorkflowPill-S6VV2Eax.js +0 -2
- package/dashboard/dist/assets/escalation-columns-CSG3LG3F.js +0 -2
- package/dashboard/dist/assets/escalation-columns-CSG3LG3F.js.map +0 -1
- package/dashboard/dist/assets/index-B9YOp7_Z.js.map +0 -1
- package/dashboard/dist/assets/index-BJwHI2aX.js +0 -63
- package/dashboard/dist/assets/index-BJwHI2aX.js.map +0 -1
- package/dashboard/dist/assets/index-CvyZWk2N.js +0 -2
- package/dashboard/dist/assets/index-D3_Kc337.js +0 -2
- package/dashboard/dist/assets/index-D9I9XhrD.js +0 -5
- package/dashboard/dist/assets/index-D9I9XhrD.js.map +0 -1
- package/dashboard/dist/assets/index-DpYsAAsj.js +0 -5
- package/dashboard/dist/assets/index-JMRKuqah.js +0 -2
- package/dashboard/dist/assets/index-waKF7bKZ.css +0 -1
- package/dashboard/dist/assets/roles-CDi58HW9.js +0 -2
- package/dashboard/dist/assets/roles-CDi58HW9.js.map +0 -1
- package/dashboard/dist/assets/vendor-icons-DPmIjhZB.js.map +0 -1
package/docs/faceted-routing.md
CHANGED
|
@@ -44,6 +44,7 @@ interface FacetQuery {
|
|
|
44
44
|
exists?: string[]; // metadata ? key — facet must be present
|
|
45
45
|
status?: string; // e.g. 'pending'
|
|
46
46
|
available?: boolean; // true = unclaimed/expired only; false = held now
|
|
47
|
+
jeopardy?: boolean; // true = past the role's priority threshold only
|
|
47
48
|
orderBy?: { field: string; direction?: 'asc' | 'desc'; numeric?: boolean }[];
|
|
48
49
|
limit?: number;
|
|
49
50
|
offset?: number;
|
|
@@ -55,6 +56,14 @@ interface FacetQuery {
|
|
|
55
56
|
text, or numeric when `numeric` is set). Every value is parameterized; only validated column
|
|
56
57
|
names / metadata keys and a fixed operator set are ever interpolated.
|
|
57
58
|
|
|
59
|
+
`jeopardy` keeps only rows past their role's priority threshold — the exact predicate behind
|
|
60
|
+
the Pace Board's priority count, evaluated per row against the role's dials
|
|
61
|
+
(`priority_facet` metadata timestamp, `created_at` when unset; `priority_threshold_minutes`,
|
|
62
|
+
`sla_minutes` when unset; roles with no threshold contribute no rows). Because the count and
|
|
63
|
+
the list share one expression, a jeopardy list's total always equals the badge that linked to
|
|
64
|
+
it. On the HTTP surface it rides as `jeopardy=1`; the canonical deep link is
|
|
65
|
+
`/escalations/available?role=<r>&jeopardy=1&view=table&orderBy=[{"field":"metadata.<facet>"|"created_at","direction":"asc"}]`.
|
|
66
|
+
|
|
58
67
|
```typescript
|
|
59
68
|
// Pending, available diabetic-certified work needing PLA, soonest-deadline first.
|
|
60
69
|
const query: FacetQuery = {
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Creating Escalations
|
|
2
|
+
|
|
3
|
+
## `conditionLT` — Atomic Pattern
|
|
4
|
+
|
|
5
|
+
Pass an escalation config to `conditionLT`. The escalation row, its metadata, and the resume timer all commit inside the workflow's Leg1 checkpoint — one write, crash-safe. The `signal_key` on the row is the resume key: the dashboard resolve endpoint and `POST /api/escalations/resolve-by-signal-key` both resume this job in place, and the `system.escalation.{id}.created` event fires automatically.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { conditionLT } from '@hotmeshio/long-tail';
|
|
9
|
+
|
|
10
|
+
export async function approvalWorkflow(envelope: LTEnvelope) {
|
|
11
|
+
const ctx = Durable.workflow.workflowInfo();
|
|
12
|
+
const signalId = `approval-${ctx.workflowId}`;
|
|
13
|
+
|
|
14
|
+
const decision = await conditionLT<{ approved: boolean; notes?: string }>(signalId, {
|
|
15
|
+
role: 'finance-reviewer',
|
|
16
|
+
type: 'approval',
|
|
17
|
+
subtype: 'budget-request',
|
|
18
|
+
priority: 2,
|
|
19
|
+
description: `Budget approval needed: $${envelope.data.amount}`,
|
|
20
|
+
metadata: {
|
|
21
|
+
form_schema: {
|
|
22
|
+
title: 'Budget Approval',
|
|
23
|
+
properties: {
|
|
24
|
+
approved: { type: 'boolean', description: 'Approve this request?' },
|
|
25
|
+
notes: { type: 'string', format: 'textarea' },
|
|
26
|
+
},
|
|
27
|
+
required: ['approved'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
envelope: { data: envelope.data },
|
|
31
|
+
timeout: '72h', // SLA: resume with false + expire the row if unresolved
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (decision === false) {
|
|
35
|
+
// SLA timer fired — the row is already status='expired', any late resolve returns already-expired
|
|
36
|
+
return { type: 'return' as const, data: { autoRejected: 'sla' } };
|
|
37
|
+
}
|
|
38
|
+
if (decision === null) {
|
|
39
|
+
// Escalation was cancelled (workflow terminated or explicit cancel)
|
|
40
|
+
return { type: 'return' as const, data: { cancelled: true } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (decision.approved) {
|
|
44
|
+
// ... approved flow ...
|
|
45
|
+
} else {
|
|
46
|
+
// ... rejection flow ...
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`conditionLT` returns `T | false | null`:
|
|
52
|
+
- `T` — the human's resolver payload
|
|
53
|
+
- `false` — SLA timeout (omit `timeout` for an open-ended wait)
|
|
54
|
+
- `null` — cancellation (workflow terminated or explicit cancel)
|
|
55
|
+
|
|
56
|
+
### Row completeness guarantee
|
|
57
|
+
|
|
58
|
+
Every field of the config — including all `metadata` facets — commits inside the Leg1 checkpoint. A claim-by-metadata router or a version-pinned facet reads a complete row from its first visible moment; there is no window where a row is visible but its metadata is still en route.
|
|
59
|
+
|
|
60
|
+
### Early-signal buffering
|
|
61
|
+
|
|
62
|
+
A resolve that races ahead of the `conditionLT` registration (a fast webhook, or a payload deposited before the workflow starts) is held as a pending signal and delivered when the wait registers — 10 minutes by default; pass `expire` to `signal()` when signaling early on purpose. Fan-out (`Promise.all` over many waits) scales the same way.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Versioned Role Schemas
|
|
67
|
+
|
|
68
|
+
Every role carries a versioned `form_schema`. Every save that changes it appends an immutable snapshot to `lt_role_schemas` and advances the role's current version. Escalations pin one with `schemaVersion`:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const decision = await conditionLT<{ approved: boolean; lotNumber: string }>(signalId, {
|
|
72
|
+
role: 'reviewer',
|
|
73
|
+
description: instructions,
|
|
74
|
+
schemaVersion: 3, // renders role schema v3, always, regardless of later edits
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The pin travels as `metadata.schema_version` on the row (GIN-indexed, queryable like any facet). A pin that names a missing version fails at creation with a 400 — it never silently falls through to a different version. Without a pin, the role's latest schema always applies.
|
|
79
|
+
|
|
80
|
+
Inspect versions:
|
|
81
|
+
- `GET /api/roles/:role/schema?version=N`
|
|
82
|
+
- `GET /api/roles/:role/schema/versions`
|
|
83
|
+
- `lt.roles.getSchema` / `lt.roles.listSchemaVersions`
|
|
84
|
+
- `ltc roles schema <role> --version N`
|
|
85
|
+
- `get_role_schema` / `list_role_schema_versions` admin MCP tools
|
|
86
|
+
- Dashboard: `/admin/roles/:role/schema`
|
|
87
|
+
|
|
88
|
+
Save a new version: `PATCH /api/roles/:role` with `form_schema` (+ optional `change_summary`), `lt.roles.update`, `ltc roles save-schema <role> --file schema.json`, the `update_role` MCP tool, or the dashboard schema editor.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Form Schema Resolution
|
|
93
|
+
|
|
94
|
+
The resolver form uses the most specific schema available for each escalation row:
|
|
95
|
+
|
|
96
|
+
1. **`metadata.form_schema`** — a full JSON Schema embedded on the row. Use when different escalation points in the same workflow need different forms.
|
|
97
|
+
2. **The role's versioned `form_schema`** — when `metadata.schema_version` is set, the snapshot at that version renders; otherwise the role's latest applies.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Working Examples
|
|
102
|
+
|
|
103
|
+
The reference examples ship as fully runnable seeds:
|
|
104
|
+
|
|
105
|
+
- `examples/workflows/rich-form/` — `intake-reviewer` role: two-column layout, ordering, date and email formats, enum, file upload, spans, required fields, `x-lt-bind`, `x-lt-help`
|
|
106
|
+
- `examples/workflows/checklist-confirmation/` — `checklist-operator` role: checklist widget with `x-lt-source` driving dynamic items from the envelope
|
|
107
|
+
- `examples/workflows/constraint-form/` — `quality-reviewer` role: the reference for all pre-submission guards — hidden required field, dynamic checklist, min/max, pattern, dynamic numeric and string-length bounds
|
|
108
|
+
|
|
109
|
+
### Simple approval
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { conditionLT } from '@hotmeshio/long-tail';
|
|
113
|
+
|
|
114
|
+
export async function approveSpendWorkflow(envelope: LTEnvelope) {
|
|
115
|
+
const ctx = Durable.workflow.workflowInfo();
|
|
116
|
+
|
|
117
|
+
const decision = await conditionLT<{ approved: boolean; notes?: string }>(
|
|
118
|
+
`spend-approval-${ctx.workflowId}`,
|
|
119
|
+
{
|
|
120
|
+
role: 'finance-reviewer',
|
|
121
|
+
type: 'approval',
|
|
122
|
+
description: `Approve spend of $${envelope.data.amount} for ${envelope.data.vendor}`,
|
|
123
|
+
priority: 2,
|
|
124
|
+
metadata: {
|
|
125
|
+
form_schema: {
|
|
126
|
+
title: 'Spend Approval',
|
|
127
|
+
description: 'Review and approve or reject this spend request.',
|
|
128
|
+
required: ['approved'],
|
|
129
|
+
properties: {
|
|
130
|
+
approved: { type: 'boolean', description: 'Check to approve' },
|
|
131
|
+
notes: { type: 'string', format: 'textarea', description: 'Optional comments' },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
timeout: '48h',
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (decision === null) return { type: 'return' as const, data: { cancelled: true } };
|
|
140
|
+
if (decision === false) return { type: 'return' as const, data: { timedOut: true } };
|
|
141
|
+
|
|
142
|
+
if (decision.approved) {
|
|
143
|
+
await releasePayment(envelope.data);
|
|
144
|
+
} else {
|
|
145
|
+
await notifyRejection(envelope.data, decision.notes);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Checklist confirmation
|
|
151
|
+
|
|
152
|
+
Dynamic checklist where item labels come from the workflow's envelope, not the static schema. Item count and wording vary per escalation without touching the form schema.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { conditionLT } from '@hotmeshio/long-tail';
|
|
156
|
+
|
|
157
|
+
export async function stationCheckWorkflow(envelope: LTEnvelope) {
|
|
158
|
+
const ctx = Durable.workflow.workflowInfo();
|
|
159
|
+
|
|
160
|
+
const steps = [
|
|
161
|
+
{ id: 'step_0', label: 'Verify patient ID matches the order' },
|
|
162
|
+
{ id: 'step_1', label: 'Confirm dosage and route of administration' },
|
|
163
|
+
{ id: 'step_2', label: 'Sign the dispensing log' },
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
const result = await conditionLT<{ checks: Record<string, boolean> }>(
|
|
167
|
+
`station-check-${ctx.workflowId}`,
|
|
168
|
+
{
|
|
169
|
+
role: 'station-operator',
|
|
170
|
+
type: 'station-check',
|
|
171
|
+
description: 'Complete all station checks before releasing',
|
|
172
|
+
priority: 1,
|
|
173
|
+
envelope: {
|
|
174
|
+
checklist_items: steps,
|
|
175
|
+
formDefaults: {
|
|
176
|
+
checks: Object.fromEntries(steps.map((s) => [s.id, false])),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
metadata: {
|
|
180
|
+
form_schema: {
|
|
181
|
+
title: 'Station Check',
|
|
182
|
+
description: 'Work through each step and confirm completion.',
|
|
183
|
+
required: ['checks'],
|
|
184
|
+
properties: {
|
|
185
|
+
checks: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
'x-lt-widget': 'checklist',
|
|
188
|
+
'x-lt-source': 'envelope.checklist_items',
|
|
189
|
+
description: 'Check each step when complete',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (!result) return { type: 'return' as const, data: { cancelled: true } };
|
|
198
|
+
|
|
199
|
+
const allClear = Object.values(result.checks).every(Boolean);
|
|
200
|
+
const failed = steps.filter((s) => !result.checks[s.id]).map((s) => s.label);
|
|
201
|
+
|
|
202
|
+
if (!allClear) {
|
|
203
|
+
await flagIncomplete({ failed });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Form Fields
|
|
2
|
+
|
|
3
|
+
The dashboard renders forms automatically from JSON Schema. No frontend code needed.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Field Types
|
|
8
|
+
|
|
9
|
+
| JSON Type | Renders As |
|
|
10
|
+
|-----------|-----------|
|
|
11
|
+
| `boolean` | Checkbox toggle |
|
|
12
|
+
| `number` | Number input |
|
|
13
|
+
| `string` | Text input (default) |
|
|
14
|
+
| `string` + `enum` | Dropdown select |
|
|
15
|
+
| `null` | Read-only "null" display |
|
|
16
|
+
| `array` | Tag display (read-only) |
|
|
17
|
+
| `object` | Nested section with recursive fields, or a widget (see [x-lt-widget.md](x-lt-widget.md)) |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## String Format Extensions
|
|
22
|
+
|
|
23
|
+
Use `format` on any `string` field to get a specialized input:
|
|
24
|
+
|
|
25
|
+
| Format | Renders as |
|
|
26
|
+
|--------|-----------|
|
|
27
|
+
| `"password"` | Masked input — ephemeral token replaces the value in the resolver payload |
|
|
28
|
+
| `"date"` | Date picker |
|
|
29
|
+
| `"date-time"` | Date + time picker |
|
|
30
|
+
| `"email"` | Email input — pre-submission guard enforces valid address format |
|
|
31
|
+
| `"uri"` | URL input — pre-submission guard enforces `https?://...` format |
|
|
32
|
+
| `"textarea"` | Multi-line textarea |
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"properties": {
|
|
37
|
+
"due_date": { "type": "string", "format": "date" },
|
|
38
|
+
"contact_email": { "type": "string", "format": "email" },
|
|
39
|
+
"report_url": { "type": "string", "format": "uri" },
|
|
40
|
+
"notes": { "type": "string", "format": "textarea" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Required Fields
|
|
48
|
+
|
|
49
|
+
Fields listed in `required` show a red asterisk and block submission when empty:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"required": ["decision"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"decision": { "type": "string", "enum": ["approve", "reject"] },
|
|
56
|
+
"notes": { "type": "string", "description": "Optional comments" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A required `object` field (e.g. a `checklist` widget) is satisfied when at least one of its boolean values is `true`.
|
|
62
|
+
|
|
63
|
+
Hidden fields — those suppressed by `x-lt-showIf` at submit time — are skipped from required validation. See [x-lt-show-if.md](x-lt-show-if.md) for conditional required fields.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Read-Only Fields
|
|
68
|
+
|
|
69
|
+
Fields with `readOnly: true` display as static text — the value is shown but cannot be edited:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"properties": {
|
|
74
|
+
"request_amount": { "type": "number", "readOnly": true },
|
|
75
|
+
"approved_amount": { "type": "number", "description": "Enter the approved amount" }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use `readOnly` with `x-lt-hide-if-empty: true` to suppress empty fact fields entirely (see [x-lt-show-if.md](x-lt-show-if.md)).
|
|
81
|
+
|
|
82
|
+
Use `readOnly` with `x-lt-widget: "markdown"` to embed a rendered content block — SOPs, checklists, review instructions — inside the form (see [x-lt-widget.md](x-lt-widget.md)).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Schema Title and Description
|
|
87
|
+
|
|
88
|
+
`title` and `description` at the schema root appear in the form header:
|
|
89
|
+
|
|
90
|
+
- **`title`** — shown as the form section header
|
|
91
|
+
- **`description`** — short subtitle beneath the title; keep to a phrase; longer guidance belongs in [`x-lt-help`](x-lt-layout.md#help-panel-x-lt-help)
|
|
92
|
+
|
|
93
|
+
Individual field `title` and `description` render as the field label and inline help text, respectively.
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"title": "Expense Approval",
|
|
98
|
+
"description": "Review the expense report and approve or reject with notes.",
|
|
99
|
+
"properties": {
|
|
100
|
+
"approved": {
|
|
101
|
+
"type": "boolean",
|
|
102
|
+
"title": "Approve",
|
|
103
|
+
"description": "Check to approve. Leave unchecked to reject."
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Credential Fields
|
|
112
|
+
|
|
113
|
+
Password fields (`"format": "password"`) are masked in the UI. On submit, the platform replaces the plain-text value with a short-lived ephemeral token (`eph:v1:*`, 15-minute TTL) before storing or signaling it to the workflow — the secret is never written to the escalation record.
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"properties": {
|
|
118
|
+
"api_key": { "type": "string", "description": "API Key" },
|
|
119
|
+
"api_secret": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"format": "password",
|
|
122
|
+
"description": "API Secret — stored as an ephemeral token, never in plain text"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"required": ["api_key", "api_secret"]
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The workflow receives the `eph:v1:*` token and passes it to the integration layer for redemption.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Draft Persistence
|
|
134
|
+
|
|
135
|
+
Form edits are saved locally (browser storage, per escalation) a moment after each change and restored when the resolver returns to the item — a lapsed claim, an accidental navigation, or a browser restart keeps typed input intact. The draft holds field values only; the form definition always renders from the current schema resolution, so a draft never resurrects a stale form. Pristine, untouched defaults are not stored. The draft clears when the escalation is resolved, acknowledged, or cancelled from that browser.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Accessibility
|
|
140
|
+
|
|
141
|
+
Generated form controls carry the wiring assistive technology depends on:
|
|
142
|
+
|
|
143
|
+
- Every input is associated with its label (`label[for]` → `input[id]`), and checkbox labels wrap their controls.
|
|
144
|
+
- Required fields set `aria-required`; failing fields set `aria-invalid` and link their message via `aria-describedby`.
|
|
145
|
+
- Inline validation messages and the side panel's error list render as `role="alert"` live regions, so corrections are announced as they happen.
|
|
146
|
+
- The checklist widget is a `role="group"` labeled by its field description.
|
|
147
|
+
- A locked form (unclaimed, claimed by another user, or a lapsed claim) is `inert`: its fields leave the tab order entirely rather than merely looking disabled.
|
|
148
|
+
- Modals are `role="dialog"` with `aria-modal` and a labelled title; Escape closes them.
|
|
149
|
+
- The error panel's entries are buttons — clicking one scrolls to and focuses the failing field, keyboard included.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Designing the Form
|
|
154
|
+
|
|
155
|
+
To create a polished resolve experience:
|
|
156
|
+
|
|
157
|
+
1. Set `title` on the schema — it becomes the form section header
|
|
158
|
+
2. Set `x-lt-help` — SOPs, tables, and links render in the side panel as markdown with `{{domain.path}}` tokens for live record values (see [x-lt-layout.md](x-lt-layout.md#help-panel-x-lt-help))
|
|
159
|
+
3. Use `readOnly` fields for context the human needs to see but not change
|
|
160
|
+
4. Use `x-lt-order` to put the most important fields first (see [x-lt-layout.md](x-lt-layout.md))
|
|
161
|
+
5. Use `required` to guide users on what must be filled
|
|
162
|
+
6. Use field-level `description` for inline help text
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Iframe Viewport Protocol
|
|
2
|
+
|
|
3
|
+
For fully custom UIs — PDF viewers, complex multi-step forms, specialized domain interfaces — replace the generated form with an iframe. The iframe owns its entire surface: platform-side validation, layout, widgets, and draft persistence apply to the generated form, so the embedded app provides its own. Reach for the schema-driven form first ([choosing your surface](../hitl-guide.md#choosing-your-surface)); use the iframe when the domain demands a surface the schema cannot express.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Schema Declaration
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"x-lt-viewport": {
|
|
12
|
+
"type": "iframe",
|
|
13
|
+
"src": "https://your-app.example.com/hitl-form"
|
|
14
|
+
},
|
|
15
|
+
"properties": { }
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
When `x-lt-viewport` is present, the dashboard renders an iframe instead of the standard form. The `properties` block still defines the resolver payload shape for typing purposes.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## URL Token Substitution
|
|
24
|
+
|
|
25
|
+
The `src` value supports `{key}` tokens — single-brace, flat key lookup. The dashboard expands them at render time using values merged from three sources, in priority order:
|
|
26
|
+
|
|
27
|
+
| Priority | Source | Field |
|
|
28
|
+
|----------|--------|-------|
|
|
29
|
+
| 1 (highest) | `escalation_payload` | Per-escalation context set by the workflow |
|
|
30
|
+
| 2 | `envelope` | Input envelope set by the workflow |
|
|
31
|
+
| 3 (lowest) | `metadata` | Row metadata |
|
|
32
|
+
|
|
33
|
+
Keys present in multiple sources resolve from the highest-priority one. A key with no match is left as `{key}` in the final URL.
|
|
34
|
+
|
|
35
|
+
### Example — CAD designer workbench
|
|
36
|
+
|
|
37
|
+
The `cad-designer` role embeds a WebGL editor. Each escalation carries `workbenchId` and `companyId` in its payload; the dashboard injects them into the iframe URL at render time:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Form schema on the role
|
|
41
|
+
const WORKBENCH_FORM_SCHEMA = {
|
|
42
|
+
'x-lt-viewport': {
|
|
43
|
+
type: 'iframe',
|
|
44
|
+
src: `${BASE_URL}/design?workbenchId={workbenchId}&companyId={companyId}`,
|
|
45
|
+
},
|
|
46
|
+
properties: {
|
|
47
|
+
stl_url: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Object-storage URL of the completed design, set by the embedded editor.',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Workflow side — the wait is a normal conditionLT; the iframe submits the payload
|
|
55
|
+
const design = await conditionLT<{ stl_url: string }>(signalId, {
|
|
56
|
+
role: 'cad-designer',
|
|
57
|
+
description: 'Design the orthotic insole for this order.',
|
|
58
|
+
escalation_payload: JSON.stringify({ workbenchId, companyId }),
|
|
59
|
+
});
|
|
60
|
+
if (design) {
|
|
61
|
+
await attachDesign(design.stl_url);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
At render time the dashboard produces:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
https://editor.internal/design?workbenchId=wb-123&companyId=co-456
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`{workbenchId}` and `{companyId}` are resolved from `escalation_payload`, which has the highest priority — so even if the same keys appear in `envelope` or `metadata`, the payload values win.
|
|
72
|
+
|
|
73
|
+
### Common token patterns
|
|
74
|
+
|
|
75
|
+
| Goal | Source to use | Token |
|
|
76
|
+
|------|--------------|-------|
|
|
77
|
+
| Per-escalation session or record ID | `escalation_payload` | `{sessionId}`, `{orderId}` |
|
|
78
|
+
| Tenant or company context | `escalation_payload` or `envelope` | `{companyId}`, `{tenantId}` |
|
|
79
|
+
| Role-wide base path (same for all escalations) | `metadata` on the escalation or schema-level constant | Embed directly in `src` |
|
|
80
|
+
| Configuration passed at escalation creation | `envelope` | `{formMode}`, `{locale}` |
|
|
81
|
+
|
|
82
|
+
> **Note:** The `{key}` syntax used in the `src` URL is distinct from the `{{domain.path}}` syntax used in `x-lt-help`, `x-lt-active`, and list schema templates. The URL substitution is a flat key lookup across merged sources; the template tokens do a domain-routed deep path lookup.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Message Protocol
|
|
87
|
+
|
|
88
|
+
Communication happens via `window.postMessage`.
|
|
89
|
+
|
|
90
|
+
### Parent → Iframe
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Sent when the iframe signals ready (or on load)
|
|
94
|
+
{
|
|
95
|
+
type: 'lt:init',
|
|
96
|
+
escalation: {
|
|
97
|
+
id: string,
|
|
98
|
+
type: string,
|
|
99
|
+
subtype: string,
|
|
100
|
+
description: string | null,
|
|
101
|
+
status: string,
|
|
102
|
+
priority: number,
|
|
103
|
+
role: string,
|
|
104
|
+
workflow_type: string | null,
|
|
105
|
+
},
|
|
106
|
+
schema: Record<string, unknown>, // The full form schema
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Optional: parent requests the iframe to submit
|
|
110
|
+
{ type: 'lt:requestSubmit' }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Iframe → Parent
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Signal that the iframe is ready to receive init data
|
|
117
|
+
{ type: 'lt:ready' }
|
|
118
|
+
|
|
119
|
+
// Submit the human's response — triggers escalation resolution
|
|
120
|
+
{ type: 'lt:submit', payload: { approved: true, notes: '...' } }
|
|
121
|
+
|
|
122
|
+
// Escalate to a different role
|
|
123
|
+
{ type: 'lt:escalate', target: 'senior-reviewer' }
|
|
124
|
+
|
|
125
|
+
// Auto-resize the iframe height
|
|
126
|
+
{ type: 'lt:resize', height: 600 }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Minimal Example
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<!DOCTYPE html>
|
|
135
|
+
<html>
|
|
136
|
+
<head><title>Custom HITL Form</title></head>
|
|
137
|
+
<body>
|
|
138
|
+
<div id="form"></div>
|
|
139
|
+
<button id="submit">Approve</button>
|
|
140
|
+
|
|
141
|
+
<script>
|
|
142
|
+
window.parent.postMessage({ type: 'lt:ready' }, '*');
|
|
143
|
+
|
|
144
|
+
window.addEventListener('message', (event) => {
|
|
145
|
+
if (event.data.type === 'lt:init') {
|
|
146
|
+
const { escalation, schema } = event.data;
|
|
147
|
+
document.getElementById('form').textContent =
|
|
148
|
+
`Reviewing: ${escalation.description}`;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
document.getElementById('submit').addEventListener('click', () => {
|
|
153
|
+
window.parent.postMessage({
|
|
154
|
+
type: 'lt:submit',
|
|
155
|
+
payload: { approved: true, reviewed_at: new Date().toISOString() },
|
|
156
|
+
}, '*');
|
|
157
|
+
});
|
|
158
|
+
</script>
|
|
159
|
+
</body>
|
|
160
|
+
</html>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Security
|
|
166
|
+
|
|
167
|
+
- The iframe runs with `sandbox="allow-scripts allow-same-origin allow-forms"`
|
|
168
|
+
- The parent validates message origins — only messages from the iframe's declared origin are accepted
|
|
169
|
+
- The `envelope` field (which may contain secrets) is not sent to the iframe
|
|
170
|
+
- Only safe escalation metadata (`id`, `type`, `description`, `status`, `priority`, `role`) is exposed
|