@hotmeshio/long-tail 0.8.4 → 0.9.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.
Files changed (257) hide show
  1. package/build/api/escalations/index.d.ts +1 -1
  2. package/build/api/escalations/index.js +2 -1
  3. package/build/api/escalations/resolve.d.ts +28 -0
  4. package/build/api/escalations/resolve.js +98 -0
  5. package/build/api/roles.d.ts +21 -0
  6. package/build/api/roles.js +50 -0
  7. package/build/lib/db/index.js +12 -1
  8. package/build/lib/db/migrate.js +27 -0
  9. package/build/lib/db/schemas/021_role_list_schema.sql +27 -0
  10. package/build/modules/defaults.d.ts +2 -0
  11. package/build/modules/defaults.js +3 -1
  12. package/build/routes/escalations/resolve.js +18 -0
  13. package/build/routes/roles.js +20 -0
  14. package/build/sdk/index.d.ts +8 -0
  15. package/build/sdk/index.js +1 -0
  16. package/build/services/escalation/crud.d.ts +36 -0
  17. package/build/services/escalation/crud.js +53 -0
  18. package/build/services/maintenance/index.js +22 -0
  19. package/build/services/role/index.d.ts +13 -1
  20. package/build/services/role/index.js +27 -0
  21. package/build/services/role/sql.d.ts +14 -2
  22. package/build/services/role/sql.js +51 -3
  23. package/build/services/role/types.d.ts +27 -0
  24. package/build/types/maintenance.d.ts +6 -1
  25. package/dashboard/dist/assets/{AdminDashboard-B-JptT54.js → AdminDashboard-5bWcBxyS.js} +2 -2
  26. package/dashboard/dist/assets/{AdminDashboard-B-JptT54.js.map → AdminDashboard-5bWcBxyS.js.map} +1 -1
  27. package/dashboard/dist/assets/{AgentConfigPage-B1Jc6iJk.js → AgentConfigPage-CVg_tFbf.js} +6 -6
  28. package/dashboard/dist/assets/{AgentConfigPage-B1Jc6iJk.js.map → AgentConfigPage-CVg_tFbf.js.map} +1 -1
  29. package/dashboard/dist/assets/{AgentDetailPage-CWpclCOY.js → AgentDetailPage-CLlkIoRQ.js} +4 -4
  30. package/dashboard/dist/assets/{AgentDetailPage-CWpclCOY.js.map → AgentDetailPage-CLlkIoRQ.js.map} +1 -1
  31. package/dashboard/dist/assets/{AgentsPage-DyAf5GA2.js → AgentsPage-CjruF_ek.js} +2 -2
  32. package/dashboard/dist/assets/{AgentsPage-DyAf5GA2.js.map → AgentsPage-CjruF_ek.js.map} +1 -1
  33. package/dashboard/dist/assets/AvailableEscalationsPage-h6awiuVk.js +2 -0
  34. package/dashboard/dist/assets/AvailableEscalationsPage-h6awiuVk.js.map +1 -0
  35. package/dashboard/dist/assets/{BotPicker-B1RdaNuY.js → BotPicker-CGEFHHDE.js} +2 -2
  36. package/dashboard/dist/assets/{BotPicker-B1RdaNuY.js.map → BotPicker-CGEFHHDE.js.map} +1 -1
  37. package/dashboard/dist/assets/{CapabilitiesPage-CxHDnyjX.js → CapabilitiesPage-BI1tNYun.js} +2 -2
  38. package/dashboard/dist/assets/{CapabilitiesPage-CxHDnyjX.js.map → CapabilitiesPage-BI1tNYun.js.map} +1 -1
  39. package/dashboard/dist/assets/{CollapsibleSection-Ds0e8cCO.js → CollapsibleSection-B9f52Z2K.js} +2 -2
  40. package/dashboard/dist/assets/{CollapsibleSection-Ds0e8cCO.js.map → CollapsibleSection-B9f52Z2K.js.map} +1 -1
  41. package/dashboard/dist/assets/{CopyableId-blg-gZvT.js → CopyableId-uot2mE_g.js} +2 -2
  42. package/dashboard/dist/assets/{CopyableId-blg-gZvT.js.map → CopyableId-uot2mE_g.js.map} +1 -1
  43. package/dashboard/dist/assets/CountdownTimer-C71erQ67.js +2 -0
  44. package/dashboard/dist/assets/CountdownTimer-C71erQ67.js.map +1 -0
  45. package/dashboard/dist/assets/{CredentialsPage-D8UVO-3A.js → CredentialsPage-smk5H_mP.js} +2 -2
  46. package/dashboard/dist/assets/{CredentialsPage-D8UVO-3A.js.map → CredentialsPage-smk5H_mP.js.map} +1 -1
  47. package/dashboard/dist/assets/{CronLabel-ZesYXxCH.js → CronLabel-CCJLmkIq.js} +2 -2
  48. package/dashboard/dist/assets/{CronLabel-ZesYXxCH.js.map → CronLabel-CCJLmkIq.js.map} +1 -1
  49. package/dashboard/dist/assets/CustomDurationPicker-DQ28g5GB.js +2 -0
  50. package/dashboard/dist/assets/CustomDurationPicker-DQ28g5GB.js.map +1 -0
  51. package/dashboard/dist/assets/DataTable-Byn3oyT9.js +2 -0
  52. package/dashboard/dist/assets/DataTable-Byn3oyT9.js.map +1 -0
  53. package/dashboard/dist/assets/{DropZone-SgiK1WOl.js → DropZone-C_Mc4UrO.js} +2 -2
  54. package/dashboard/dist/assets/{DropZone-SgiK1WOl.js.map → DropZone-C_Mc4UrO.js.map} +1 -1
  55. package/dashboard/dist/assets/{ElapsedCell-CO8S6vdb.js → ElapsedCell-Cw1uvJKx.js} +2 -2
  56. package/dashboard/dist/assets/{ElapsedCell-CO8S6vdb.js.map → ElapsedCell-Cw1uvJKx.js.map} +1 -1
  57. package/dashboard/dist/assets/EscalationListSchemaPage-3v2tiCnq.js +20 -0
  58. package/dashboard/dist/assets/EscalationListSchemaPage-3v2tiCnq.js.map +1 -0
  59. package/dashboard/dist/assets/EscalationSchemaPage-DIq2pMED.js +9 -0
  60. package/dashboard/dist/assets/EscalationSchemaPage-DIq2pMED.js.map +1 -0
  61. package/dashboard/dist/assets/{EscalationsOverview-DMpRvVql.js → EscalationsOverview-BqOZ1v8w.js} +2 -2
  62. package/dashboard/dist/assets/{EscalationsOverview-DMpRvVql.js.map → EscalationsOverview-BqOZ1v8w.js.map} +1 -1
  63. package/dashboard/dist/assets/EventTable-CyfHfOWj.js +2 -0
  64. package/dashboard/dist/assets/EventTable-CyfHfOWj.js.map +1 -0
  65. package/dashboard/dist/assets/{FilterBar-B4Mk2PvT.js → FilterBar-DDIn8TOs.js} +2 -2
  66. package/dashboard/dist/assets/{FilterBar-B4Mk2PvT.js.map → FilterBar-DDIn8TOs.js.map} +1 -1
  67. package/dashboard/dist/assets/{GraphInvokePage-CTuul-av.js → GraphInvokePage-DbNUaWhH.js} +2 -2
  68. package/dashboard/dist/assets/{GraphInvokePage-CTuul-av.js.map → GraphInvokePage-DbNUaWhH.js.map} +1 -1
  69. package/dashboard/dist/assets/{HomePage-BKFpOaHX.js → HomePage-Cj9sLt4Z.js} +2 -2
  70. package/dashboard/dist/assets/{HomePage-BKFpOaHX.js.map → HomePage-Cj9sLt4Z.js.map} +1 -1
  71. package/dashboard/dist/assets/{ListToolbar-Dz2_neVF.js → ListToolbar-D7PoQOZC.js} +2 -2
  72. package/dashboard/dist/assets/{ListToolbar-Dz2_neVF.js.map → ListToolbar-D7PoQOZC.js.map} +1 -1
  73. package/dashboard/dist/assets/{McpOverview-Do-vaEqV.js → McpOverview-D9g1iJyn.js} +2 -2
  74. package/dashboard/dist/assets/{McpOverview-Do-vaEqV.js.map → McpOverview-D9g1iJyn.js.map} +1 -1
  75. package/dashboard/dist/assets/{McpQueryDetailPage-HwDGNSbN.js → McpQueryDetailPage-DwS1pXdT.js} +2 -2
  76. package/dashboard/dist/assets/{McpQueryDetailPage-HwDGNSbN.js.map → McpQueryDetailPage-DwS1pXdT.js.map} +1 -1
  77. package/dashboard/dist/assets/{McpQueryPage-DxMUpQ7R.js → McpQueryPage-DZQ1z6-H.js} +2 -2
  78. package/dashboard/dist/assets/{McpQueryPage-DxMUpQ7R.js.map → McpQueryPage-DZQ1z6-H.js.map} +1 -1
  79. package/dashboard/dist/assets/McpRunDetailPage-Bx6w39lr.js +2 -0
  80. package/dashboard/dist/assets/{McpRunDetailPage-BzzZIXIk.js.map → McpRunDetailPage-Bx6w39lr.js.map} +1 -1
  81. package/dashboard/dist/assets/{McpRunsPage-D3J42d2i.js → McpRunsPage-C-oS_tTh.js} +2 -2
  82. package/dashboard/dist/assets/{McpRunsPage-D3J42d2i.js.map → McpRunsPage-C-oS_tTh.js.map} +1 -1
  83. package/dashboard/dist/assets/{NamespacePill-SErKwgkb.js → NamespacePill-DhgC3EUO.js} +2 -2
  84. package/dashboard/dist/assets/{NamespacePill-SErKwgkb.js.map → NamespacePill-DhgC3EUO.js.map} +1 -1
  85. package/dashboard/dist/assets/OperationsPage-CQzX6jM1.js +2 -0
  86. package/dashboard/dist/assets/OperationsPage-CQzX6jM1.js.map +1 -0
  87. package/dashboard/dist/assets/OperatorDashboard-Cqf87ojd.js +2 -0
  88. package/dashboard/dist/assets/{OperatorDashboard-BqJn8wEZ.js.map → OperatorDashboard-Cqf87ojd.js.map} +1 -1
  89. package/dashboard/dist/assets/PageHeader-sGNTmSXX.js +2 -0
  90. package/dashboard/dist/assets/PageHeader-sGNTmSXX.js.map +1 -0
  91. package/dashboard/dist/assets/{PageHeaderWithStats-W3akV6nB.js → PageHeaderWithStats-87STTAJX.js} +2 -2
  92. package/dashboard/dist/assets/{PageHeaderWithStats-W3akV6nB.js.map → PageHeaderWithStats-87STTAJX.js.map} +1 -1
  93. package/dashboard/dist/assets/{ProcessDetailPage-C8sT2xuE.js → ProcessDetailPage-CplnDXkz.js} +2 -2
  94. package/dashboard/dist/assets/{ProcessDetailPage-C8sT2xuE.js.map → ProcessDetailPage-CplnDXkz.js.map} +1 -1
  95. package/dashboard/dist/assets/{ProcessesListPage-eA8buUgd.js → ProcessesListPage-DFOJC27f.js} +2 -2
  96. package/dashboard/dist/assets/{ProcessesListPage-eA8buUgd.js.map → ProcessesListPage-DFOJC27f.js.map} +1 -1
  97. package/dashboard/dist/assets/RoleDetailPage-VrWOaPlg.js +8 -0
  98. package/dashboard/dist/assets/RoleDetailPage-VrWOaPlg.js.map +1 -0
  99. package/dashboard/dist/assets/{RolePill-DHWixsQR.js → RolePill-C91qbDOW.js} +2 -2
  100. package/dashboard/dist/assets/{RolePill-DHWixsQR.js.map → RolePill-C91qbDOW.js.map} +1 -1
  101. package/dashboard/dist/assets/{RolesPage-BQNQxsMY.js → RolesPage-DdI1rVAP.js} +2 -2
  102. package/dashboard/dist/assets/{RolesPage-BQNQxsMY.js.map → RolesPage-DdI1rVAP.js.map} +1 -1
  103. package/dashboard/dist/assets/{RunAsSelector-mhusrlYE.js → RunAsSelector-C6EozzKV.js} +2 -2
  104. package/dashboard/dist/assets/{RunAsSelector-mhusrlYE.js.map → RunAsSelector-C6EozzKV.js.map} +1 -1
  105. package/dashboard/dist/assets/SlidePanel-BpWveV1T.js +2 -0
  106. package/dashboard/dist/assets/SlidePanel-BpWveV1T.js.map +1 -0
  107. package/dashboard/dist/assets/{StatusBadge-DZ9137bI.js → StatusBadge-CEQMRmSQ.js} +2 -2
  108. package/dashboard/dist/assets/StatusBadge-CEQMRmSQ.js.map +1 -0
  109. package/dashboard/dist/assets/{StickyPagination-CqT9Q1I6.js → StickyPagination-BEgkYJ0-.js} +2 -2
  110. package/dashboard/dist/assets/{StickyPagination-CqT9Q1I6.js.map → StickyPagination-BEgkYJ0-.js.map} +1 -1
  111. package/dashboard/dist/assets/{StreamMessageDetail-jOJaoupp.js → StreamMessageDetail-DWVPtzj7.js} +2 -2
  112. package/dashboard/dist/assets/{StreamMessageDetail-jOJaoupp.js.map → StreamMessageDetail-DWVPtzj7.js.map} +1 -1
  113. package/dashboard/dist/assets/{SwimlaneTimeline-BrfmWgAj.js → SwimlaneTimeline-4NrDJNYc.js} +2 -2
  114. package/dashboard/dist/assets/{SwimlaneTimeline-BrfmWgAj.js.map → SwimlaneTimeline-4NrDJNYc.js.map} +1 -1
  115. package/dashboard/dist/assets/{TagInput-CGGC3DTR.js → TagInput-Csm36_pS.js} +2 -2
  116. package/dashboard/dist/assets/{TagInput-CGGC3DTR.js.map → TagInput-Csm36_pS.js.map} +1 -1
  117. package/dashboard/dist/assets/{TaskDetailPage-Cus_JHIC.js → TaskDetailPage-BvdaAUtF.js} +2 -2
  118. package/dashboard/dist/assets/{TaskDetailPage-Cus_JHIC.js.map → TaskDetailPage-BvdaAUtF.js.map} +1 -1
  119. package/dashboard/dist/assets/{TaskQueuePill-D6omQJNY.js → TaskQueuePill-5XdNaqwM.js} +2 -2
  120. package/dashboard/dist/assets/{TaskQueuePill-D6omQJNY.js.map → TaskQueuePill-5XdNaqwM.js.map} +1 -1
  121. package/dashboard/dist/assets/{TasksListPage-B7GK8AoX.js → TasksListPage-BxA161LH.js} +2 -2
  122. package/dashboard/dist/assets/{TasksListPage-B7GK8AoX.js.map → TasksListPage-BxA161LH.js.map} +1 -1
  123. package/dashboard/dist/assets/{TimeAgo-C606LzT3.js → TimeAgo-ChwJ4fjZ.js} +2 -2
  124. package/dashboard/dist/assets/{TimeAgo-C606LzT3.js.map → TimeAgo-ChwJ4fjZ.js.map} +1 -1
  125. package/dashboard/dist/assets/{TimestampCell-hfh6zvH-.js → TimestampCell-C7KzmvyC.js} +2 -2
  126. package/dashboard/dist/assets/{TimestampCell-hfh6zvH-.js.map → TimestampCell-C7KzmvyC.js.map} +1 -1
  127. package/dashboard/dist/assets/{ToolPill-BW0iRwZu.js → ToolPill-CAwj7EPw.js} +2 -2
  128. package/dashboard/dist/assets/{ToolPill-BW0iRwZu.js.map → ToolPill-CAwj7EPw.js.map} +1 -1
  129. package/dashboard/dist/assets/{ToolTestPanel-CsPsyvQq.js → ToolTestPanel-ISkuXtkT.js} +2 -2
  130. package/dashboard/dist/assets/{ToolTestPanel-CsPsyvQq.js.map → ToolTestPanel-ISkuXtkT.js.map} +1 -1
  131. package/dashboard/dist/assets/{TopicDetailPage-Diz6r7zr.js → TopicDetailPage-D8uZ8Wbs.js} +3 -3
  132. package/dashboard/dist/assets/{TopicDetailPage-Diz6r7zr.js.map → TopicDetailPage-D8uZ8Wbs.js.map} +1 -1
  133. package/dashboard/dist/assets/TopicsPage-rj88vA_1.js +2 -0
  134. package/dashboard/dist/assets/{TopicsPage-B6fn7fFh.js.map → TopicsPage-rj88vA_1.js.map} +1 -1
  135. package/dashboard/dist/assets/{UserName-Cc-5QH5q.js → UserName-D03Lh6Aj.js} +2 -2
  136. package/dashboard/dist/assets/{UserName-Cc-5QH5q.js.map → UserName-D03Lh6Aj.js.map} +1 -1
  137. package/dashboard/dist/assets/WorkflowExecutionPage-LD9WzPee.js +2 -0
  138. package/dashboard/dist/assets/WorkflowExecutionPage-LD9WzPee.js.map +1 -0
  139. package/dashboard/dist/assets/{WorkflowPill-BtNBSplz.js → WorkflowPill-fodIWnT3.js} +2 -2
  140. package/dashboard/dist/assets/{WorkflowPill-BtNBSplz.js.map → WorkflowPill-fodIWnT3.js.map} +1 -1
  141. package/dashboard/dist/assets/{WorkflowsDashboard-CHCM6hUa.js → WorkflowsDashboard-DH0jitAb.js} +2 -2
  142. package/dashboard/dist/assets/{WorkflowsDashboard-CHCM6hUa.js.map → WorkflowsDashboard-DH0jitAb.js.map} +1 -1
  143. package/dashboard/dist/assets/{WorkflowsOverview-WeOvl8-_.js → WorkflowsOverview-D6BVaH9e.js} +2 -2
  144. package/dashboard/dist/assets/{WorkflowsOverview-WeOvl8-_.js.map → WorkflowsOverview-D6BVaH9e.js.map} +1 -1
  145. package/dashboard/dist/assets/{YamlWorkflowDetailPage-6GfhVDpv.js → YamlWorkflowDetailPage-D_CfNEqW.js} +2 -2
  146. package/dashboard/dist/assets/{YamlWorkflowDetailPage-6GfhVDpv.js.map → YamlWorkflowDetailPage-D_CfNEqW.js.map} +1 -1
  147. package/dashboard/dist/assets/{YamlWorkflowsPage-DqcY3ocP.js → YamlWorkflowsPage-BMJgtcAq.js} +2 -2
  148. package/dashboard/dist/assets/{YamlWorkflowsPage-DqcY3ocP.js.map → YamlWorkflowsPage-BMJgtcAq.js.map} +1 -1
  149. package/dashboard/dist/assets/{agents-DlzvjNxE.js → agents-Ci0tZDO3.js} +2 -2
  150. package/dashboard/dist/assets/{agents-DlzvjNxE.js.map → agents-Ci0tZDO3.js.map} +1 -1
  151. package/dashboard/dist/assets/{bots-CWMN-KUI.js → bots-DZyt1oO4.js} +2 -2
  152. package/dashboard/dist/assets/{bots-CWMN-KUI.js.map → bots-DZyt1oO4.js.map} +1 -1
  153. package/dashboard/dist/assets/{capabilities-_WSZ7cxO.js → capabilities-DzVKeWyC.js} +2 -2
  154. package/dashboard/dist/assets/{capabilities-_WSZ7cxO.js.map → capabilities-DzVKeWyC.js.map} +1 -1
  155. package/dashboard/dist/assets/{controlplane-_k9nWZy3.js → controlplane-CQzrn-rD.js} +2 -2
  156. package/dashboard/dist/assets/{controlplane-_k9nWZy3.js.map → controlplane-CQzrn-rD.js.map} +1 -1
  157. package/dashboard/dist/assets/escalation-columns-s29Q4mrF.js +2 -0
  158. package/dashboard/dist/assets/{escalation-columns-Dyrf6Tdp.js.map → escalation-columns-s29Q4mrF.js.map} +1 -1
  159. package/dashboard/dist/assets/{index-kKufOPLo.js → index-3SrgIA7w.js} +2 -2
  160. package/dashboard/dist/assets/{index-kKufOPLo.js.map → index-3SrgIA7w.js.map} +1 -1
  161. package/dashboard/dist/assets/{index-Dd4Si1Rl.js → index-B2dB90aC.js} +2 -2
  162. package/dashboard/dist/assets/{index-Dd4Si1Rl.js.map → index-B2dB90aC.js.map} +1 -1
  163. package/dashboard/dist/assets/index-BSOcWfeR.js +5 -0
  164. package/dashboard/dist/assets/index-BSOcWfeR.js.map +1 -0
  165. package/dashboard/dist/assets/{index-uUYXmvFf.js → index-BWy4UvaT.js} +2 -2
  166. package/dashboard/dist/assets/{index-uUYXmvFf.js.map → index-BWy4UvaT.js.map} +1 -1
  167. package/dashboard/dist/assets/{index-C3h0qGzG.js → index-BqKxJ2fB.js} +2 -2
  168. package/dashboard/dist/assets/{index-C3h0qGzG.js.map → index-BqKxJ2fB.js.map} +1 -1
  169. package/dashboard/dist/assets/{index-DwWP-Z_c.js → index-C9moIiIq.js} +5 -5
  170. package/dashboard/dist/assets/{index-DwWP-Z_c.js.map → index-C9moIiIq.js.map} +1 -1
  171. package/dashboard/dist/assets/{index-Dc-Ye2wk.js → index-CcKXMU-s.js} +2 -2
  172. package/dashboard/dist/assets/{index-Dc-Ye2wk.js.map → index-CcKXMU-s.js.map} +1 -1
  173. package/dashboard/dist/assets/index-DIPe82gq.css +1 -0
  174. package/dashboard/dist/assets/{index-D8GcHxNJ.js → index-DOHGHMP7.js} +2 -2
  175. package/dashboard/dist/assets/{index-D8GcHxNJ.js.map → index-DOHGHMP7.js.map} +1 -1
  176. package/dashboard/dist/assets/{index-C5Yc3TT7.js → index-DTn4XzYN.js} +2 -2
  177. package/dashboard/dist/assets/{index-C5Yc3TT7.js.map → index-DTn4XzYN.js.map} +1 -1
  178. package/dashboard/dist/assets/index-Dv8mzG9B.js +2 -0
  179. package/dashboard/dist/assets/index-Dv8mzG9B.js.map +1 -0
  180. package/dashboard/dist/assets/{index-Ex5tEte8.js → index-LwodswlU.js} +2 -2
  181. package/dashboard/dist/assets/{index-Ex5tEte8.js.map → index-LwodswlU.js.map} +1 -1
  182. package/dashboard/dist/assets/{index-C7VXLKJs.js → index-kPipMHZx.js} +2 -2
  183. package/dashboard/dist/assets/{index-C7VXLKJs.js.map → index-kPipMHZx.js.map} +1 -1
  184. package/dashboard/dist/assets/{index-BiApvBEz.js → index-sdc9ecdW.js} +2 -2
  185. package/dashboard/dist/assets/{index-BiApvBEz.js.map → index-sdc9ecdW.js.map} +1 -1
  186. package/dashboard/dist/assets/{knowledge-fYoEF4Lx.js → knowledge-fi5z5YLq.js} +2 -2
  187. package/dashboard/dist/assets/{knowledge-fYoEF4Lx.js.map → knowledge-fi5z5YLq.js.map} +1 -1
  188. package/dashboard/dist/assets/{mcp-1lzlgtS8.js → mcp-CH8Tj9Pw.js} +2 -2
  189. package/dashboard/dist/assets/{mcp-1lzlgtS8.js.map → mcp-CH8Tj9Pw.js.map} +1 -1
  190. package/dashboard/dist/assets/{mcp-query-nlrAz7m_.js → mcp-query-6alwNh7K.js} +2 -2
  191. package/dashboard/dist/assets/{mcp-query-nlrAz7m_.js.map → mcp-query-6alwNh7K.js.map} +1 -1
  192. package/dashboard/dist/assets/{pipelines-Ihi7EKXi.js → pipelines-Dqh9PLUw.js} +2 -2
  193. package/dashboard/dist/assets/{pipelines-Ihi7EKXi.js.map → pipelines-Dqh9PLUw.js.map} +1 -1
  194. package/dashboard/dist/assets/roles-dJKBZUBh.js +2 -0
  195. package/dashboard/dist/assets/roles-dJKBZUBh.js.map +1 -0
  196. package/dashboard/dist/assets/{tasks-Crk9Y3C4.js → tasks-BYz490un.js} +2 -2
  197. package/dashboard/dist/assets/{tasks-Crk9Y3C4.js.map → tasks-BYz490un.js.map} +1 -1
  198. package/dashboard/dist/assets/{topics-CtfcCyw9.js → topics-Dt975EWi.js} +2 -2
  199. package/dashboard/dist/assets/{topics-CtfcCyw9.js.map → topics-Dt975EWi.js.map} +1 -1
  200. package/dashboard/dist/assets/{useEventHooks-Dn_vN2xE.js → useEventHooks-ZT6RV2Gj.js} +2 -2
  201. package/dashboard/dist/assets/{useEventHooks-Dn_vN2xE.js.map → useEventHooks-ZT6RV2Gj.js.map} +1 -1
  202. package/dashboard/dist/assets/{useNamespace-Cfs3vKWk.js → useNamespace-B_G1iE9i.js} +2 -2
  203. package/dashboard/dist/assets/{useNamespace-Cfs3vKWk.js.map → useNamespace-B_G1iE9i.js.map} +1 -1
  204. package/dashboard/dist/assets/{useYamlActivityEvents-ymobxH47.js → useYamlActivityEvents-DGkvIadL.js} +2 -2
  205. package/dashboard/dist/assets/{useYamlActivityEvents-ymobxH47.js.map → useYamlActivityEvents-DGkvIadL.js.map} +1 -1
  206. package/dashboard/dist/assets/{users-DngGIiC_.js → users-C2jopvM1.js} +2 -2
  207. package/dashboard/dist/assets/{users-DngGIiC_.js.map → users-C2jopvM1.js.map} +1 -1
  208. package/dashboard/dist/assets/{vendor-icons-DOIDKgo6.js → vendor-icons-BXhWQC7l.js} +161 -121
  209. package/dashboard/dist/assets/vendor-icons-BXhWQC7l.js.map +1 -0
  210. package/dashboard/dist/assets/{workflows-DuUspMAl.js → workflows-By8Wgvig.js} +2 -2
  211. package/dashboard/dist/assets/{workflows-DuUspMAl.js.map → workflows-By8Wgvig.js.map} +1 -1
  212. package/dashboard/dist/assets/{yaml-workflows-DXQCiOsP.js → yaml-workflows-Cq_vp41d.js} +2 -2
  213. package/dashboard/dist/assets/{yaml-workflows-DXQCiOsP.js.map → yaml-workflows-Cq_vp41d.js.map} +1 -1
  214. package/dashboard/dist/index.html +3 -3
  215. package/docs/api/http/escalations.md +20 -0
  216. package/docs/api/sdk/escalations.md +37 -2
  217. package/docs/dashboard.md +16 -3
  218. package/docs/data.md +1 -1
  219. package/docs/hitl-guide.md +240 -21
  220. package/docs/maintenance.md +6 -3
  221. package/docs/oauth-and-delegation.md +1 -1
  222. package/package.json +3 -3
  223. package/dashboard/dist/assets/AvailableEscalationsPage-DDwAgklw.js +0 -2
  224. package/dashboard/dist/assets/AvailableEscalationsPage-DDwAgklw.js.map +0 -1
  225. package/dashboard/dist/assets/CustomDurationPicker-OKlwH5a_.js +0 -2
  226. package/dashboard/dist/assets/CustomDurationPicker-OKlwH5a_.js.map +0 -1
  227. package/dashboard/dist/assets/DataTable-ZWgVQg7F.js +0 -2
  228. package/dashboard/dist/assets/DataTable-ZWgVQg7F.js.map +0 -1
  229. package/dashboard/dist/assets/EscalationSchemaPage-Br5sD8-N.js +0 -9
  230. package/dashboard/dist/assets/EscalationSchemaPage-Br5sD8-N.js.map +0 -1
  231. package/dashboard/dist/assets/EventTable-BARJeiS2.js +0 -2
  232. package/dashboard/dist/assets/EventTable-BARJeiS2.js.map +0 -1
  233. package/dashboard/dist/assets/McpRunDetailPage-BzzZIXIk.js +0 -2
  234. package/dashboard/dist/assets/OperationsPage-BaM-QDhy.js +0 -2
  235. package/dashboard/dist/assets/OperationsPage-BaM-QDhy.js.map +0 -1
  236. package/dashboard/dist/assets/OperatorDashboard-BqJn8wEZ.js +0 -2
  237. package/dashboard/dist/assets/PageHeader-DJo7yMUR.js +0 -2
  238. package/dashboard/dist/assets/PageHeader-DJo7yMUR.js.map +0 -1
  239. package/dashboard/dist/assets/RoleDetailPage-aR-ror5v.js +0 -11
  240. package/dashboard/dist/assets/RoleDetailPage-aR-ror5v.js.map +0 -1
  241. package/dashboard/dist/assets/StatusBadge-DZ9137bI.js.map +0 -1
  242. package/dashboard/dist/assets/TopicsPage-B6fn7fFh.js +0 -2
  243. package/dashboard/dist/assets/WorkflowExecutionPage-BFq7UdhA.js +0 -2
  244. package/dashboard/dist/assets/WorkflowExecutionPage-BFq7UdhA.js.map +0 -1
  245. package/dashboard/dist/assets/escalation-CK8ElYzD.js +0 -2
  246. package/dashboard/dist/assets/escalation-CK8ElYzD.js.map +0 -1
  247. package/dashboard/dist/assets/escalation-columns-Dyrf6Tdp.js +0 -2
  248. package/dashboard/dist/assets/index-BdqKh0UK.css +0 -1
  249. package/dashboard/dist/assets/index-OV1JoB_P.js +0 -6
  250. package/dashboard/dist/assets/index-OV1JoB_P.js.map +0 -1
  251. package/dashboard/dist/assets/index-ZBj6Xgim.js +0 -2
  252. package/dashboard/dist/assets/index-ZBj6Xgim.js.map +0 -1
  253. package/dashboard/dist/assets/roles-D4Wn7kE9.js +0 -2
  254. package/dashboard/dist/assets/roles-D4Wn7kE9.js.map +0 -1
  255. package/dashboard/dist/assets/useCollapsedSections-BU5HULGs.js +0 -2
  256. package/dashboard/dist/assets/useCollapsedSections-BU5HULGs.js.map +0 -1
  257. package/dashboard/dist/assets/vendor-icons-DOIDKgo6.js.map +0 -1
