@hotmeshio/long-tail 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +79 -49
  2. package/build/api/escalations/index.d.ts +1 -1
  3. package/build/api/escalations/index.js +2 -1
  4. package/build/api/escalations/resolve.d.ts +10 -0
  5. package/build/api/escalations/resolve.js +52 -0
  6. package/build/api/topics.d.ts +14 -4
  7. package/build/api/topics.js +9 -9
  8. package/build/lib/events/index.js +6 -0
  9. package/build/lib/events/publish.js +3 -7
  10. package/build/lib/events/system-events.js +5 -6
  11. package/build/routes/escalations/resolve.js +15 -3
  12. package/build/routes/topics.js +2 -4
  13. package/build/services/agent/input-mapper.js +33 -10
  14. package/build/services/escalation/crud.d.ts +18 -0
  15. package/build/services/escalation/crud.js +29 -0
  16. package/build/services/escalation/map.js +1 -0
  17. package/build/services/interceptor/index.js +25 -6
  18. package/build/services/orchestrator/condition.d.ts +30 -25
  19. package/build/services/orchestrator/condition.js +30 -26
  20. package/build/system/mcp-servers/events.js +1 -3
  21. package/build/types/escalation.d.ts +1 -0
  22. package/build/types/events.d.ts +90 -23
  23. package/dashboard/dist/assets/{AdminDashboard-CMzR4d-w.js → AdminDashboard-DIQEViVY.js} +2 -2
  24. package/dashboard/dist/assets/{AdminDashboard-CMzR4d-w.js.map → AdminDashboard-DIQEViVY.js.map} +1 -1
  25. package/dashboard/dist/assets/{AgentConfigPage-DHCpN4xk.js → AgentConfigPage-Caf5-th-.js} +2 -2
  26. package/dashboard/dist/assets/{AgentConfigPage-DHCpN4xk.js.map → AgentConfigPage-Caf5-th-.js.map} +1 -1
  27. package/dashboard/dist/assets/{AgentDetailPage-CSrejvSl.js → AgentDetailPage-_a4tKaM3.js} +2 -2
  28. package/dashboard/dist/assets/{AgentDetailPage-CSrejvSl.js.map → AgentDetailPage-_a4tKaM3.js.map} +1 -1
  29. package/dashboard/dist/assets/{AgentsPage-ntuzWHCD.js → AgentsPage-BNlAkEIK.js} +2 -2
  30. package/dashboard/dist/assets/{AgentsPage-ntuzWHCD.js.map → AgentsPage-BNlAkEIK.js.map} +1 -1
  31. package/dashboard/dist/assets/{AvailableEscalationsPage-Ci24fdxC.js → AvailableEscalationsPage-Cqy5ECm4.js} +2 -2
  32. package/dashboard/dist/assets/{AvailableEscalationsPage-Ci24fdxC.js.map → AvailableEscalationsPage-Cqy5ECm4.js.map} +1 -1
  33. package/dashboard/dist/assets/{BotPicker-lj42d48P.js → BotPicker-DBvd1yFL.js} +2 -2
  34. package/dashboard/dist/assets/{BotPicker-lj42d48P.js.map → BotPicker-DBvd1yFL.js.map} +1 -1
  35. package/dashboard/dist/assets/{CapabilitiesPage-Bea9yLmU.js → CapabilitiesPage-BIdG9RTG.js} +2 -2
  36. package/dashboard/dist/assets/{CapabilitiesPage-Bea9yLmU.js.map → CapabilitiesPage-BIdG9RTG.js.map} +1 -1
  37. package/dashboard/dist/assets/{CollapsibleSection-BeBsI1M4.js → CollapsibleSection-CggyKhyZ.js} +2 -2
  38. package/dashboard/dist/assets/{CollapsibleSection-BeBsI1M4.js.map → CollapsibleSection-CggyKhyZ.js.map} +1 -1
  39. package/dashboard/dist/assets/{CredentialsPage-BDzFa3HV.js → CredentialsPage-B4sXGLRY.js} +2 -2
  40. package/dashboard/dist/assets/{CredentialsPage-BDzFa3HV.js.map → CredentialsPage-B4sXGLRY.js.map} +1 -1
  41. package/dashboard/dist/assets/{CronLabel-DeNm1I4r.js → CronLabel-GcS5qzZr.js} +2 -2
  42. package/dashboard/dist/assets/{CronLabel-DeNm1I4r.js.map → CronLabel-GcS5qzZr.js.map} +1 -1
  43. package/dashboard/dist/assets/{CustomDurationPicker-C2OH9YcV.js → CustomDurationPicker-CUYE36-K.js} +2 -2
  44. package/dashboard/dist/assets/{CustomDurationPicker-C2OH9YcV.js.map → CustomDurationPicker-CUYE36-K.js.map} +1 -1
  45. package/dashboard/dist/assets/{ElapsedCell-WdzqA0dR.js → ElapsedCell-BFOMsgwF.js} +2 -2
  46. package/dashboard/dist/assets/{ElapsedCell-WdzqA0dR.js.map → ElapsedCell-BFOMsgwF.js.map} +1 -1
  47. package/dashboard/dist/assets/{EscalationsOverview-BIEeflEV.js → EscalationsOverview-BfjW6haL.js} +2 -2
  48. package/dashboard/dist/assets/{EscalationsOverview-BIEeflEV.js.map → EscalationsOverview-BfjW6haL.js.map} +1 -1
  49. package/dashboard/dist/assets/{EventTable-NIhWP__B.js → EventTable-N0XmLk3C.js} +2 -2
  50. package/dashboard/dist/assets/{EventTable-NIhWP__B.js.map → EventTable-N0XmLk3C.js.map} +1 -1
  51. package/dashboard/dist/assets/{GraphInvokePage-DtAW8ilc.js → GraphInvokePage-DHiGHiCk.js} +2 -2
  52. package/dashboard/dist/assets/{GraphInvokePage-DtAW8ilc.js.map → GraphInvokePage-DHiGHiCk.js.map} +1 -1
  53. package/dashboard/dist/assets/{HomePage-HsO-M8ub.js → HomePage-jckQolbx.js} +2 -2
  54. package/dashboard/dist/assets/{HomePage-HsO-M8ub.js.map → HomePage-jckQolbx.js.map} +1 -1
  55. package/dashboard/dist/assets/{ListToolbar-DqaRlXrF.js → ListToolbar-B71DqaXr.js} +2 -2
  56. package/dashboard/dist/assets/{ListToolbar-DqaRlXrF.js.map → ListToolbar-B71DqaXr.js.map} +1 -1
  57. package/dashboard/dist/assets/{McpOverview-B4GsHxij.js → McpOverview-CPD6NRGt.js} +2 -2
  58. package/dashboard/dist/assets/{McpOverview-B4GsHxij.js.map → McpOverview-CPD6NRGt.js.map} +1 -1
  59. package/dashboard/dist/assets/{McpQueryDetailPage-DZKpzWST.js → McpQueryDetailPage-YyqXw6Dt.js} +2 -2
  60. package/dashboard/dist/assets/{McpQueryDetailPage-DZKpzWST.js.map → McpQueryDetailPage-YyqXw6Dt.js.map} +1 -1
  61. package/dashboard/dist/assets/{McpQueryPage-B-8WR3GV.js → McpQueryPage-BWf45EYD.js} +2 -2
  62. package/dashboard/dist/assets/{McpQueryPage-B-8WR3GV.js.map → McpQueryPage-BWf45EYD.js.map} +1 -1
  63. package/dashboard/dist/assets/{McpRunDetailPage-Dt7Ai3qB.js → McpRunDetailPage-Cv43tkjz.js} +2 -2
  64. package/dashboard/dist/assets/{McpRunDetailPage-Dt7Ai3qB.js.map → McpRunDetailPage-Cv43tkjz.js.map} +1 -1
  65. package/dashboard/dist/assets/{McpRunsPage-Bdke-HoQ.js → McpRunsPage-BFards4K.js} +2 -2
  66. package/dashboard/dist/assets/{McpRunsPage-Bdke-HoQ.js.map → McpRunsPage-BFards4K.js.map} +1 -1
  67. package/dashboard/dist/assets/{OperatorDashboard-VZ97mufd.js → OperatorDashboard-BJZxuOKI.js} +2 -2
  68. package/dashboard/dist/assets/{OperatorDashboard-VZ97mufd.js.map → OperatorDashboard-BJZxuOKI.js.map} +1 -1
  69. package/dashboard/dist/assets/{ProcessDetailPage-BLfLfmWX.js → ProcessDetailPage-BjkLfyrm.js} +2 -2
  70. package/dashboard/dist/assets/{ProcessDetailPage-BLfLfmWX.js.map → ProcessDetailPage-BjkLfyrm.js.map} +1 -1
  71. package/dashboard/dist/assets/{ProcessesListPage-DG54t-Nd.js → ProcessesListPage-DOzeduP3.js} +2 -2
  72. package/dashboard/dist/assets/{ProcessesListPage-DG54t-Nd.js.map → ProcessesListPage-DOzeduP3.js.map} +1 -1
  73. package/dashboard/dist/assets/{RolesPage-Bdlv3DdW.js → RolesPage-DSSQrGTh.js} +2 -2
  74. package/dashboard/dist/assets/{RolesPage-Bdlv3DdW.js.map → RolesPage-DSSQrGTh.js.map} +1 -1
  75. package/dashboard/dist/assets/{RunAsSelector-BFxxMvL3.js → RunAsSelector-CM7lk1YB.js} +2 -2
  76. package/dashboard/dist/assets/{RunAsSelector-BFxxMvL3.js.map → RunAsSelector-CM7lk1YB.js.map} +1 -1
  77. package/dashboard/dist/assets/{SwimlaneTimeline-BZmad7WQ.js → SwimlaneTimeline-DtCgJNpV.js} +2 -2
  78. package/dashboard/dist/assets/{SwimlaneTimeline-BZmad7WQ.js.map → SwimlaneTimeline-DtCgJNpV.js.map} +1 -1
  79. package/dashboard/dist/assets/{TaskDetailPage-CLl2mgBC.js → TaskDetailPage-BC9xnyLT.js} +2 -2
  80. package/dashboard/dist/assets/{TaskDetailPage-CLl2mgBC.js.map → TaskDetailPage-BC9xnyLT.js.map} +1 -1
  81. package/dashboard/dist/assets/{TasksListPage-DoHXxZF-.js → TasksListPage-DtbLfUS-.js} +2 -2
  82. package/dashboard/dist/assets/{TasksListPage-DoHXxZF-.js.map → TasksListPage-DtbLfUS-.js.map} +1 -1
  83. package/dashboard/dist/assets/{TimeAgo-DZF9w8Rl.js → TimeAgo-xrDlFnPy.js} +2 -2
  84. package/dashboard/dist/assets/{TimeAgo-DZF9w8Rl.js.map → TimeAgo-xrDlFnPy.js.map} +1 -1
  85. package/dashboard/dist/assets/{TimestampCell-DPpoTdrw.js → TimestampCell-BneATvjR.js} +2 -2
  86. package/dashboard/dist/assets/{TimestampCell-DPpoTdrw.js.map → TimestampCell-BneATvjR.js.map} +1 -1
  87. package/dashboard/dist/assets/{ToolTestPanel-DtAgJQfr.js → ToolTestPanel-1lQ_GQod.js} +2 -2
  88. package/dashboard/dist/assets/{ToolTestPanel-DtAgJQfr.js.map → ToolTestPanel-1lQ_GQod.js.map} +1 -1
  89. package/dashboard/dist/assets/TopicDetailPage-CDtlXyut.js +9 -0
  90. package/dashboard/dist/assets/TopicDetailPage-CDtlXyut.js.map +1 -0
  91. package/dashboard/dist/assets/{TopicsPage-Bc-4ne6V.js → TopicsPage-COX2kDKK.js} +2 -2
  92. package/dashboard/dist/assets/{TopicsPage-Bc-4ne6V.js.map → TopicsPage-COX2kDKK.js.map} +1 -1
  93. package/dashboard/dist/assets/{UserName-O2Q4-E6E.js → UserName-Cb2WvhCN.js} +2 -2
  94. package/dashboard/dist/assets/{UserName-O2Q4-E6E.js.map → UserName-Cb2WvhCN.js.map} +1 -1
  95. package/dashboard/dist/assets/{WorkflowExecutionPage-DEDsBmn1.js → WorkflowExecutionPage-BWCTOTXF.js} +2 -2
  96. package/dashboard/dist/assets/{WorkflowExecutionPage-DEDsBmn1.js.map → WorkflowExecutionPage-BWCTOTXF.js.map} +1 -1
  97. package/dashboard/dist/assets/{WorkflowsDashboard-Bcf17vCt.js → WorkflowsDashboard-DOaDSC98.js} +2 -2
  98. package/dashboard/dist/assets/{WorkflowsDashboard-Bcf17vCt.js.map → WorkflowsDashboard-DOaDSC98.js.map} +1 -1
  99. package/dashboard/dist/assets/{WorkflowsOverview-Cwo2rqGT.js → WorkflowsOverview-CeE7a51J.js} +2 -2
  100. package/dashboard/dist/assets/{WorkflowsOverview-Cwo2rqGT.js.map → WorkflowsOverview-CeE7a51J.js.map} +1 -1
  101. package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bv8ZFwO-.js → YamlWorkflowDetailPage-cNpR0PHg.js} +2 -2
  102. package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bv8ZFwO-.js.map → YamlWorkflowDetailPage-cNpR0PHg.js.map} +1 -1
  103. package/dashboard/dist/assets/{YamlWorkflowsPage-DOiEQDOq.js → YamlWorkflowsPage-DWvNfzji.js} +2 -2
  104. package/dashboard/dist/assets/{YamlWorkflowsPage-DOiEQDOq.js.map → YamlWorkflowsPage-DWvNfzji.js.map} +1 -1
  105. package/dashboard/dist/assets/{agents-D09G0HCv.js → agents-BvTiBKBQ.js} +2 -2
  106. package/dashboard/dist/assets/{agents-D09G0HCv.js.map → agents-BvTiBKBQ.js.map} +1 -1
  107. package/dashboard/dist/assets/{bots-D0LhyZZM.js → bots-CeMR1Lnp.js} +2 -2
  108. package/dashboard/dist/assets/{bots-D0LhyZZM.js.map → bots-CeMR1Lnp.js.map} +1 -1
  109. package/dashboard/dist/assets/{capabilities-DON4-NXs.js → capabilities-D7uJlnxj.js} +2 -2
  110. package/dashboard/dist/assets/{capabilities-DON4-NXs.js.map → capabilities-D7uJlnxj.js.map} +1 -1
  111. package/dashboard/dist/assets/{controlplane-Bihd1kXf.js → controlplane-Cf6lZsSF.js} +2 -2
  112. package/dashboard/dist/assets/{controlplane-Bihd1kXf.js.map → controlplane-Cf6lZsSF.js.map} +1 -1
  113. package/dashboard/dist/assets/{escalation-CP2XbdXK.js → escalation-BCFBWGP-.js} +2 -2
  114. package/dashboard/dist/assets/{escalation-CP2XbdXK.js.map → escalation-BCFBWGP-.js.map} +1 -1
  115. package/dashboard/dist/assets/{escalation-columns-9aw8Y4qq.js → escalation-columns-Do7k4fte.js} +2 -2
  116. package/dashboard/dist/assets/{escalation-columns-9aw8Y4qq.js.map → escalation-columns-Do7k4fte.js.map} +1 -1
  117. package/dashboard/dist/assets/{index-DgLZ8Ix5.js → index-8C9YLM_u.js} +2 -2
  118. package/dashboard/dist/assets/{index-DgLZ8Ix5.js.map → index-8C9YLM_u.js.map} +1 -1
  119. package/dashboard/dist/assets/{index-CCup2uaP.js → index-B216fPN_.js} +2 -2
  120. package/dashboard/dist/assets/{index-CCup2uaP.js.map → index-B216fPN_.js.map} +1 -1
  121. package/dashboard/dist/assets/index-BCfzac3h.css +1 -0
  122. package/dashboard/dist/assets/{index-Cb7aSzox.js → index-BDhEpETz.js} +2 -2
  123. package/dashboard/dist/assets/{index-Cb7aSzox.js.map → index-BDhEpETz.js.map} +1 -1
  124. package/dashboard/dist/assets/{index-_JsRJPds.js → index-BS5pcmBR.js} +2 -2
  125. package/dashboard/dist/assets/{index-_JsRJPds.js.map → index-BS5pcmBR.js.map} +1 -1
  126. package/dashboard/dist/assets/{index-B-BK3vtk.js → index-BUu3WKvk.js} +2 -2
  127. package/dashboard/dist/assets/{index-B-BK3vtk.js.map → index-BUu3WKvk.js.map} +1 -1
  128. package/dashboard/dist/assets/{index-BTp73vYK.js → index-C1p_rvkY.js} +2 -2
  129. package/dashboard/dist/assets/{index-BTp73vYK.js.map → index-C1p_rvkY.js.map} +1 -1
  130. package/dashboard/dist/assets/{index-CMRW_PE-.js → index-C51Jzvoz.js} +2 -2
  131. package/dashboard/dist/assets/{index-CMRW_PE-.js.map → index-C51Jzvoz.js.map} +1 -1
  132. package/dashboard/dist/assets/{index-BBBGETMs.js → index-D0fjUKvx.js} +2 -2
  133. package/dashboard/dist/assets/{index-BBBGETMs.js.map → index-D0fjUKvx.js.map} +1 -1
  134. package/dashboard/dist/assets/{index-BInTEEIX.js → index-D3SjB3UM.js} +2 -2
  135. package/dashboard/dist/assets/{index-BInTEEIX.js.map → index-D3SjB3UM.js.map} +1 -1
  136. package/dashboard/dist/assets/{index-rbuNUyAh.js → index-DSjbScPX.js} +2 -2
  137. package/dashboard/dist/assets/{index-rbuNUyAh.js.map → index-DSjbScPX.js.map} +1 -1
  138. package/dashboard/dist/assets/{index-B7lEd0cY.js → index-eWNx4UQ8.js} +3 -3
  139. package/dashboard/dist/assets/{index-B7lEd0cY.js.map → index-eWNx4UQ8.js.map} +1 -1
  140. package/dashboard/dist/assets/{index-H5Yb8CY2.js → index-gKm3HAgO.js} +2 -2
  141. package/dashboard/dist/assets/{index-H5Yb8CY2.js.map → index-gKm3HAgO.js.map} +1 -1
  142. package/dashboard/dist/assets/{index-Cjb8ulHm.js → index-oulO44Iy.js} +2 -2
  143. package/dashboard/dist/assets/{index-Cjb8ulHm.js.map → index-oulO44Iy.js.map} +1 -1
  144. package/dashboard/dist/assets/{knowledge-Bl_KaoKJ.js → knowledge-Bje8_ndK.js} +2 -2
  145. package/dashboard/dist/assets/{knowledge-Bl_KaoKJ.js.map → knowledge-Bje8_ndK.js.map} +1 -1
  146. package/dashboard/dist/assets/{mcp-DXbFGoA8.js → mcp-Bpl6p11J.js} +2 -2
  147. package/dashboard/dist/assets/{mcp-DXbFGoA8.js.map → mcp-Bpl6p11J.js.map} +1 -1
  148. package/dashboard/dist/assets/{mcp-query-Bg69DF2x.js → mcp-query-DMXj-t5f.js} +2 -2
  149. package/dashboard/dist/assets/{mcp-query-Bg69DF2x.js.map → mcp-query-DMXj-t5f.js.map} +1 -1
  150. package/dashboard/dist/assets/{pipelines-DK9LTg9F.js → pipelines-CFh9nBxn.js} +2 -2
  151. package/dashboard/dist/assets/{pipelines-DK9LTg9F.js.map → pipelines-CFh9nBxn.js.map} +1 -1
  152. package/dashboard/dist/assets/{roles-BrsBN4hO.js → roles-CtjQQWSQ.js} +2 -2
  153. package/dashboard/dist/assets/{roles-BrsBN4hO.js.map → roles-CtjQQWSQ.js.map} +1 -1
  154. package/dashboard/dist/assets/{tasks-CwjvPECN.js → tasks-Nz9lVCT1.js} +2 -2
  155. package/dashboard/dist/assets/{tasks-CwjvPECN.js.map → tasks-Nz9lVCT1.js.map} +1 -1
  156. package/dashboard/dist/assets/topics-CiG2N8Z_.js +2 -0
  157. package/dashboard/dist/assets/{topics-BrwkmaFR.js.map → topics-CiG2N8Z_.js.map} +1 -1
  158. package/dashboard/dist/assets/{useEventHooks-Cd1GM1NG.js → useEventHooks-Bimqt7hU.js} +2 -2
  159. package/dashboard/dist/assets/{useEventHooks-Cd1GM1NG.js.map → useEventHooks-Bimqt7hU.js.map} +1 -1
  160. package/dashboard/dist/assets/{useNamespace-D9lghZ25.js → useNamespace-FrG0Monz.js} +2 -2
  161. package/dashboard/dist/assets/{useNamespace-D9lghZ25.js.map → useNamespace-FrG0Monz.js.map} +1 -1
  162. package/dashboard/dist/assets/{useYamlActivityEvents-vOhAwmKO.js → useYamlActivityEvents-JmD4dH5n.js} +2 -2
  163. package/dashboard/dist/assets/{useYamlActivityEvents-vOhAwmKO.js.map → useYamlActivityEvents-JmD4dH5n.js.map} +1 -1
  164. package/dashboard/dist/assets/{users-ChQ7soaq.js → users-DgyonYq6.js} +2 -2
  165. package/dashboard/dist/assets/{users-ChQ7soaq.js.map → users-DgyonYq6.js.map} +1 -1
  166. package/dashboard/dist/assets/{workflows-BLKji1_1.js → workflows-BOwGINtF.js} +2 -2
  167. package/dashboard/dist/assets/{workflows-BLKji1_1.js.map → workflows-BOwGINtF.js.map} +1 -1
  168. package/dashboard/dist/assets/{yaml-workflows-BUhMfdaw.js → yaml-workflows-I1tcjCFK.js} +2 -2
  169. package/dashboard/dist/assets/{yaml-workflows-BUhMfdaw.js.map → yaml-workflows-I1tcjCFK.js.map} +1 -1
  170. package/dashboard/dist/index.html +2 -2
  171. package/docs/api/http/escalations.md +19 -0
  172. package/docs/api/sdk/escalations.md +33 -0
  173. package/docs/data.md +61 -38
  174. package/docs/events.md +34 -13
  175. package/docs/hitl-guide.md +44 -1
  176. package/package.json +2 -2
  177. package/dashboard/dist/assets/TopicDetailPage-W9RKkNNp.js +0 -9
  178. package/dashboard/dist/assets/TopicDetailPage-W9RKkNNp.js.map +0 -1
  179. package/dashboard/dist/assets/index-BFyzZGtv.css +0 -1
  180. package/dashboard/dist/assets/topics-BrwkmaFR.js +0 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Long Tail