@@ -387,7 +387,7 @@ export async function stationWorker(envelope: LTEnvelope) {
387
387
  workflowType: 'stationWorker',
388
388
  metadata: { orderId: envelope.data.orderId, station: 'qc' },
389
389
  envelope: { instructions: 'Review and approve or reject' },
390
- timeout: '24h', // SLA for this worklist row (hotmesh 0.25.1+)
390
+ timeout: '24h', // SLA for this worklist row
391
391
  });
392
392
 
393
393
  if (decision === false) {
@@ -405,7 +405,7 @@ export async function stationWorker(envelope: LTEnvelope) {
405
405
 
406
406
  ### SLA-gated waits (`timeout`)
407
407
 
408
- `ConditionQueueConfig.timeout` (hotmesh 0.25.1+) arms the same resume timer as
408
+ `ConditionQueueConfig.timeout` arms the same resume timer as
409
409
  `condition(signalId, '24h')` **in the same single Leg1 write** that creates the
410
410
  escalation row. The race is resolved atomically on both sides: a signal that
411
411
  arrives first resolves the row normally and the timer is inert; a timer that
@@ -823,6 +823,41 @@ const result = await lt.escalations.resolveByIds({
823
823
 
824
824
  **Returns:** `LTApiResult<{ resolved: number; escalationIds: string[] }>` — only still-`pending` rows are resolved.
825
825
 
826
+ ## resolveAllOrNone
827
+
828
+ Atomic bulk resolve with per-row payloads: every listed escalation resolves with its own `resolverPayload` in one SQL statement, or nothing resolves. Rows backing a live `condition()` waiter are first-class — each waiter's wake commits with its resolve, delivering that row's payload as the condition's return value (the same wake contract as `resolve`). Use it for gang handoffs where each member must receive a distinct mandate and a partial batch is unacceptable.
829
+
830
+ RBAC matches `resolveByIds`: per-item write scope, and any missing or out-of-scope id returns 404 with nothing resolved. `requireClaimed` additionally asserts — inside the same guarded statement — that every row is currently assigned to the caller, closing the window where another principal re-claims a member between the caller's claim and the resolve.
831
+
832
+ ```typescript
833
+ const result = await lt.escalations.resolveAllOrNone({
834
+ items: [
835
+ { id: 'esc_left', resolverPayload: { gcodeRef: 'gcode-left', unit: 'left' } },
836
+ { id: 'esc_right', resolverPayload: { gcodeRef: 'gcode-right', unit: 'right' } },
837
+ ],
838
+ metadata: { outcome: 'gang-dispatched' },
839
+ requireClaimed: true,
840
+ });
841
+
842
+ if (result.status === 409) {
843
+ // nothing resolved — result.data.failed names exactly the blocking rows
844
+ // (not-found | already-resolved | already-cancelled | already-expired |
845
+ // assignee-mismatch | unsupported-resolution-path); re-gang around them
846
+ }
847
+ ```
848
+
849
+ | Field | Type | Required | Description |
850
+ |-------|------|----------|-------------|
851
+ | `items` | `Array<{ id, resolverPayload }>` | Yes | The batch — each row resolves with its own payload. Ids must be unique; max 100 items (`LT_ESCALATION_BULK_RESOLVE_MAX`) |
852
+ | `metadata` | `Record<string, any>` | No | Shared outcome patch merged into every row's GIN-indexed metadata |
853
+ | `requireClaimed` | `boolean` | No | Assert every row is currently assigned to the caller, inside the atomic statement |
854
+
855
+ **Returns:** `LTApiResult<{ resolved: number; escalationIds: string[] }>` on success. On 409 the data carries `{ error, failedIds, failed: [{ id, reason }] }` — only the rows that blocked the batch are listed; resolvable members stay pending, untouched.
856
+
857
+ Rows that resolve through legacy signal routing (`metadata.signal_id` / `metadata.signal_routing`) require the single `resolve` path, which delivers their workflow signal; including one blocks the batch with reason `unsupported-resolution-path`. Password-format fields are redacted per row against that row's own `form_schema` before the payload enters the signal store.
858
+
859
+ **Auth:** Required
860
+
826
861
  ## resolveBySignalKey
827
862
 
828
863
  Resolve an efficient (atomic) escalation directly by its `signal_key` and resume the waiting workflow in place. For callers that know the deterministic signal id and want to skip the id lookup. RBAC-scoped to the escalation's role.
package/docs/dashboard.md CHANGED
@@ -6,7 +6,7 @@ The Long Tail dashboard is a React single-page application for managing procedur
6
6
 
7
7
  The sidebar organizes pages into six groups.
8
8
 
9
- ### React
9
+ ### Monitor
10
10
 
11
11
  The reactive, event-driven surface — where operations watch the floor and builders configure choreography.
12
12
 
@@ -196,7 +196,9 @@ Lists all procedural workflow runs across the system.
196
196
 
197
197
  - **Tier filter** (top) — switch between All, Certified, and Durable to focus on specific workflow types.
198
198
  - **Columns:** Workflow name, workflow ID, status (running/completed/failed), start time, and duration.
199
- - **Click any row** to see the full execution detail: task record with milestones, activity checkpoints, resolver payloads, and any associated escalations.
199
+ - **Click any row** to see the full execution detail: the swimlane timeline and events fill the main column, and a full-height side panel carries the record's facts. The main header stays quiet — just the title and a panel toggle; status, the refresh/copy toolbar, and the **Actions** menu (a small caret anchor at the right of the panel's icon row) all live in the panel. The panel and main column share the width as a flex set — the main column narrows as the panel expands.
200
+ - **Details** — status, workflow identity (type, ID, parent), task queue, start/end times, duration, history size, and activity counts.
201
+ - **Escalations** — every escalation the workflow raised, each a row with type, role, age, and status badge, so multiple escalations across the run's lifecycle read as a table rather than running on; related child tasks list the same way below.
200
202
  - **Duration** is computed from start to completion — useful for identifying slow workflows or comparing performance across versions.
201
203
 
202
204
  **API:** `GET /api/workflows/executions` lists runs with tier, status, and pagination filters.
@@ -265,6 +267,15 @@ Accessible at `/admin/roles/:role/schema`. The versioned form behind a role's es
265
267
 
266
268
  **API:** `GET /api/roles/:role/schema` fetches the latest or a pinned version. `GET /api/roles/:role/schema/versions` lists the history. `PATCH /api/roles/:role` with `form_schema` (+ optional `change_summary`) saves a new version.
267
269
 
270
+ ### Escalations List Schema
271
+
272
+ Accessible at `/admin/roles/:role/list-schema`. The versioned rich view for the role's escalation **list** page — the list-page analog of the resolve form. Opt-in: when a role owns a `list_schema`, its list page renders a role-authored view (the live item as a card plus a load-on-demand history) instead of the engineer table, scoped to that one role. See the [HITL guide](hitl-guide.md#escalations-list-schema) for the `x-lt-layout` / `x-lt-active` / `x-lt-history` vocabulary.
273
+
274
+ - **Editor + version rail** — same shape as the Escalation Schema editor, but on its own **independent** version timeline: editing the list view never advances the resolve form's version.
275
+ - The list always renders the latest version — no pinning (a display template, not a payload contract).
276
+
277
+ **API:** `GET /api/roles/:role/list-schema` (latest or `?version=`), `GET /api/roles/:role/list-schema/versions`, and `PATCH /api/roles/:role` with `list_schema` (+ optional `change_summary`).
278
+
268
279
  ### DB Maintenance
269
280
 
270
281
  Database housekeeping tools for keeping PostgreSQL healthy under sustained workflow load.
@@ -294,7 +305,7 @@ Browse individual stream messages from the Postgres engine and worker stream tab
294
305
 
295
306
  - **Namespace & Source** — required filters. Engine streams carry internal orchestration messages; worker streams carry task messages with workflow metadata (job ID, activity, message type).
296
307
  - **Filters** — narrow by status (pending, claimed, processed, dead-lettered) and stream name (partial match).
297
- - **Message detail** — click any row to open the inspector panel. Shows timestamps, retry info, worker metadata, and the full JSON payload with expandable tree view.
308
+ - **Message detail** — click any row to slide open the inspector panel: a full-height column beside the table with its own scroll, like the left nav. The table narrows to make room, so no cells are covered. Shows timestamps, retry info, worker metadata, and the full JSON payload with expandable tree view.
298
309
  - **Pagination & sorting** — standard controls. Sort by created time, stream name, or priority.
299
310
 
300
311
  Messages are read-only. Status is derived from timestamps: pending (no timestamps set), claimed (reserved), processed (expired), or dead-lettered.
@@ -307,9 +318,11 @@ The central queue for all escalation activity across every workflow.
307
318
 
308
319
  - **Filter bar** — filter by status (pending/claimed/resolved), role, workflow type, priority, and time window.
309
320
  - **Columns:** Escalation ID, workflow type, role, status, priority, created time, and claimed-by user.
321
+ - **Rich list view** — when the list is scoped to exactly one role (`?role=<role>`) and that role owns a [list schema](#escalations-list-schema), a role-authored view renders in place of the table (the live item as a card, plus a load-on-demand history), with a **Table view** toggle back to the columns.
310
322
  - **Claim** — click the claim action to lock an escalation to your user. Only users with matching roles see pending escalations. The queue list and aggregate stats reflect `read_all` memberships — a member scoped to `read_self` lands directly on their own assigned item in user mode rather than browsing the full queue.
311
323
  - **Resolve** — after claiming, submit a resolver payload. The form is pre-filled from the role's versioned `form_schema` field defaults and from the workflow's seeded `envelope.formDefaults` (reverse-mapped through each field's `x-lt-bind`). The dashboard maps the flat form to the nested payload via `x-lt-bind` and the submitted payload is stored as-is. Resolution triggers a workflow re-run with the resolver data injected. A `member` whose `write_scope` is `self` can resolve only items already assigned to them; `write_scope=none` is read-only.
312
324
  - **Escalate** — forward a claimed escalation to a higher-tier role via the escalation chain.
325
+ - **Side panel** — a slide-in beside the resolve form with switchable views, selected by the icon set at its top: **Help** (the form's `x-lt-help` markdown, `{{domain.path}}`-interpolated against the live record, or a state-aware hint such as "Claim this escalation to enable the form"), **Details** (status, role, priority, claim provenance, timestamps, and — for builders — identifier links), **AI Analysis** (what triage diagnosed and corrected — shown when AI is enabled and triage data is present), **Metadata** (the row's metadata values), **Context** (input envelope, escalation context, resolver payload), and **Record** (the raw escalation JSON, builders only). The panel and form share the width as a flex set — the form column narrows as the panel expands. It opens expanded on Help when the form carries `x-lt-help`, stays hidden otherwise, and the page-header panel button toggles it either way.
313
326
 
314
327
  **API:** `GET /api/escalations` lists with filters. `POST /api/escalations/:id/claim` claims. `POST /api/escalations/:id/resolve` resolves.
315
328
 
package/docs/data.md CHANGED
@@ -104,7 +104,7 @@ Records human intervention requests. Created when a workflow returns
104
104
  workflow calls `condition(signalId, config)` / `conditionLT(signalId, config)`.
105
105
  Updated when claimed or resolved.
106
106
 
107
- Since v0.5.3 the storage is the **shared HotMesh table `public.hmsh_escalations`**
107
+ The storage is the **shared HotMesh table `public.hmsh_escalations`**
108
108
  (written by both long-tail and the HotMesh SDK). `lt_escalations` remains as a
109
109
  backward-compatible **view** over it — `SELECT *` plus a computed `available`
110
110
  column — so existing read queries and the public API are unchanged. Indexes are
@@ -83,9 +83,24 @@ export async function approvalWorkflow(envelope: LTEnvelope) {
83
83
  }
84
84
  ```
85
85
 
86
- The `timeout` field (hotmesh 0.25.1+) makes the wait SLA-gated in the same
87
- single Leg1 write: one `conditionLT` call yields the worklist row AND the
88
- resume timer. Omit it for an open-ended wait.
86
+ The `timeout` field makes the wait SLA-gated in the same single Leg1 write:
87
+ one `conditionLT` call yields the worklist row AND the resume timer. Omit it
88
+ for an open-ended wait.
89
+
90
+ Two engine contracts worth building on:
91
+
92
+ - **The row is complete from its first visible moment.** Every field of the
93
+ config — including `metadata` facets — commits inside the Leg1 checkpoint.
94
+ A claim-by-metadata router or a version-pinned facet (e.g. a
95
+ `schema_version` the resolver UI renders) can trust every row it reads;
96
+ there is no window where a row is visible but its metadata is still en route.
97
+ - **Early signals are buffered.** A resolve that races ahead of the
98
+ `condition()` registration (a fast webhook, a payload deposited before the
99
+ workflow starts) is held as a pending signal and delivered when the wait
100
+ registers — 10 minutes by default; pass `expire` to `signal()` (e.g. `'1h'`)
101
+ when signaling early on purpose. Fan-out (`Promise.all` over many waits)
102
+ scales the same way: buffering covers every signal that outruns its
103
+ registration.
89
104
 
90
105
  #### Two-step form
91
106
 
@@ -209,12 +224,48 @@ await lt.escalations.resolve({
209
224
 
210
225
  The patch is distinct from the resolver payload: the payload resumes the paused workflow and is not indexed; the metadata patch is the durable, queryable record on the row. Use it for the audit trail and analytics — disposition, reviewer, time-to-resolve — so the escalation table answers *what was asked, what was decided, and how long it took* without a parallel log.
211
226
 
227
+ ### Resolving a set atomically
228
+
229
+ When one decision settles a SET of waits — each with its own payload — use
230
+ `lt.escalations.resolveAllOrNone({ items })` (`POST /api/escalations/resolve-all-or-none`).
231
+ Every listed row resolves with its own `resolverPayload` in one SQL statement,
232
+ waking each parked workflow with its own value, or nothing resolves and the
233
+ 409 body names exactly the rows that blocked (`failedIds` + reasons). Pass
234
+ `requireClaimed: true` in claim-then-resolve flows to assert, inside the same
235
+ statement, that every row is still assigned to the caller. See the
236
+ [SDK reference](./api/sdk/escalations.md#resolveallornone) for the full contract.
237
+
212
238
  ---
213
239
 
214
240
  ## JSON Schema Form Authoring
215
241
 
216
242
  The dashboard renders forms automatically from JSON Schema. No frontend code needed.
217
243
 
244
+ The full custom vocabulary at a glance — every `x-lt-*` keyword and extension key the renderer honors:
245
+
246
+ | Keyword | Level | Purpose |
247
+ |---------|-------|---------|
248
+ | `x-lt-widget` | field | Rich control: `file-upload`, `code-editor`, `signature`, `rich-text`, `markdown` |
249
+ | `x-lt-language` | field | Syntax hint shown by the `code-editor` widget |
250
+ | `accept` | field | File-type filter for the `file-upload` widget (e.g. `".pdf,.png"`) |
251
+ | `x-lt-bind` | field | Path this field's value occupies in the resolver payload (e.g. `"customer.email"`) |
252
+ | `x-lt-span` | field | Column span in a `two-column` layout (`2` = full width) |
253
+ | `x-lt-order` | schema | Field render sequence |
254
+ | `x-lt-layout` | schema | `"two-column"` grid layout |
255
+ | `x-lt-help` | schema | Markdown guidance for the side panel's Help view; `{{domain.path}}` tokens interpolate live record values |
256
+ | `x-lt-context` | schema | Plain-text fallback for the Help view when `x-lt-help` is absent |
257
+ | `x-lt-viewport` | schema | Replace the generated form with a custom iframe UI |
258
+ | `format` | field | Input specialization: `password`, `date`, `date-time`, `email`, `uri`, `textarea` |
259
+ | `readOnly` | field | Static display (or a rendered content block with the `markdown` widget) |
260
+ | `required` | schema | Fields that must be filled before submit |
261
+ | `title` / `description` | both | Section header / helper text |
262
+
263
+ The working reference is `examples/workflows/rich-form/` — the `intake-reviewer` role's
264
+ versioned `form_schema` (seeded by `examples/seed-rich-form.ts`) exercises the whole
265
+ vocabulary in one form: side-panel help (`x-lt-help` with a checklist, table, tokens,
266
+ and a relative link), two-column layout, ordering, date and email formats, enum, file
267
+ upload, spans, required fields, and `x-lt-bind` mapping into a nested payload.
268
+
218
269
  ### Supported Field Types
219
270
 
220
271
  | JSON Type | Renders As |
@@ -260,6 +311,7 @@ For rich inputs beyond standard HTML types:
260
311
  | `"code-editor"` | Monospace textarea with tab-key support. Use `x-lt-language` for syntax hint. |
261
312
  | `"signature"` | HTML5 Canvas drawing pad. Outputs PNG data URL. |
262
313
  | `"rich-text"` | Tall textarea for formatted text input. |
314
+ | `"markdown"` | Markdown source, rendered with the same engine as the docs drawer (headings, tables, lists, code blocks, callouts). Editable fields get a Write/Preview toggle; with `readOnly: true` the field is a pure content block — see below. |
263
315
 
264
316
  ```json
265
317
  {
@@ -285,6 +337,101 @@ For rich inputs beyond standard HTML types:
285
337
  }
286
338
  ```
287
339
 
340
+ #### Markdown content blocks
341
+
342
+ `readOnly: true` + `x-lt-widget: "markdown"` turns a field into a rendered content
343
+ block: the markdown in its `default` displays as HTML inside the form — headings,
344
+ tables, checklists, callouts. The versioned schema carries the page source itself,
345
+ so review instructions and SOPs version with the form they belong to, and the
346
+ source rides along in the resolver payload like any read-only field.
347
+
348
+ ```json
349
+ {
350
+ "properties": {
351
+ "review_guide": {
352
+ "type": "string",
353
+ "readOnly": true,
354
+ "x-lt-widget": "markdown",
355
+ "x-lt-span": 2,
356
+ "default": "### Review checklist\n\n1. Confirm the **legal name** matches.\n2. Send a test message before approving.\n\n> Escalate non-standard contract language to legal."
357
+ }
358
+ }
359
+ }
360
+ ```
361
+
362
+ Without `readOnly`, the field is a markdown *editor* — the resolver writes source in
363
+ a Write/Preview toggle and the submitted value is the markdown text.
364
+
365
+ ### Help Panel (`x-lt-help`)
366
+
367
+ Schema-level `x-lt-help` carries the form's guidance — checklists, tier tables,
368
+ callouts, links — as markdown. The dashboard renders it in the side panel beside the
369
+ form, so the form itself stays a clean title and fields while the SOP sits one glance
370
+ to the right. The help versions with the form: it lives in the same `form_schema`
371
+ snapshot in `lt_role_schemas`.
372
+
373
+ ```json
374
+ {
375
+ "title": "Customer Intake",
376
+ "x-lt-help": "### Review checklist\n\n1. Confirm the **legal name** matches.\n2. Send a test message before approving.\n\nThis escalation is **{{escalation.status}}** in the **{{escalation.role}}** queue.\n\n[Back to the queue](/escalations/queue?role=intake-reviewer)",
377
+ "properties": { ... }
378
+ }
379
+ ```
380
+
381
+ `{{domain.path}}` tokens interpolate live values from the escalation surface using the
382
+ `x-lt-bind` path syntax (dot keys, optional `[n]` indices). Five domains are available:
383
+
384
+ | Domain | Resolves against |
385
+ |--------|------------------|
386
+ | `escalation` | The escalation row (`{{escalation.role}}`, `{{escalation.status}}`) |
387
+ | `metadata` | The row's metadata dict (`{{metadata.schema_version}}`) |
388
+ | `envelope` | The workflow-sent input envelope (`{{envelope.formDefaults.customer.name}}`) |
389
+ | `payload` | The escalation context payload (`{{payload.category}}`) |
390
+ | `resolver` | The submitted resolver payload (`{{resolver.notes}}`) |
391
+
392
+ A missing value renders as an em dash. Links whose href starts with `/` navigate
393
+ inside the dashboard.
394
+
395
+ The Help view falls back in order: `x-lt-help` → `x-lt-context` (plain text) → a
396
+ state-aware hint ("Claim this escalation to enable the form", "Fill out the form and
397
+ submit to resolve it", and so on), so the panel always tells the resolver what the
398
+ page expects of them. The panel's other views surface the record itself: **Metadata**
399
+ (the row's metadata values), **Context** (input envelope, escalation context, resolver
400
+ payload), and **Record** (the raw escalation JSON, builders only).
401
+
402
+ ### Payload Binding (`x-lt-bind`)
403
+
404
+ The form is flat; the payload the workflow consumes rarely is. A field may declare
405
+ `x-lt-bind` — the path its value occupies in the resolver payload (dot keys, optional
406
+ `[n]` indices). The dashboard maps the flat form through the binds on submit, and
407
+ reverse-maps workflow-seeded `envelope.formDefaults` through them to prefill. A field
408
+ with no bind lands at its own name at the payload root (1:1).
409
+
410
+ ```json
411
+ {
412
+ "properties": {
413
+ "customer_name": { "type": "string", "x-lt-bind": "customer.name" },
414
+ "contact_email": { "type": "string", "format": "email", "x-lt-bind": "customer.email" },
415
+ "tier": { "type": "string", "enum": ["starter", "professional"], "x-lt-bind": "contract.tier" },
416
+ "notes": { "type": "string", "format": "textarea" }
417
+ }
418
+ }
419
+ ```
420
+
421
+ Submitting `{ customer_name, contact_email, tier, notes }` stores:
422
+
423
+ ```json
424
+ {
425
+ "customer": { "name": "…", "email": "…" },
426
+ "contract": { "tier": "…" },
427
+ "notes": "…"
428
+ }
429
+ ```
430
+
431
+ Only the FORM is versioned on the role — the payload shape is the workflow's own
432
+ contract, produced by the binds. Evolve the form and its binds together, and the
433
+ workflow's resolver type in the same commit.
434
+
288
435
  ### Layout Options (`x-lt-layout`)
289
436
 
290
437
  Control how fields are arranged:
@@ -349,8 +496,9 @@ Fields with `readOnly: true` display as static text. Useful for showing context
349
496
  ### Schema Title and Description
350
497
 
351
498
  The `title` and `description` at the schema root are used in the UI:
352
- - **`title`**: Shown as the section header (replaces "Submit Your Resolution" in user mode)
353
- - **`description`** or **`x-lt-context`**: In user mode, displayed as a context panel alongside the form in a two-panel layout
499
+ - **`title`**: Shown as the form's section header
500
+ - **`description`**: Shown as helper text beneath the title — keep it to a short phrase; longer guidance belongs in `x-lt-help`
501
+ - **`x-lt-help`**: Rendered as markdown in the side panel beside the form
354
502
 
355
503
  ```json
356
504
  {
@@ -469,27 +617,36 @@ Communication happens via `window.postMessage`.
469
617
 
470
618
  ---
471
619
 
472
- ## Dev Mode vs User Mode
620
+ ## The Escalation Detail Page
473
621
 
474
- The escalation detail page has two viewing modes:
622
+ The escalation detail page has one view, built for the person resolving the item: the
623
+ escalation's description is the page title, the form starts directly beneath it, and
624
+ the action bar closes the page. The lifecycle sparkline (waiting / claimed / resolved
625
+ ratios) sits as a short persistent row above the side panel. Everything else lives in
626
+ the side panel, ordered by specificity:
475
627
 
476
- | Aspect | Dev Mode | User Mode |
477
- |--------|----------|-----------|
478
- | **Default for** | Admins, superadmins, engineers | All other roles |
479
- | **Shows** | Everything | Title, description, form, action bar |
480
- | **Hides** | Nothing | Technical IDs, raw JSON, envelope data, AI triage, raw JSON editor |
481
- | **Persistence** | sessionStorage (per browser session) | sessionStorage |
628
+ | View | Shows | Available to |
629
+ |------|-------|--------------|
630
+ | **Help** | The form's `x-lt-help` markdown, or a state-aware hint | Everyone |
631
+ | **Details** | Status, role, priority, claim provenance, timestamps; identifier links below a divider | Everyone (identifiers: builders) |
632
+ | **AI Analysis** | What triage diagnosed and corrected | When AI is enabled and triage data is present |
633
+ | **Metadata** | The row's metadata values | Everyone |
634
+ | **Context** | Input envelope, escalation context, resolver payload | Everyone |
635
+ | **Record** | The raw escalation JSON | Builders (admins, superadmins, engineers) |
482
636
 
483
- **Key principle**: User mode only hides technical debugging information. All HITL workflow actions (claim, submit, escalate, release) are always visible in both modes.
637
+ The page is two fixed-height columns beneath the global toolbar the form column and
638
+ the panel each scroll independently, so the panel stays pinned like the left nav while
639
+ long forms or long panel content scroll. The form column narrows as the panel expands
640
+ (the panel is capped at half the page). When the form carries `x-lt-help`, the panel
641
+ opens expanded on the Help view; otherwise it stays hidden until the page-header panel
642
+ button summons it.
484
643
 
485
- Privileged users can toggle between modes via the switch in the page header.
644
+ ### Designing the Form
486
645
 
487
- ### Designing for User Mode
488
-
489
- To create a polished user mode experience:
646
+ To create a polished resolve experience:
490
647
 
491
648
  1. Set `title` on your schema — it replaces the section header
492
- 2. Set `description` or `x-lt-context` — it appears as a context panel in a two-panel layout
649
+ 2. Set `x-lt-help` — checklists, tables, and links render as markdown in the side panel, with `{{domain.path}}` tokens for live record values; keep `description` to a short subtitle
493
650
  3. Use `readOnly` fields for context the human needs to see but shouldn't edit
494
651
  4. Use `x-lt-order` to put the most important fields first
495
652
  5. Use `required` to guide users on what must be filled
@@ -497,6 +654,68 @@ To create a polished user mode experience:
497
654
 
498
655
  ---
499
656
 
657
+ ## Escalations List Schema
658
+
659
+ The form schema formats one escalation on the detail page. A role can also own a
660
+ `list_schema` that formats its whole **list** page — the list-page analog of the
661
+ resolve form. It is opt-in and applies only when the list is scoped to exactly one
662
+ role (`/escalations/available?role=<role>`). Absent, the list renders the standard
663
+ engineer table; present, a rich role-authored view renders with a "Table view" toggle
664
+ one click away. It is versioned **independently** of the form schema (its own timeline;
665
+ a list edit never bumps the form version) and edited on its own page,
666
+ `/admin/roles/:role/list-schema`. The list always renders the latest version.
667
+
668
+ This is what turns a queue like a `policy-document` role — where a looped workflow
669
+ keeps exactly one escalation live and each resolved one is a revision — into a document
670
+ with a history, instead of a one-row table.
671
+
672
+ ### Vocabulary
673
+
674
+ Every string is a markdown/text template run through the same `{{domain.path}}` token
675
+ binding as `x-lt-help` (domains `escalation | metadata | envelope | payload | resolver`,
676
+ evaluated against each row); `body` strings render through the markdown renderer.
677
+
678
+ | Key | Level | Purpose |
679
+ |-----|-------|---------|
680
+ | `x-lt-layout` | schema | `"active-history"` (two columns), `"active"` (card only), or `"table"` (fallback) |
681
+ | `x-lt-help` | schema | Optional markdown header, interpolated with the active row |
682
+ | `x-lt-active` | schema | The live item card: `{ title, subtitle?, body?, fields?: [{label, value}] }` |
683
+ | `x-lt-history` | schema | History column: `{ row: { title, subtitle?, meta? }, limit?, status? }` |
684
+
685
+ The **active** item is the first non-terminal escalation. The **history** column is not
686
+ auto-loaded — a "Load full history" link fetches resolved items on demand (`status`
687
+ defaults to `resolved`, `limit` to 25). Unknown/absent `x-lt-layout` is a safe no-op
688
+ that falls back to the table.
689
+
690
+ ### Example — a policy-document role
691
+
692
+ ```json
693
+ {
694
+ "x-lt-layout": "active-history",
695
+ "x-lt-help": "# {{metadata.title}}\nThe authoritative policy. One revision is live at a time.",
696
+ "x-lt-active": {
697
+ "title": "{{metadata.title}}",
698
+ "subtitle": "Revision {{metadata.revision}} · effective {{metadata.effective_date}}",
699
+ "body": "{{metadata.document_markdown}}",
700
+ "fields": [
701
+ { "label": "Owner", "value": "{{metadata.owner}}" },
702
+ { "label": "Claimed by", "value": "{{escalation.assigned_to}}" }
703
+ ]
704
+ },
705
+ "x-lt-history": {
706
+ "row": { "title": "{{metadata.title}} — revision {{metadata.revision}}" },
707
+ "limit": 25
708
+ }
709
+ }
710
+ ```
711
+
712
+ The working reference is `examples/workflows/policy-document/` (role seeded by
713
+ `examples/seed-policy-document.ts`): a looped workflow opens one policy-review
714
+ escalation, parks on it, and folds each resolution into the next revision — so the
715
+ policy facts ride the row's metadata and the list view reads them with `{{metadata.*}}`.
716
+
717
+ ---
718
+
500
719
  ## Role-Based Routing
501
720
 
502
721
  Escalations are routed by role. Users only see escalations for roles they hold.
@@ -782,10 +1001,10 @@ When you author a HITL-backed workflow, the platform handles:
782
1001
  - **Claim/release** — soft-lock with TTL, prevents duplicate work
783
1002
  - **Real-time updates** — NATS/Socket.IO events push changes to the dashboard instantly
784
1003
  - **Form rendering** — JSON Schema to rich form controls, no frontend code needed
785
- - **Dev/user mode** — technical vs clean views, per-session preference
1004
+ - **Side panel** — help, AI analysis, metadata, context, and raw-record views beside the form
786
1005
  - **Section state persistence** — collapsed sections remembered across navigation
787
1006
  - **Escalation chains** — users can re-route work to other roles
788
- - **AI triage** — optional auto-resolution for common patterns (dev mode)
1007
+ - **AI triage** — optional auto-resolution for common patterns
789
1008
  - **Signal routing** — 5 resolution paths (conditionLT, waitFor, triage, re-run, notification-only)
790
1009
  - **Credential security** — password fields use ephemeral tokens, never stored in plain text
791
1010
  - **Telemetry** — trace IDs link escalations to OpenTelemetry traces
@@ -28,6 +28,7 @@ await start({
28
28
  { target: 'jobs', olderThan: '14 days', action: 'delete', hasEntity: false },
29
29
  { target: 'jobs', olderThan: '14 days', action: 'prune', hasEntity: true },
30
30
  { target: 'jobs', olderThan: '180 days', action: 'delete', pruned: true },
31
+ { target: 'escalations', olderThan: '180 days', action: 'delete' },
31
32
  ],
32
33
  },
33
34
  });
@@ -42,14 +43,15 @@ await start({
42
43
 
43
44
  ## What gets cleaned
44
45
 
45
- Four categories of data are subject to maintenance:
46
+ Five categories of data are subject to maintenance:
46
47
 
47
48
  | Category | Description |
48
49
  |---|---|
49
- | **Streams** | Redis-style stream messages used internally by HotMesh to coordinate activities. These are pure infrastructure; no user-facing data resides here. |
50
+ | **Streams** | Stream messages used internally by HotMesh to coordinate activities. These are pure infrastructure; no user-facing data resides here. |
50
51
  | **Transient jobs** | Job rows where the `entity` column is `NULL`. These represent activity executions, signal deliveries, and other bookkeeping that is not tied to a named workflow entity. They serve no purpose after execution completes. |
51
52
  | **Entity jobs** | Job rows where `entity` is set. These represent actual workflow instances -- the rows that back `Durable.Client.workflow.search()` calls and execution exports. Deleting them removes the workflow record entirely. |
52
53
  | **Pruned jobs** | Entity jobs that have already had their execution artifacts stripped (the `pruned_at` column is not `NULL`). These retain core data but no longer carry execution scaffolding. They can be hard-deleted after a longer retention window. |
54
+ | **Terminal escalations** | Escalation rows in a terminal status (`resolved`, `cancelled`, `expired`). Terminal rows are inert audit records -- every engine state transition guards on `status = 'pending'`, so aging them out is safe for live waiters, claims, and signal delivery. The engine-owned prune deletes in atomic batches until the retention horizon is drained. Pending rows are never touched. |
53
55
 
54
56
  ## Prune vs. delete
55
57
 
@@ -93,11 +95,12 @@ Defined in `types/maintenance.ts`. Each rule describes a single cleanup operatio
93
95
 
94
96
  | Field | Type | Required | Description |
95
97
  |---|---|---|---|
96
- | `target` | `'streams' \| 'jobs'` | Yes | The resource type to act on. |
98
+ | `target` | `'streams' \| 'jobs' \| 'escalations'` | Yes | The resource type to act on. |
97
99
  | `action` | `'delete' \| 'prune'` | Yes | Whether to remove entirely or strip execution artifacts. |
98
100
  | `olderThan` | `string` | Yes | A PostgreSQL interval expression: `'7 days'`, `'24 hours'`, `'90 days'`. |
99
101
  | `hasEntity` | `boolean` | No | When `target` is `'jobs'`: `true` selects entity jobs, `false` selects transient jobs (where `entity IS NULL`). |
100
102
  | `pruned` | `boolean` | No | When `true`, only targets jobs where `pruned_at IS NOT NULL` -- jobs that have already been pruned. |
103
+ | `statuses` | `Array<'resolved' \| 'cancelled' \| 'expired'>` | No | When `target` is `'escalations'`: which terminal statuses to age out. Defaults to all three. |
101
104
 
102
105
  ### `LTMaintenanceConfig`
103
106
 
@@ -143,7 +143,7 @@ if (ctx) {
143
143
  }
144
144
  ```
145
145
 
146
- The MCP client (`callServerTool`) also reads `ToolContext` automatically. When no explicit `authContext` is passed, it derives `_auth` from the ambient context. This closes the identity gap for YAML workflow workers, which previously had no auth propagation.
146
+ The MCP client (`callServerTool`) also reads `ToolContext` automatically. When no explicit `authContext` is passed, it derives `_auth` from the ambient context YAML workflow workers get auth propagation this way without any explicit wiring.
147
147
 
148
148
  ## Delegation Tokens
149
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.8.4",
3
+ "version": "0.9.0",
4
4
  "description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "build": "tsc --build tsconfig.json && cp -r lib/db/schemas build/lib/db/schemas",
26
26
  "clean-build": "npm run clean && npm run build",
27
27
  "start": "ts-node index.ts",
28
- "start:dev": "ts-node-dev --respawn index.ts",
28
+ "start:dev": "ts-node-dev --respawn --transpile-only index.ts",
29
29
  "migrate": "ts-node lib/db/migrate.ts",
30
30
  "lint": "eslint . --ext .ts",
31
31
  "lint:fix": "eslint . --fix --ext .ts",
@@ -72,7 +72,7 @@
72
72
  "@anthropic-ai/sdk": "^0.92.0",
73
73
  "@aws-sdk/client-s3": "^3.1017.0",
74
74
  "@aws-sdk/s3-request-presigner": "^3.1045.0",
75
- "@hotmeshio/hotmesh": "^0.25.6",
75
+ "@hotmeshio/hotmesh": "^0.26.1",
76
76
  "@modelcontextprotocol/sdk": "^1.27.1",
77
77
  "@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
78
78
  "@opentelemetry/resources": "^2.5.1",
@@ -1,2 +0,0 @@
1
- import{j as e,a as p}from"./vendor-query-B2UbickB.js";import{c as Ie,f as $e,a as De}from"./vendor-react-CXumBFUA.js";import{u as Ue,j as He,k as Je,g as qe,l as Ge,m as Qe,n as Ve,o as Ye,p as Ke,q as Xe,r as We,s as Ze,t as et,v as tt}from"./index-DwWP-Z_c.js";import{f as st}from"./useEventHooks-Dn_vN2xE.js";import{u as te,C as fe,a as at}from"./CustomDurationPicker-OKlwH5a_.js";import{u as nt}from"./roles-D4Wn7kE9.js";import{u as lt}from"./useFilterParams-x-Dg0Vgz.js";import{u as pe}from"./useDebouncedValue-BA5S7V54.js";import{b as it}from"./api-path-u2Jf9QXS.js";import{D as rt}from"./DataTable-ZWgVQg7F.js";import{S as ot}from"./StickyPagination-CqT9Q1I6.js";import{P as ct}from"./PageHeader-DJo7yMUR.js";import{P as dt}from"./constants-BHkpVaqx.js";import{M as se}from"./Modal-CSrxpXeM.js";import{u as ut}from"./users-DngGIiC_.js";import{E as xt,a as mt}from"./escalation-columns-Dyrf6Tdp.js";import{R as ht,a as gt}from"./RowActions-BA9L9djf.js";import{L as ft}from"./ListToolbar-Dz2_neVF.js";import{a4 as pt,S as bt,X as yt}from"./vendor-icons-DOIDKgo6.js";import"./EmptyState-BcsfPq9T.js";import"./FilterBar-B4Mk2PvT.js";import"./PriorityBadge-B4ykBH7f.js";import"./RolePill-DHWixsQR.js";import"./WorkflowPill-BtNBSplz.js";import"./escalation-CK8ElYzD.js";const F=t=>{if(t)try{return JSON.parse(t)}catch{return}},A=t=>t==null||Array.isArray(t)&&t.length===0||typeof t=="object"&&!Array.isArray(t)&&Object.keys(t).length===0;function vt(t){const s={},o=F(t.get("facets"));A(o)||(s.facets=o);const c=F(t.get("block"));A(c)||(s.block=c);const x=F(t.get("range"));A(x)||(s.range=x);const f=F(t.get("exists"));A(f)||(s.exists=f);const h=F(t.get("roles"));A(h)||(s.roles=h);const l=F(t.get("orderBy"));return A(l)||(s.orderBy=l),t.get("available")!=null&&(s.available=t.get("available")==="true"),s}function jt(t,s){const o=(c,x)=>A(x)?t.delete(c):t.set(c,JSON.stringify(x));o("facets",s.facets),o("block",s.block),o("range",s.range),o("exists",s.exists),o("roles",s.roles),o("orderBy",s.orderBy),s.available==null?t.delete("available"):t.set("available",String(s.available))}function Ct(t){var s,o,c,x;return Object.keys(t.facets??{}).length+(((s=t.block)==null?void 0:s.length)??0)+(((o=t.range)==null?void 0:o.length)??0)+(((c=t.exists)==null?void 0:c.length)??0)+(((x=t.orderBy)==null?void 0:x.length)??0)+(t.available!=null?1:0)}const kt=["<","<=",">",">=","="],Nt=["priority","created_at","updated_at","status","role"],P="h-7 rounded border border-surface-border bg-surface px-2 text-xs text-text-primary focus:border-accent focus:outline-none",H="text-text-tertiary hover:text-text-primary transition-colors";function ge(t){return t==="true"?!0:t==="false"?!1:t.trim()!==""&&!Number.isNaN(Number(t))?Number(t):t}const St=t=>Object.entries(t??{}).map(([s,o])=>({key:s,value:String(o)})),Pt=t=>(t??[]).map(s=>{const[o,c]=Object.entries(s)[0]??["",""];return{key:o,value:String(c)}});function T({title:t,hint:s,children:o}){return e.jsxs("div",{className:"border-t border-surface-border/50 py-3 first:border-t-0 first:pt-1",children:[e.jsxs("div",{className:"mb-1.5 flex items-baseline justify-between",children:[e.jsx("span",{className:"text-[11px] font-medium uppercase tracking-wide text-text-tertiary",children:t}),s&&e.jsx("span",{className:"text-[10px] font-mono text-text-tertiary/70",children:s})]}),o]})}function J({value:t,onChange:s,placeholder:o}){return e.jsx("input",{list:"lt-facet-keys",className:`${P} w-40`,placeholder:o,value:t,onChange:c=>s(c.target.value)})}function wt({value:t,onChange:s,facetKeys:o=[],search:c="",onSearchChange:x}){const f=St(t.facets),h=Pt(t.block),l=t.range??[],u=t.exists??[],b=t.orderBy??[],v=a=>{const i={};for(const n of a)n.key&&(i[n.key]=ge(n.value));s({...t,facets:Object.keys(i).length?i:void 0})},N=a=>{const i=a.filter(n=>n.key).map(n=>({[n.key]:ge(n.value)}));s({...t,block:i.length?i:void 0})},k=a=>s({...t,range:a.length?a:void 0}),S=a=>s({...t,exists:a.length?a:void 0}),C=a=>s({...t,orderBy:a.length?a:void 0});return e.jsxs("div",{className:"text-xs",children:[e.jsx("datalist",{id:"lt-facet-keys",children:o.map(a=>e.jsx("option",{value:a},a))}),e.jsxs("datalist",{id:"lt-sort-fields",children:[Nt.map(a=>e.jsx("option",{value:a},a)),o.map(a=>e.jsx("option",{value:`metadata.${a}`},a))]}),x&&e.jsx(T,{title:"Match id",hint:"id · workflow · origin",children:e.jsx("input",{className:`${P} w-full`,placeholder:"Exact escalation, workflow, or origin id…",value:c,onChange:a=>x(a.target.value)})}),e.jsx(T,{title:"Has facet",hint:"metadata @>",children:[...f,{key:"",value:""}].map((a,i)=>e.jsxs("div",{className:"mb-1 flex items-center gap-1",children:[e.jsx(J,{value:a.key,placeholder:"key",onChange:n=>{const r=[...f];r[i]={...a,key:n},v(r)}}),e.jsx("span",{className:"text-text-tertiary",children:"="}),e.jsx("input",{className:`${P} w-40`,placeholder:"value",value:a.value,onChange:n=>{const r=[...f];r[i]={...a,value:n.target.value},v(r)}}),i<f.length&&e.jsx("button",{className:H,onClick:()=>v(f.filter((n,r)=>r!==i)),children:"×"})]},i))}),e.jsx(T,{title:"Range",hint:"numeric",children:[...l,{facet:"",op:"<=",value:0}].map((a,i)=>e.jsxs("div",{className:"mb-1 flex items-center gap-1",children:[e.jsx(J,{value:a.facet,placeholder:"numeric facet",onChange:n=>{const r=[...l];r[i]={...a,facet:n},k(r.filter(y=>y.facet))}}),e.jsx("select",{className:P,value:a.op,onChange:n=>{const r=[...l];r[i]={...a,op:n.target.value},k(r.filter(y=>y.facet))},children:kt.map(n=>e.jsx("option",{value:n,children:n},n))}),e.jsx("input",{type:"number",className:`${P} w-24`,value:a.value,onChange:n=>{const r=[...l];r[i]={...a,value:Number(n.target.value)},k(r.filter(y=>y.facet))}}),i<l.length&&e.jsx("button",{className:H,onClick:()=>k(l.filter((n,r)=>r!==i)),children:"×"})]},i))}),e.jsx(T,{title:"Has key",hint:"metadata ? key",children:e.jsxs("div",{className:"flex flex-wrap items-center gap-1",children:[u.map(a=>e.jsxs("span",{className:"flex items-center gap-1 rounded bg-accent/15 px-1.5 py-0.5 text-accent",children:[a,e.jsx("button",{className:"text-accent/70 hover:text-accent",onClick:()=>S(u.filter(i=>i!==a)),children:"×"})]},a)),e.jsx(J,{value:"",placeholder:"+ key",onChange:a=>{a&&!u.includes(a)&&S([...u,a])}})]})}),e.jsx(T,{title:"Exclude",hint:"NOT @>",children:[...h,{key:"",value:""}].map((a,i)=>e.jsxs("div",{className:"mb-1 flex items-center gap-1",children:[e.jsx(J,{value:a.key,placeholder:"key",onChange:n=>{const r=[...h];r[i]={...a,key:n},N(r)}}),e.jsx("span",{className:"text-text-tertiary",children:"="}),e.jsx("input",{className:`${P} w-40`,placeholder:"value",value:a.value,onChange:n=>{const r=[...h];r[i]={...a,value:n.target.value},N(r)}}),i<h.length&&e.jsx("button",{className:H,onClick:()=>N(h.filter((n,r)=>r!==i)),children:"×"})]},i))}),e.jsx(T,{title:"Sort by",children:[...b,{field:"",direction:"asc"}].map((a,i)=>e.jsxs("div",{className:"mb-1 flex items-center gap-1",children:[e.jsx("input",{list:"lt-sort-fields",className:`${P} w-44`,placeholder:"column or metadata.key",value:a.field,onChange:n=>{const r=[...b];r[i]={...a,field:n.target.value},C(r.filter(y=>y.field))}}),e.jsxs("select",{className:P,value:a.direction??"asc",onChange:n=>{const r=[...b];r[i]={...a,direction:n.target.value},C(r.filter(y=>y.field))},children:[e.jsx("option",{value:"asc",children:"asc"}),e.jsx("option",{value:"desc",children:"desc"})]}),e.jsxs("label",{className:"flex items-center gap-1 text-text-tertiary",children:[e.jsx("input",{type:"checkbox",checked:!!a.numeric,onChange:n=>{const r=[...b];r[i]={...a,numeric:n.target.checked},C(r.filter(y=>y.field))}}),"num"]}),i<b.length&&e.jsx("button",{className:H,onClick:()=>C(b.filter((n,r)=>r!==i)),children:"×"})]},i))})]})}const At=t=>t.isPriorityPending||t.isClaimPending||t.isAssignPending||t.isEscalatePending||t.isTriagePending||t.isCancelPending,ee="inline-flex items-center h-7 px-2.5 text-[11px] font-medium text-text-secondary bg-surface border border-surface-border rounded-[4px] hover:border-accent/50 hover:text-text-primary transition-colors disabled:opacity-50 disabled:hover:border-surface-border",Z=`${ee} appearance-none cursor-pointer`,Bt="inline-flex items-center h-7 px-2.5 text-[11px] font-medium text-status-error bg-surface border border-status-error/30 rounded-[4px] hover:border-status-error hover:bg-status-error/5 transition-colors disabled:opacity-50";function Et(t){var h;const s=At(t),o=te(),{data:c}=Ue(),x=((h=c==null?void 0:c.ai)==null?void 0:h.enabled)===!0,f=t.selectedCount>0;return e.jsx("div",{className:`grid transition-all duration-200 ease-in-out ${f?"grid-rows-[1fr] opacity-100":"grid-rows-[0fr] opacity-0 pointer-events-none"}`,children:e.jsx("div",{className:"overflow-hidden",children:e.jsxs("div",{className:"flex flex-wrap items-center gap-2 py-2.5 pl-3 pr-2 mt-4 mb-3 bg-surface-sunken/60 border-l-2 border-l-accent",children:[e.jsxs("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-accent tabular-nums",children:[t.selectedCount," selected"]}),e.jsx("div",{className:"mx-1 h-4 w-px bg-surface-border"}),e.jsxs("select",{onChange:l=>{l.target.value&&(t.onSetPriority(parseInt(l.target.value)),l.target.value="")},disabled:s,className:Z,defaultValue:"",children:[e.jsx("option",{value:"",disabled:!0,children:"Priority…"}),dt.map(l=>e.jsx("option",{value:l.value,children:l.label},l.value))]}),e.jsxs("select",{onChange:l=>{l.target.value&&(t.onClaim(parseInt(l.target.value)),l.target.value="")},disabled:s,className:Z,defaultValue:"",children:[e.jsx("option",{value:"",disabled:!0,children:"Claim for…"}),o.map(l=>e.jsx("option",{value:l.value,children:l.label},l.value))]}),e.jsx("button",{onClick:t.onAssign,disabled:s,className:ee,children:t.isAssignPending?"Assigning…":"Assign to…"}),t.availableRoles.length>0&&e.jsxs("select",{onChange:l=>{l.target.value&&(t.onEscalate(l.target.value),l.target.value="")},disabled:s,className:Z,defaultValue:"",children:[e.jsx("option",{value:"",disabled:!0,children:"Escalate to…"}),t.availableRoles.map(l=>e.jsx("option",{value:l,children:l},l))]}),x&&e.jsx("button",{onClick:t.onTriage,disabled:s,className:ee,children:t.isTriagePending?"Triaging…":"Triage"}),e.jsx("button",{onClick:t.onCancel,disabled:s,className:Bt,children:t.isCancelPending?"Cancelling…":"Cancel"}),e.jsx("div",{className:"flex-1"}),s&&e.jsx("span",{className:"text-[11px] text-text-tertiary animate-pulse",children:"Processing…"}),e.jsx("button",{onClick:t.onClearSelection,className:"px-1 text-[11px] font-medium text-text-tertiary hover:text-text-primary transition-colors",children:"Clear"})]})})})}function Ot({open:t,onClose:s,selectedCount:o,selectedRoles:c,onSubmit:x,isPending:f}){const{isSuperAdmin:h}=He(),l=te(),[u,b]=p.useState("user"),[v,N]=p.useState(null),[k,S]=p.useState(""),[C,a]=p.useState("30"),[i,n]=p.useState(0),r=p.useCallback(m=>n(m),[]),y=!h&&c.length===1?c[0]:void 0,$=pe(k,250),{data:g,isLoading:_}=ut({role:y,status:"active",search:$.trim()||void 0,limit:200}),B=(g==null?void 0:g.users)??[],E=()=>{b("user"),N(null),S(""),a("30"),n(0),s()},q=m=>{N(m),b("duration")},z=()=>{b("user")},G=()=>{if(!v)return;const m=C==="custom"?i:parseInt(C);!m||m<=0||x(v.id,m)};return e.jsx(se,{open:t,onClose:E,title:"Assign Escalations",children:e.jsxs("div",{className:"space-y-4",children:[u==="user"&&e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Select a user to assign"," ",e.jsx("span",{className:"font-medium text-text-primary",children:o})," ","escalation(s) to:"]}),e.jsx("input",{type:"text",value:k,onChange:m=>S(m.target.value),placeholder:"Search by name or email...",className:"input text-xs w-full",autoFocus:!0}),e.jsxs("div",{className:"max-h-60 overflow-y-auto border border-surface-border rounded-lg divide-y divide-surface-border",children:[_&&e.jsx("p",{className:"text-xs text-text-tertiary p-3",children:"Loading users..."}),!_&&B.length===0&&e.jsx("p",{className:"text-xs text-text-tertiary p-3",children:"No users found"}),B.map(m=>e.jsxs("button",{onClick:()=>q(m),className:"w-full text-left px-3 py-2 hover:bg-surface-hover transition-colors",children:[e.jsx("p",{className:"text-xs font-medium text-text-primary",children:m.display_name||m.external_id}),m.email&&e.jsx("p",{className:"text-[10px] text-text-tertiary",children:m.email})]},m.id))]}),e.jsx("div",{className:"flex justify-end pt-2",children:e.jsx("button",{onClick:E,className:"btn-secondary text-xs",children:"Cancel"})})]}),u==="duration"&&v&&e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Assign to"," ",e.jsx("span",{className:"font-medium text-text-primary",children:v.display_name||v.external_id})," ","for:"]}),e.jsxs("select",{value:C,onChange:m=>{a(m.target.value),n(0)},className:"select w-full text-sm",children:[l.map(m=>e.jsx("option",{value:m.value,children:m.label},m.value)),e.jsx("option",{value:"custom",children:"Other..."})]}),C==="custom"&&e.jsx(fe,{onChange:r,autoFocus:!0}),e.jsxs("div",{className:"flex justify-end gap-3 pt-2",children:[e.jsx("button",{onClick:z,className:"btn-secondary text-xs",children:"Back"}),e.jsx("button",{onClick:G,disabled:f,className:"btn-primary text-xs",children:f?"Assigning...":"Assign"})]})]})]})})}function Mt({open:t,onClose:s,selectedCount:o,onSubmit:c,isPending:x}){const[f,h]=p.useState(""),l=()=>{c(f||void 0)};return e.jsx(se,{open:t,onClose:s,title:"Submit for AI Triage",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Submit ",e.jsx("span",{className:"font-medium text-text-primary",children:o})," escalation(s) for AI triage? The triage orchestrator will take over resolution."]}),e.jsxs("div",{children:[e.jsxs("label",{className:"text-xs font-medium text-text-secondary block mb-1",children:["Triage hint ",e.jsx("span",{className:"text-text-tertiary",children:"(optional)"})]}),e.jsx("input",{type:"text",value:f,onChange:u=>h(u.target.value),placeholder:"e.g., image_orientation",className:"input text-xs font-mono w-full"}),e.jsx("p",{className:"text-[10px] text-text-tertiary mt-1",children:"Guides the triage workflow on what remediation to apply"})]}),e.jsxs("div",{className:"flex justify-end gap-3 pt-2",children:[e.jsx("button",{onClick:s,className:"btn-secondary text-xs",children:"Cancel"}),e.jsx("button",{onClick:l,disabled:x,className:"btn-primary text-xs",children:x?"Submitting...":"Submit for Triage"})]})]})})}function R(t,s,o){return c=>{t.mutate(c,{onSuccess:()=>{s(),o==null||o()}})}}function Ft(t){const s=()=>[...t.selectedIds];return{handleSetPriority:u=>{R(t.setPriority,t.clearSelection)({ids:s(),priority:u})},handleBulkClaim:u=>{R(t.bulkClaim,t.clearSelection)({ids:s(),durationMinutes:u})},handleBulkEscalate:u=>{R(t.bulkEscalate,t.clearSelection)({ids:s(),targetRole:u})},handleBulkTriage:u=>{R(t.bulkTriage,t.clearSelection,t.closeTriageModal)({ids:s(),hint:u})},handleBulkAssign:(u,b)=>{R(t.bulkAssign,t.clearSelection,t.closeAssignModal)({ids:s(),targetUserId:u,durationMinutes:b})},handleBulkCancel:()=>{R(t.bulkCancel,t.clearSelection,t.closeCancelModal)({ids:s()})}}}function Tt({claimTarget:t,onClose:s,claimDuration:o,onDurationChange:c,claimDurations:x,onCustomClaimChange:f,onClaim:h,isPending:l}){return e.jsx(se,{open:!!t,onClose:s,title:"Claim Escalation",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Claim ",e.jsx("span",{className:"font-medium text-text-primary",children:t==null?void 0:t.type})," for:"]}),e.jsxs("select",{value:o,onChange:u=>{c(u.target.value)},className:"select w-full text-sm",children:[x.map(u=>e.jsx("option",{value:u.value,children:u.label},u.value)),e.jsx("option",{value:"custom",children:"Other..."})]}),o==="custom"&&e.jsx(fe,{onChange:f,autoFocus:!0}),e.jsxs("div",{className:"flex justify-end gap-3 pt-2",children:[e.jsx("button",{onClick:s,className:"btn-secondary text-xs",children:"Cancel"}),e.jsx("button",{onClick:h,className:"btn-primary text-xs",disabled:l,children:l?"Claiming...":"Claim"})]})]})})}function is(){var de,ue,xe,me,he;st();const t=Ie(),{filters:s,setFilter:o,pagination:c,sort:x,setSort:f}=lt({filters:{role:"",type:"",priority:"",status:"available",search:""}}),h=pe(s.search,300),l=te(),[u,b]=p.useState(null),[v,N]=p.useState("30"),[k,S]=p.useState(0),C=p.useCallback(d=>S(d),[]),[a,i]=p.useState(new Set),[n,r]=p.useState(!1),[y,$]=$e(),g=p.useMemo(()=>vt(y),[y]),_=p.useCallback(d=>{$(j=>{const W=new URLSearchParams(j);return jt(W,d),W.delete("page"),W},{replace:!0})},[$]),{data:B}=Je(n),E=Ct(g)+(s.search?1:0),[q,z]=p.useState(!1),[G,m]=p.useState(!1),[be,Q]=p.useState(!1),ae=qe(),ne=Ge(),le=Qe(),V=Ve(),ie=Ye(),Y=Ke(),D=Xe(),{data:O}=nt(),{data:K}=We();p.useEffect(()=>{i(new Set)},[s.role,s.type,s.priority,s.status,h,c.page,c.pageSize]);const L=s.status||"",I=L==="available",X=L==="claimed",re=X?"pending":L==="resolved"?"resolved":L==="cancelled"?"cancelled":L==="expired"?"expired":void 0,oe={role:s.role||void 0,type:s.type||void 0,priority:s.priority?parseInt(s.priority):void 0,limit:c.pageSize,offset:c.offset,sort_by:x.sort_by||"created_at",order:x.order||"desc",search:h||void 0,...g},ye=Ze({...oe,enabled:I}),ve=et({status:re,claimed:X||void 0,...oe,enabled:!I}),je=I?ye:ve,{data:M,isLoading:Ce,error:U,refetch:ke,isFetching:Ne}=je,Se=it(`/escalations${I?"/available":""}`,{status:re,claimed:X||void 0,role:s.role||void 0,type:s.type||void 0,priority:s.priority||void 0,search:h||void 0,sort_by:x.sort_by||"created_at",order:x.order||"desc",limit:c.pageSize,offset:c.offset,facets:g.facets&&Object.keys(g.facets).length?JSON.stringify(g.facets):void 0,block:(de=g.block)!=null&&de.length?JSON.stringify(g.block):void 0,range:(ue=g.range)!=null&&ue.length?JSON.stringify(g.range):void 0,exists:(xe=g.exists)!=null&&xe.length?JSON.stringify(g.exists):void 0,roles:(me=g.roles)!=null&&me.length?JSON.stringify(g.roles):void 0,orderBy:(he=g.orderBy)!=null&&he.length?JSON.stringify(g.orderBy):void 0,available:g.available!=null?String(g.available):void 0}),w=(M==null?void 0:M.escalations)??[],ce=(M==null?void 0:M.total)??0,{canBulk:Pe}=tt(),we=p.useMemo(()=>{const d=new Set;for(const j of w)a.has(j.id)&&d.add(j.role);return[...d]},[w,a]),Ae=p.useCallback(()=>i(new Set),[]),Be=()=>{if(!u)return;const d=v==="custom"?k:parseInt(v);!d||d<=0||ae.mutate({id:u.id,durationMinutes:d},{onSuccess:()=>{b(null),t(`/escalations/detail/${u.id}`)}})},{handleSetPriority:Ee,handleBulkClaim:Oe,handleBulkEscalate:Me,handleBulkTriage:Fe,handleBulkAssign:Te,handleBulkCancel:Re}=Ft({selectedIds:a,clearSelection:Ae,setPriority:ne,bulkClaim:le,bulkEscalate:ie,bulkTriage:Y,bulkAssign:V,bulkCancel:D,closeTriageModal:()=>z(!1),closeAssignModal:()=>m(!1),closeCancelModal:()=>Q(!1)}),_e=d=>{const j=new Set(a);j.has(d)?j.delete(d):j.add(d),i(j)},ze=()=>{a.size===w.length?i(new Set):i(new Set(w.map(d=>d.id)))},Le=[Pe?{key:"select",label:e.jsx("input",{type:"checkbox",checked:w.length>0&&a.size===w.length,onChange:ze,className:"rounded"}),render:d=>e.jsx("input",{type:"checkbox",checked:a.has(d.id),onChange:j=>{j.stopPropagation(),_e(d.id)},onClick:j=>j.stopPropagation(),className:"rounded"}),className:"w-10"}:{key:"spacer",label:"",render:()=>null,className:"w-10"},...xt,{key:"actions",label:"",render:d=>e.jsx(ht,{children:e.jsx(gt,{icon:pt,title:"Claim escalation",onClick:()=>b(d),colorClass:"text-accent/75 hover:text-accent",size:"sm"})}),className:"w-10 text-right"}];return e.jsxs("div",{children:[e.jsx(ct,{title:"All Escalations",docsHash:"#docs:dashboard.md:all-escalations"}),e.jsx(mt,{filters:s,setFilter:o,roles:(O==null?void 0:O.roles)??[],types:(K==null?void 0:K.types)??[],showStatus:!0,showSearch:!1,actions:e.jsxs(e.Fragment,{children:[e.jsx(ft,{onRefresh:()=>ke(),isFetching:Ne,apiPath:Se}),e.jsxs("button",{onClick:()=>r(d=>!d),className:"relative ml-2 inline-flex h-7 w-7 items-center justify-center rounded text-text-tertiary hover:bg-surface-hover hover:text-text-primary transition-colors",title:"Faceted query",children:[e.jsx(bt,{className:"w-4 h-4"}),E>0&&e.jsx("span",{className:"absolute -right-1 -top-1 flex h-3.5 min-w-[14px] items-center justify-center rounded-full bg-accent px-0.5 text-[9px] font-medium text-white",children:E})]})]})}),De.createPortal(e.jsxs("div",{className:`fixed right-0 bottom-0 w-[420px] z-40 border-l border-surface-border bg-surface overflow-y-auto shadow-lg transition-transform duration-200 ease-out ${n?"translate-x-0":"pointer-events-none translate-x-full"}`,style:{top:"3.5rem"},"aria-hidden":!n,children:[e.jsxs("div",{className:"sticky top-0 z-10 flex items-center justify-between px-4 py-3 bg-surface border-b border-surface-border/50",children:[e.jsx("span",{className:"text-xs font-medium text-text-primary",children:"Faceted query"}),e.jsxs("div",{className:"flex items-center gap-2",children:[E>0&&e.jsx("button",{onClick:()=>{_({}),o("search","")},className:"text-[11px] text-text-tertiary hover:text-text-primary transition-colors",children:"Clear"}),e.jsx("button",{onClick:()=>r(!1),className:"p-1 rounded hover:bg-surface-hover text-text-tertiary hover:text-text-primary transition-colors",title:"Close",children:e.jsx(yt,{className:"w-4 h-4"})})]})]}),e.jsxs("div",{className:"px-4 py-2",children:[e.jsxs("p",{className:"mb-2 text-[11px] leading-snug text-text-tertiary",children:["Precise metadata facets plus exact correlation-id lookup — they compose in one SQL query. Facet keys are the ones that actually exist in your visible escalations. Set status to ",e.jsx("span",{className:"font-medium",children:"All"})," to find an order across every status; the whole query is shareable via the URL."]}),e.jsx(wt,{value:g,onChange:_,facetKeys:(B==null?void 0:B.keys)??[],search:s.search??"",onSearchChange:d=>o("search",d)})]})]}),document.body),e.jsx(Et,{selectedCount:a.size,onClearSelection:()=>i(new Set),onSetPriority:Ee,onClaim:Oe,onAssign:()=>m(!0),onEscalate:Me,onTriage:()=>z(!0),onCancel:()=>Q(!0),isPriorityPending:ne.isPending,isClaimPending:le.isPending,isAssignPending:V.isPending,isEscalatePending:ie.isPending,isTriagePending:Y.isPending,isCancelPending:D.isPending,availableRoles:(O==null?void 0:O.roles)??[]}),U&&e.jsx("div",{className:"mb-4 px-4 py-3 rounded-md bg-status-error/10 border border-status-error/20 text-xs text-status-error",children:U.message==="Session expired"?"Your session has expired. Please log in again.":`Failed to load escalations: ${U.message}`}),e.jsx(rt,{columns:Le,data:w,keyFn:d=>d.id,onRowClick:d=>t(`/escalations/detail/${d.id}`,{state:{from:"/escalations/available"}}),isLoading:Ce,emptyMessage:U?"Unable to load data":"No escalations",sort:x,onSort:f}),e.jsx(ot,{page:c.page,totalPages:c.totalPages(ce),onPageChange:c.setPage,total:ce,pageSize:c.pageSize,onPageSizeChange:c.setPageSize}),e.jsx(Tt,{claimTarget:u,onClose:()=>b(null),claimDuration:v,onDurationChange:d=>{N(d),S(0)},claimDurations:l,customClaimMinutes:k,onCustomClaimChange:C,onClaim:Be,isPending:ae.isPending}),e.jsx(Mt,{open:q,onClose:()=>z(!1),selectedCount:a.size,onSubmit:Fe,isPending:Y.isPending}),e.jsx(Ot,{open:G,onClose:()=>m(!1),selectedCount:a.size,selectedRoles:we,onSubmit:Te,isPending:V.isPending}),e.jsx(at,{open:be,onClose:()=>Q(!1),onConfirm:Re,selectedCount:a.size,isPending:D.isPending,error:D.error})]})}export{is as AvailableEscalationsPage};
2
- //# sourceMappingURL=AvailableEscalationsPage-DDwAgklw.js.map