2
2
 
3
- Write durable workflows in TypeScript. When they need a human, they escalate. When they need AI, they orchestrate. When a pattern repeats, they compile it away. Postgres is the engine.
3
+ The world is not passive. Machines fail, humans intervene, and conditions change. Long Tail gives TypeScript workflows a durable way to adapt on the fly: call machines, wait on people, resume from Postgres.
4
4
 
5
5
  ```bash
6
6
  npm install @hotmeshio/long-tail
@@ -8,7 +8,7 @@ npm install @hotmeshio/long-tail
8
8
 
9
9
  ## How it works
10
10
 
11
- You write a workflow function. Each activity call checkpoints to Postgres if the process crashes, it resumes from the last completed step.
11
+ Author in TypeScript. Checkpoint to Postgres. Route work to machines or people with the same workflow model.
12
12
 
13
13
  ```typescript
14
14
  import { Durable } from '@hotmeshio/hotmesh';
@@ -18,23 +18,33 @@ import * as activities from './activities';
18
18
  const { analyzeContent } = Durable.workflow.proxyActivities<typeof activities>({ activities });
19
19
 
20
20
  export async function reviewContent(envelope: LTEnvelope) {
21
+ // A machine does the work — your code, an API, a model.
21
22
  const analysis = await analyzeContent(envelope.data.content);
22
23
 
23
24
  if (analysis.confidence >= 0.85) {
24
25
  return { type: 'return' as const, data: { approved: true, analysis } };
25
26
  }
26
27
 
27
- // Low confidence escalate to a human reviewer
28
- return {
29
- type: 'escalation' as const,
30
- role: 'reviewer',
31
- message: `Review needed (confidence: ${analysis.confidence})`,
32
- data: { content: envelope.data.content, analysis },
33
- };
28
+ // The work goes to a person: the workflow suspends and writes one escalation
29
+ // row — assigned to a role, claimable and resolvable from the dashboard, API, or MCP.
30
+ const { workflowId } = Durable.workflow.workflowInfo();
31
+ const decision = await Durable.workflow.condition<{ approved: boolean; notes?: string }>(
32
+ `review-${workflowId}`,
33
+ {
34
+ role: 'reviewer',
35
+ type: 'content-review',
36
+ priority: 2,
37
+ description: `Confidence ${analysis.confidence} — needs a human`,
38
+ metadata: { contentId: envelope.data.contentId },
39
+ envelope: { data: envelope.data, analysis },
40
+ },
41
+ );
42
+
43
+ return { type: 'return' as const, data: { approved: decision.approved, analysis } };
34
44
  }
35
45
  ```
36
46
 
37
- That's a complete workflow. It runs, checkpoints, and when confidence is low, it hands off to a human. The human resolves it through the dashboard or API, and the workflow completes. No separate queue system, no webhook callbacks — the escalation is part of the execution.
47
+ Two surfaces, one model. A `proxyActivity` targets a machine: call it, get a result. `condition()` targets the external world — a reviewer, an operator, a factory cell. It suspends the workflow and writes a single escalation row carrying everything needed to route the work: the `role` that should act, its `type` and `priority`, and any `metadata` to display or filter on. People work that row through an RBAC-scoped surface — find it, claim it, resolve it from the dashboard, the API, or MCP, and resolving it resumes the workflow exactly where it paused.
38
48
 
39
49
  Activities are plain functions:
40
50
 
@@ -62,6 +72,8 @@ Dashboard at [http://localhost:3000](http://localhost:3000). The [boilerplate](h
62
72
 
63
73
  ## The pattern
64
74
 
75
+ Four moves, from a plan you write to a plan that keeps up with reality.
76
+
65
77
  **Step 1 — Author a durable workflow.** Your function checkpoints to Postgres. It can sleep, branch, call child workflows, wait for signals. Standard durable execution.
66
78
 
67
79
  **Step 2 — Certify it.** Promotion to certified adds interceptor guarantees: failures escalate instead of throwing, escalation chains route through RBAC-scoped roles, and every error is either handled or surfaced. It cannot silently fail.
@@ -72,7 +84,7 @@ curl -X PUT http://localhost:3000/api/workflows/reviewContent/config \
72
84
  -d '{ "invocable": true, "task_queue": "default", "default_role": "reviewer" }'
73
85
  ```
74
86
 
75
- **Step 3 — React to events.** Workflows publish topics. Agents subscribe. When `activity.failed` fires, an automation can re-run the step, notify a team, or trigger a different workflow. The choreography is dynamic — add subscribers through the dashboard without changing code.
87
+ **Step 3 — React to events.** Signals are reality reporting back. Workflows publish topics; agents subscribe. When `activity.failed` fires, an automation can re-run the step, notify a team, or trigger a different workflow. The choreography is dynamic — add subscribers through the dashboard without changing code.
76
88
 
77
89
  **Step 4 — Compile what repeats.** The same workflow has two forms. What you wrote in Step 1 is the *procedural* form — readable, Temporal-like, emulated atop the graph: cheap to maintain, heavier to run. The Designer compiles a working execution into the *graph* form — the same durable workflow as a deterministic DAG: no LLM at runtime, no replay overhead, typed in and out, roughly 3x faster. Every procedural pattern has a graph equivalent and the reverse; you pick readability or speed without giving up durability, escalation, or transactional guarantees. It deploys as a reusable tool that any workflow or API call can invoke.
78
90
 
@@ -134,7 +146,7 @@ const lt = await start({
134
146
  });
135
147
  ```
136
148
 
137
- All three paths produce the same outcome: tools callable as durable activities. See the [MCP guide](docs/mcp.md).
149
+ All three paths produce the same outcome: tools callable as durable activities. See the [MCP guide](https://github.com/hotmeshio/long-tail/blob/main/docs/mcp.md).
138
150
 
139
151
  ## Compile workflows
140
152
 
@@ -145,44 +157,62 @@ export ANTHROPIC_API_KEY=sk-ant-...
145
157
  npx ltc compile workflows/
146
158
  ```
147
159
 
148
- The source is the spec. The compiled YAML is the optimized execution. Both live in the repo. See the [Compiler Guide](docs/compiler.md).
160
+ The source is the spec. The compiled YAML is the optimized execution. Both live in the repo. See the [Compiler Guide](https://github.com/hotmeshio/long-tail/blob/main/docs/compiler.md).
149
161
 
150
162
  ## Register a graph flow by hand
151
163
 
152
- `graphWorkflows` is the graph-form peer of `workers`: hand-author the HotMesh YAML and it's created, deployed, and activated at startup. This hello-world assembles a greeting from the input with a single trigger mapping:
164
+ `graphWorkflows` is the graph-form peer of `workers`: hand-author the HotMesh YAML and it's created, deployed, and activated at startup. The same human surface is declarative here — a `hook` with an `escalation:` block. When the flow reaches it, the workflow suspends and writes the escalation row; a person resolves it and the flow continues:
153
165
 
154
166
  ```typescript
155
167
  const lt = await start({
156
168
  database: { connectionString: process.env.DATABASE_URL },
157
169
  graphWorkflows: [{
158
- name: 'hello_world',
170
+ name: 'order_approval',
159
171
  namespace: 'graph',
160
- inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
172
+ inputSchema: { type: 'object', properties: { orderId: { type: 'string' }, region: { type: 'string' } }, required: ['orderId'] },
161
173
  yaml: `
162
174
  app:
163
175
  id: graph
164
176
  version: '1'
165
177
  graphs:
166
- - subscribes: hello_world
167
- publishes: hello_world.done
168
- input: { schema: { type: object, properties: { name: { type: string } } } }
169
- output: { schema: { type: object, properties: { greeting: { type: string } } } }
178
+ - subscribes: order_approval
179
+ publishes: order_approval.done
180
+ input: { schema: { type: object, properties: { orderId: { type: string }, region: { type: string } } } }
181
+ output: { schema: { type: object, properties: { approved: { type: boolean } } } }
170
182
  activities:
171
183
  trigger:
172
184
  type: trigger
185
+ review:
186
+ type: hook
187
+ escalation:
188
+ role: approver
189
+ type: order-approval
190
+ priority: 2
191
+ description: Approve order for dispatch
192
+ metadata:
193
+ orderId: '{trigger.output.data.orderId}'
194
+ region: '{trigger.output.data.region}'
195
+ envelope:
196
+ instructions: Review and approve or reject
173
197
  job:
174
198
  maps:
175
- greeting:
176
- '@pipe':
177
- - ['Hello, ', '{$self.input.data.name}', '!']
178
- - ['{@string.concat}']
179
- transitions: {}
199
+ approved: '{review.hook.data.approved}'
200
+ transitions:
201
+ trigger:
202
+ - to: review
203
+ hooks:
204
+ order_approval.approve:
205
+ - to: review
206
+ conditions:
207
+ match:
208
+ - expected: '{$job.metadata.jid}'
209
+ actual: '{$self.hook.data.id}'
180
210
  `,
181
211
  }],
182
212
  });
183
213
  ```
184
214
 
185
- It appears under **Orchestrate › Graph** and runs the same way a procedural workflow doesdurable, transactional, invocable from the dashboard or API.
215
+ It appears under **Orchestrate › Graph** and routes to a person exactly like the procedural `condition()` abovethe same claim-and-resolve surface, declared in YAML. The `metadata` expressions resolve against the live job at suspension time, so the row carries the real order values.
186
216
 
187
217
  ## Full configuration
188
218
 
@@ -240,7 +270,7 @@ client.events.on('task.completed', (event) => console.log('done:', event.workflo
240
270
  client.events.on('escalation.*', (event) => notifyTeam(event));
241
271
  ```
242
272
 
243
- Every SDK call returns an `LTApiResult` — same status codes, same validation, same RBAC. See the [SDK guide](docs/sdk.md).
273
+ Every SDK call returns an `LTApiResult` — same status codes, same validation, same RBAC. See the [SDK guide](https://github.com/hotmeshio/long-tail/blob/main/docs/sdk.md).
244
274
 
245
275
  ## Deployment
246
276
 
@@ -258,31 +288,31 @@ await start({ database: { connectionString: process.env.DATABASE_URL }, server:
258
288
  const lt = createClient({ auth: { userId: 'service' } });
259
289
  ```
260
290
 
261
- All modes share PostgreSQL and scale independently. See [Cloud Deployment](docs/cloud.md).
291
+ All modes share PostgreSQL and scale independently. See [Cloud Deployment](https://github.com/hotmeshio/long-tail/blob/main/docs/cloud.md).
262
292
 
263
293
  ## Docs
264
294
 
265
295
  | Guide | What it covers |
266
296
  |-------|---------------|
267
- | [The Long Tail Story](docs/story.md) | Why this exists, what accumulates over time |
268
- | [Workflows](docs/workflows.md) | Activities, interceptor, escalation lifecycle, composition |
269
- | [IAM](docs/iam.md) | Identity propagation, service accounts, credential exchange |
270
- | [Dashboard](docs/dashboard.md) | Navigation, key pages, event feed |
271
- | [MCP](docs/mcp.md) | Server registration, tool calls, human queue |
272
- | [Compilation](docs/compilation.md) | Dynamic to deterministic pipeline wizard |
273
- | [Compiler](docs/compiler.md) | `ltc compile` — durable TypeScript to YAML DAGs |
274
- | [CLI](docs/cli.md) | `ltc` — terminal access to workflows, escalations, knowledge, MCP |
275
- | [Escalation Strategies](docs/escalation-strategies.md) | Default, MCP triage, custom handlers |
276
- | [SDK](docs/sdk.md) | Embedded usage, `createClient`, event subscriptions |
277
- | [Architecture](docs/architecture.md) | Project structure, conventions, discovery |
278
- | [Cloud](docs/cloud.md) | AWS ECS, GCP Cloud Run, Docker |
279
- | [Data Model](docs/data.md) | Database schema |
280
-
281
- **Adapters:** [Auth](docs/auth.md) · [Events](docs/events.md) · [Telemetry](docs/telemetry.md) · [Logging](docs/logging.md) · [Maintenance](docs/maintenance.md) · [OAuth](docs/oauth-and-delegation.md)
282
-
283
- **HTTP API:** [Workflows](docs/api/http/workflows.md) · [Tasks](docs/api/http/tasks.md) · [Escalations](docs/api/http/escalations.md) · [YAML Workflows](docs/api/http/yaml-workflows.md) · [Users](docs/api/http/users.md) · [Roles](docs/api/http/roles.md) · [Service Accounts](docs/api/http/service-accounts.md) · [MCP Servers](docs/api/http/mcp-servers.md) · [Pipelines](docs/api/http/pipelines.md) · [Exports](docs/api/http/exports.md)
284
-
285
- **SDK:** [Overview](docs/sdk.md) · [Workflows](docs/api/sdk/workflows.md) · [Tasks](docs/api/sdk/tasks.md) · [Escalations](docs/api/sdk/escalations.md) · [YAML Workflows](docs/api/sdk/yaml-workflows.md) · [MCP](docs/api/sdk/mcp.md) · [Events](docs/api/sdk/events.md)
297
+ | [The Long Tail Story](https://github.com/hotmeshio/long-tail/blob/main/docs/story.md) | Why this exists, what accumulates over time |
298
+ | [Workflows](https://github.com/hotmeshio/long-tail/blob/main/docs/workflows.md) | Activities, interceptor, escalation lifecycle, composition |
299
+ | [IAM](https://github.com/hotmeshio/long-tail/blob/main/docs/iam.md) | Identity propagation, service accounts, credential exchange |
300
+ | [Dashboard](https://github.com/hotmeshio/long-tail/blob/main/docs/dashboard.md) | Navigation, key pages, event feed |
301
+ | [MCP](https://github.com/hotmeshio/long-tail/blob/main/docs/mcp.md) | Server registration, tool calls, human queue |
302
+ | [Compilation](https://github.com/hotmeshio/long-tail/blob/main/docs/compilation.md) | Dynamic to deterministic pipeline wizard |
303
+ | [Compiler](https://github.com/hotmeshio/long-tail/blob/main/docs/compiler.md) | `ltc compile` — durable TypeScript to YAML DAGs |
304
+ | [CLI](https://github.com/hotmeshio/long-tail/blob/main/docs/cli.md) | `ltc` — terminal access to workflows, escalations, knowledge, MCP |
305
+ | [Escalation Strategies](https://github.com/hotmeshio/long-tail/blob/main/docs/escalation-strategies.md) | Default, MCP triage, custom handlers |
306
+ | [SDK](https://github.com/hotmeshio/long-tail/blob/main/docs/sdk.md) | Embedded usage, `createClient`, event subscriptions |
307
+ | [Architecture](https://github.com/hotmeshio/long-tail/blob/main/docs/architecture.md) | Project structure, conventions, discovery |
308
+ | [Cloud](https://github.com/hotmeshio/long-tail/blob/main/docs/cloud.md) | AWS ECS, GCP Cloud Run, Docker |
309
+ | [Data Model](https://github.com/hotmeshio/long-tail/blob/main/docs/data.md) | Database schema |
310
+
311
+ **Adapters:** [Auth](https://github.com/hotmeshio/long-tail/blob/main/docs/auth.md) · [Events](https://github.com/hotmeshio/long-tail/blob/main/docs/events.md) · [Telemetry](https://github.com/hotmeshio/long-tail/blob/main/docs/telemetry.md) · [Logging](https://github.com/hotmeshio/long-tail/blob/main/docs/logging.md) · [Maintenance](https://github.com/hotmeshio/long-tail/blob/main/docs/maintenance.md) · [OAuth](https://github.com/hotmeshio/long-tail/blob/main/docs/oauth-and-delegation.md)
312
+
313
+ **HTTP API:** [Workflows](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/workflows.md) · [Tasks](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/tasks.md) · [Escalations](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/escalations.md) · [YAML Workflows](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/yaml-workflows.md) · [Users](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/users.md) · [Roles](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/roles.md) · [Service Accounts](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/service-accounts.md) · [MCP Servers](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/mcp-servers.md) · [Pipelines](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/pipelines.md) · [Exports](https://github.com/hotmeshio/long-tail/blob/main/docs/api/http/exports.md)
314
+
315
+ **SDK:** [Overview](https://github.com/hotmeshio/long-tail/blob/main/docs/sdk.md) · [Workflows](https://github.com/hotmeshio/long-tail/blob/main/docs/api/sdk/workflows.md) · [Tasks](https://github.com/hotmeshio/long-tail/blob/main/docs/api/sdk/tasks.md) · [Escalations](https://github.com/hotmeshio/long-tail/blob/main/docs/api/sdk/escalations.md) · [YAML Workflows](https://github.com/hotmeshio/long-tail/blob/main/docs/api/sdk/yaml-workflows.md) · [MCP](https://github.com/hotmeshio/long-tail/blob/main/docs/api/sdk/mcp.md) · [Events](https://github.com/hotmeshio/long-tail/blob/main/docs/api/sdk/events.md)
286
316
 
287
317
  ## Contributing
288
318
 
@@ -301,8 +331,8 @@ Open [http://localhost:3000](http://localhost:3000). Example workflows seed the
301
331
  | `engineer` | `l0ngt@1l` | engineer |
302
332
  | `reviewer` | `l0ngt@1l` | reviewer |
303
333
 
304
- See [Contributing](docs/contributing.md).
334
+ See [Contributing](https://github.com/hotmeshio/long-tail/blob/main/docs/contributing.md).
305
335
 
306
336
  ## License
307
337
 
308
- See [LICENSE](LICENSE).
338
+ See [LICENSE](https://github.com/hotmeshio/long-tail/blob/main/LICENSE).
@@ -3,5 +3,5 @@ export { listEscalations, listAvailableEscalations, listDistinctTypes, getEscala
3
3
  export { getEscalation, getEscalationsByWorkflowId, escalateToRole } from './single';
4
4
  export { claimEscalation, releaseEscalation } from './claim';
5
5
  export { releaseExpiredClaims, updatePriority, bulkClaim, bulkAssign, bulkEscalate, bulkTriage } from './bulk';
6
- export { resolveEscalation } from './resolve';
6
+ export { resolveEscalation, resolveBySignalKey } from './resolve';
7
7
  export { findByMetadata, claimByMetadata, resolveByMetadata } from './metadata';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getEscalationStats = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
3
+ exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveBySignalKey = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getEscalationStats = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
4
4
  var create_1 = require("./create");
5
5
  Object.defineProperty(exports, "createEscalation", { enumerable: true, get: function () { return create_1.createEscalation; } });
6
6
  var list_1 = require("./list");
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "bulkEscalate", { enumerable: true, get: function
24
24
  Object.defineProperty(exports, "bulkTriage", { enumerable: true, get: function () { return bulk_1.bulkTriage; } });
25
25
  var resolve_1 = require("./resolve");
26
26
  Object.defineProperty(exports, "resolveEscalation", { enumerable: true, get: function () { return resolve_1.resolveEscalation; } });
27
+ Object.defineProperty(exports, "resolveBySignalKey", { enumerable: true, get: function () { return resolve_1.resolveBySignalKey; } });
27
28
  var metadata_1 = require("./metadata");
28
29
  Object.defineProperty(exports, "findByMetadata", { enumerable: true, get: function () { return metadata_1.findByMetadata; } });
29
30
  Object.defineProperty(exports, "claimByMetadata", { enumerable: true, get: function () { return metadata_1.claimByMetadata; } });
@@ -16,3 +16,13 @@ export declare function resolveEscalation(input: {
16
16
  id: string;
17
17
  resolverPayload: Record<string, any>;
18
18
  }, _auth: LTApiAuth): Promise<LTApiResult>;
19
+ /**
20
+ * Resolve an efficient (atomic) escalation directly by its `signal_key` and
21
+ * resume the waiting workflow in place. For webhook callers that know the
22
+ * deterministic signal id (e.g. `signal-scan-ar-${orderId}`) and want to skip
23
+ * the id lookup. RBAC-scoped to the caller's visible roles.
24
+ */
25
+ export declare function resolveBySignalKey(input: {
26
+ signalKey: string;
27
+ resolverPayload: Record<string, any>;
28
+ }, auth: LTApiAuth): Promise<LTApiResult>;
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.resolveEscalation = resolveEscalation;
37
+ exports.resolveBySignalKey = resolveBySignalKey;
37
38
  const escalationService = __importStar(require("../../services/escalation"));
38
39
  const taskService = __importStar(require("../../services/task"));
39
40
  const escalation_strategy_1 = require("../../services/escalation-strategy");
@@ -41,6 +42,7 @@ const ephemeral_1 = require("../../services/iam/ephemeral");
41
42
  const deployer_1 = require("../../services/yaml-workflow/deployer");
42
43
  const workers_1 = require("../../workers");
43
44
  const defaults_1 = require("../../modules/defaults");
45
+ const helpers_1 = require("./helpers");
44
46
  // ── Orchestrator ─────────────────────────────────────────────────────────
45
47
  /**
46
48
  * Resolve a pending escalation with a human-provided payload.
@@ -76,6 +78,13 @@ async function resolveEscalation(input, _auth) {
76
78
  if (signalRouting?.signalId) {
77
79
  return resolveViaSignalRouting(escalation, resolverPayload);
78
80
  }
81
+ // Path 0: efficient (atomic) escalation — signal_key resumes in place.
82
+ // The row was written inside the workflow's Leg1 checkpoint via
83
+ // `condition(signalId, config)`. The SDK's resolve marks it resolved AND
84
+ // delivers the signal to `signal_key`, resuming THIS job — no re-run.
85
+ if (escalation.signal_key) {
86
+ return resolveViaSignalKey(escalation, resolverPayload);
87
+ }
79
88
  // Path C: escalation strategy may redirect to triage
80
89
  const envelope = await reconstructEnvelope(escalation);
81
90
  const strategy = escalation_strategy_1.escalationStrategyRegistry.current;
@@ -97,6 +106,34 @@ async function resolveEscalation(input, _auth) {
97
106
  return { status: 500, error: err.message };
98
107
  }
99
108
  }
109
+ /**
110
+ * Resolve an efficient (atomic) escalation directly by its `signal_key` and
111
+ * resume the waiting workflow in place. For webhook callers that know the
112
+ * deterministic signal id (e.g. `signal-scan-ar-${orderId}`) and want to skip
113
+ * the id lookup. RBAC-scoped to the caller's visible roles.
114
+ */
115
+ async function resolveBySignalKey(input, auth) {
116
+ try {
117
+ const { signalKey, resolverPayload } = input;
118
+ if (!signalKey)
119
+ return { status: 400, error: 'signalKey is required' };
120
+ if (!resolverPayload)
121
+ return { status: 400, error: 'resolverPayload is required' };
122
+ const escalation = await escalationService.getEscalationBySignalKey(signalKey);
123
+ if (!escalation)
124
+ return { status: 404, error: 'Escalation not found' };
125
+ if (escalation.status !== 'pending')
126
+ return { status: 409, error: 'Escalation not available for resolution' };
127
+ const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
128
+ if (visibleRoles && !visibleRoles.includes(escalation.role)) {
129
+ return { status: 404, error: 'Escalation not found' };
130
+ }
131
+ return resolveViaSignalKey(escalation, resolverPayload);
132
+ }
133
+ catch (err) {
134
+ return { status: 500, error: err.message };
135
+ }
136
+ }
100
137
  // ── Resolution paths ─────────────────────────────────────────────────────
101
138
  /** Path A: lightweight conditionLT signal — inject $escalation_id and signal the running workflow. */
102
139
  async function resolveViaConditionSignal(escalation, resolverPayload) {
@@ -107,6 +144,21 @@ async function resolveViaConditionSignal(escalation, resolverPayload) {
107
144
  // Event published by service layer (services/escalation/crud.ts)
108
145
  return signaledResult(escalation, escalation.workflow_id);
109
146
  }
147
+ /**
148
+ * Path 0: efficient escalation — resolve by `signal_key`. The SDK delivers the
149
+ * signal to the waiting `condition()` AND marks the row resolved in one
150
+ * transaction, so the original job resumes in place (no re-run, no separate
151
+ * resolve activity). Password fields are redacted before they enter the signal.
152
+ */
153
+ async function resolveViaSignalKey(escalation, resolverPayload) {
154
+ const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
155
+ const resolved = await escalationService.resolveEscalation(escalation.id, signalPayload);
156
+ if (!resolved) {
157
+ return { status: 409, error: 'Escalation not available for resolution' };
158
+ }
159
+ // Event published by service layer (services/escalation/crud.ts)
160
+ return signaledResult(escalation, escalation.workflow_id || '');
161
+ }
110
162
  /** Path B: waitFor signal escalation — signal via YAML engine or Durable handle. */
111
163
  async function resolveViaSignalRouting(escalation, resolverPayload) {
112
164
  const signalRouting = escalation.metadata.signal_routing;
@@ -1,3 +1,4 @@
1
+ import type { LTEvent } from '../types';
1
2
  import type { LTApiResult } from '../types/sdk';
2
3
  export declare function listTopics(input: {
3
4
  category?: string;
@@ -27,10 +28,19 @@ export declare function deleteTopic(input: {
27
28
  * Literal segments must match exactly.
28
29
  */
29
30
  export declare function isValidVariant(pattern: string, subject: string): boolean;
31
+ /**
32
+ * A publishable event envelope. The request body IS the event: any `LTEvent`
33
+ * field the caller wants to set (`id`, `source`, `data`, and — for system
34
+ * families — `workflowId` / `workflowName` / `taskQueue` / `status` / etc.),
35
+ * minus the server-managed ones (`type` is derived from the subject, `timestamp`
36
+ * is stamped). `subject` is an optional concrete variant of a wildcard topic.
37
+ *
38
+ * One typed object, infinitely extensible — no per-field API parameters.
39
+ */
40
+ export type PublishEventInput = Partial<Omit<LTEvent, 'type' | 'timestamp'>> & {
41
+ subject?: string;
42
+ };
30
43
  export declare function publishTopic(input: {
31
44
  topic: string;
32
- subject?: string;
33
- eventId?: string;
34
- data: Record<string, any>;
35
- source?: string;
45
+ event?: PublishEventInput;
36
46
  }): Promise<LTApiResult>;
@@ -129,19 +129,19 @@ function isValidVariant(pattern, subject) {
129
129
  }
130
130
  async function publishTopic(input) {
131
131
  try {
132
- const publishSubject = input.subject || input.topic;
132
+ const { subject, ...fields } = input.event ?? {};
133
+ const publishSubject = subject || input.topic;
133
134
  // Validate subject is a valid variant of the topic pattern
134
- if (input.subject && !isValidVariant(input.topic, input.subject)) {
135
- return { status: 400, error: `Subject "${input.subject}" does not match topic pattern "${input.topic}"` };
135
+ if (subject && !isValidVariant(input.topic, subject)) {
136
+ return { status: 400, error: `Subject "${subject}" does not match topic pattern "${input.topic}"` };
136
137
  }
138
+ // The envelope is the caller's; the server owns type/timestamp (and mints id
139
+ // downstream in eventRegistry.publish if the caller didn't supply one).
137
140
  const event = {
138
- id: input.eventId,
141
+ ...fields,
139
142
  type: publishSubject,
140
- source: input.source || 'dashboard',
141
- workflowId: '',
142
- workflowName: '',
143
- taskQueue: '',
144
- data: input.data,
143
+ source: fields.source || 'dashboard',
144
+ data: fields.data ?? {},
145
145
  timestamp: new Date().toISOString(),
146
146
  };
147
147
  await events_1.eventRegistry.publish(event);
@@ -30,9 +30,15 @@ class LTEventRegistry {
30
30
  async publish(event) {
31
31
  if (!this.adapters.length)
32
32
  return;
33
+ // The manager guarantees the universal envelope fields: mint an idempotent
34
+ // id and stamp a timestamp when the publisher (e.g. a custom app event) did
35
+ // not supply them. `type` is the publisher's responsibility.
33
36
  if (!event.id) {
34
37
  event.id = `evt-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`;
35
38
  }
39
+ if (!event.timestamp) {
40
+ event.timestamp = new Date().toISOString();
41
+ }
36
42
  await Promise.allSettled(this.adapters.map((a) => a.publish(event).catch((err) => {
37
43
  logger_1.loggerRegistry.error(`[lt-events] adapter publish failed: ${err?.message}`);
38
44
  })));
@@ -103,12 +103,10 @@ function publishActivityEvent(params) {
103
103
  */
104
104
  function publishKnowledgeEvent(params) {
105
105
  const action = params.type.split('.')[1];
106
+ // No workflow context — knowledge events carry only the minimal envelope + data.
106
107
  return fireAndForget({
107
108
  type: `system.knowledge.${params.domain}.${action}`,
108
109
  source: 'knowledge',
109
- workflowId: '',
110
- workflowName: '',
111
- taskQueue: '',
112
110
  data: { domain: params.domain, key: params.key },
113
111
  timestamp: new Date().toISOString(),
114
112
  });
@@ -124,12 +122,10 @@ function publishFileEvent(params) {
124
122
  const dotIdx = filename.lastIndexOf('.');
125
123
  const name = dotIdx > 0 ? filename.slice(0, dotIdx) : filename;
126
124
  const extension = dotIdx > 0 ? filename.slice(dotIdx + 1) : '';
125
+ // No workflow context — file events carry only the minimal envelope + data.
127
126
  return fireAndForget({
128
127
  type: `system.file.${action}`,
129
128
  source: 'file-storage',
130
- workflowId: '',
131
- workflowName: '',
132
- taskQueue: '',
133
129
  data: {
134
130
  path: params.path,
135
131
  name,
@@ -147,12 +143,12 @@ function publishFileEvent(params) {
147
143
  */
148
144
  function publishAgentEvent(params) {
149
145
  const action = params.type.replace('agent.', '');
146
+ // Agents have no task queue; agentId/agentName ride workflowId/workflowName for routing parity.
150
147
  return fireAndForget({
151
148
  type: `system.agent.${params.agentName}.${action}`,
152
149
  source: 'agent',
153
150
  workflowId: params.agentId,
154
151
  workflowName: params.agentName,
155
- taskQueue: '',
156
152
  status: params.status,
157
153
  data: params.data,
158
154
  timestamp: new Date().toISOString(),
@@ -27,9 +27,9 @@ function mapSystemEvent(event) {
27
27
  return {
28
28
  type: event.type,
29
29
  source: 'sdk',
30
- workflowId: row.workflow_id || event.workflow_id || '',
31
- workflowName: row.workflow_type || '',
32
- taskQueue: row.task_queue || '',
30
+ workflowId: row.workflow_id || event.workflow_id || undefined,
31
+ workflowName: row.workflow_type || undefined,
32
+ taskQueue: row.task_queue || undefined,
33
33
  escalationId: row.id || segments[2],
34
34
  originId: row.origin_id || event.origin_id || undefined,
35
35
  status: ESCALATION_STATUS_BY_VERB[verb] ?? verb,
@@ -42,9 +42,8 @@ function mapSystemEvent(event) {
42
42
  return {
43
43
  type: event.type,
44
44
  source: 'sdk',
45
- workflowId: event.workflow_id || '',
46
- workflowName: '',
47
- taskQueue: event.data?.taskQueue || '',
45
+ workflowId: event.workflow_id || undefined,
46
+ taskQueue: event.data?.taskQueue || undefined,
48
47
  data: event.data,
49
48
  timestamp: event.ts,
50
49
  };
@@ -36,11 +36,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.registerResolveRoutes = registerResolveRoutes;
37
37
  const api = __importStar(require("../../api/escalations"));
38
38
  function registerResolveRoutes(router) {
39
+ /**
40
+ * POST /api/escalations/resolve-by-signal-key
41
+ * Resolve an efficient (atomic) escalation by its signal_key and resume the
42
+ * waiting workflow in place. For webhook callers that know the deterministic
43
+ * signal id. Literal single-segment path — registered before /:id/resolve so
44
+ * it is never shadowed by the parameterized route.
45
+ * Body: { signalKey: string, resolverPayload: Record<string, any> }
46
+ */
47
+ router.post('/resolve-by-signal-key', async (req, res) => {
48
+ const result = await api.resolveBySignalKey({ signalKey: req.body?.signalKey, resolverPayload: req.body?.resolverPayload }, req.auth);
49
+ res.status(result.status).json(result.data ?? { error: result.error });
50
+ });
39
51
  /**
40
52
  * POST /api/escalations/:id/resolve
41
- * Start a new workflow with resolver data to re-run the failed step.
42
- * The interceptor in the new workflow resolves the escalation record
43
- * and signals back to the orchestrator (if any) on success.
53
+ * Resolve a pending escalation with a human-provided payload. Routes by
54
+ * escalation shape: efficient (signal_key) resumes the job in place; legacy
55
+ * paths signal via routing metadata or re-run the original workflow.
44
56
  * Body: { resolverPayload: Record<string, any> }
45
57
  */
46
58
  router.post('/:id/resolve', async (req, res) => {
@@ -92,12 +92,10 @@ router.delete('/by-name/:topic', async (req, res) => {
92
92
  * Publish a test event to the event bus with the given topic and payload.
93
93
  */
94
94
  router.post('/by-name/:topic/publish', async (req, res) => {
95
+ // The request body IS the event envelope (Partial<LTEvent> + optional subject).
95
96
  const result = await api.publishTopic({
96
97
  topic: decodeURIComponent(req.params.topic),
97
- subject: req.body.subject || undefined,
98
- eventId: req.body.eventId || undefined,
99
- data: req.body.data ?? {},
100
- source: req.body.source ?? 'dashboard',
98
+ event: (req.body ?? {}),
101
99
  });
102
100
  res.status(result.status).json(result.data ?? { error: result.error });
103
101
  });
@@ -2,21 +2,44 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.applyInputMapping = applyInputMapping;
4
4
  /**
5
- * Resolve a template string like "{event.data.orderId}" against an event object.
6
- * Returns the resolved value, or the raw template if the path doesn't exist.
5
+ * Resolve a dotted path like "event.data.orderId" against `{ event }`.
6
+ * Returns the resolved value, or `undefined` if any segment is missing.
7
7
  */
8
- function resolveTemplate(template, event) {
9
- const match = template.match(/^\{(.+)\}$/);
10
- if (!match)
11
- return template;
12
- const path = match[1].split('.');
8
+ function resolvePath(path, event) {
9
+ const segments = path.trim().split('.');
13
10
  let current = { event };
14
- for (const segment of path) {
11
+ for (const segment of segments) {
15
12
  if (current == null || typeof current !== 'object')
16
- return template;
13
+ return undefined;
17
14
  current = current[segment];
18
15
  }
19
- return current ?? template;
16
+ return current;
17
+ }
18
+ /**
19
+ * Resolve a template string against an event.
20
+ *
21
+ * - A string that is exactly one token resolving to a **non-scalar**
22
+ * (object/array) returns that value unchanged — full-object resolution,
23
+ * e.g. `"{event.data}"` → the whole data object.
24
+ * - Every other string is interpolated **inline**: each `{path}` is replaced by
25
+ * `String(resolved)`. This covers tokens embedded in surrounding text, multiple
26
+ * tokens in one string, and single tokens that resolve to a scalar — e.g.
27
+ * `"https://host/lt?entity={event.workflowName}&q={event.workflowId}"`.
28
+ * A token whose path does not exist is left verbatim, so an unresolved
29
+ * `{event.data.missing}` passes through unchanged (other tokens still resolve).
30
+ */
31
+ function resolveTemplate(template, event) {
32
+ const exact = template.match(/^\{([^}]+)\}$/);
33
+ if (exact) {
34
+ const resolved = resolvePath(exact[1], event);
35
+ if (resolved !== null && resolved !== undefined && typeof resolved === 'object') {
36
+ return resolved;
37
+ }
38
+ }
39
+ return template.replace(/\{([^}]+)\}/g, (original, path) => {
40
+ const resolved = resolvePath(path, event);
41
+ return resolved !== null && resolved !== undefined ? String(resolved) : original;
42
+ });
20
43
  }
21
44
  /**
22
45
  * Recursively apply input mapping templates against an event.