@hotmeshio/long-tail 0.5.4 → 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 (166) hide show
  1. package/README.md +79 -49
  2. package/build/api/topics.d.ts +14 -4
  3. package/build/api/topics.js +9 -9
  4. package/build/lib/events/index.js +6 -0
  5. package/build/lib/events/publish.js +3 -7
  6. package/build/lib/events/system-events.js +5 -6
  7. package/build/routes/topics.js +2 -4
  8. package/build/services/agent/input-mapper.js +33 -10
  9. package/build/services/interceptor/index.js +25 -6
  10. package/build/system/mcp-servers/events.js +1 -3
  11. package/build/types/events.d.ts +90 -23
  12. package/dashboard/dist/assets/{AdminDashboard-CMzR4d-w.js → AdminDashboard-DIQEViVY.js} +2 -2
  13. package/dashboard/dist/assets/{AdminDashboard-CMzR4d-w.js.map → AdminDashboard-DIQEViVY.js.map} +1 -1
  14. package/dashboard/dist/assets/{AgentConfigPage-DHCpN4xk.js → AgentConfigPage-Caf5-th-.js} +2 -2
  15. package/dashboard/dist/assets/{AgentConfigPage-DHCpN4xk.js.map → AgentConfigPage-Caf5-th-.js.map} +1 -1
  16. package/dashboard/dist/assets/{AgentDetailPage-CSrejvSl.js → AgentDetailPage-_a4tKaM3.js} +2 -2
  17. package/dashboard/dist/assets/{AgentDetailPage-CSrejvSl.js.map → AgentDetailPage-_a4tKaM3.js.map} +1 -1
  18. package/dashboard/dist/assets/{AgentsPage-ntuzWHCD.js → AgentsPage-BNlAkEIK.js} +2 -2
  19. package/dashboard/dist/assets/{AgentsPage-ntuzWHCD.js.map → AgentsPage-BNlAkEIK.js.map} +1 -1
  20. package/dashboard/dist/assets/{AvailableEscalationsPage-Ci24fdxC.js → AvailableEscalationsPage-Cqy5ECm4.js} +2 -2
  21. package/dashboard/dist/assets/{AvailableEscalationsPage-Ci24fdxC.js.map → AvailableEscalationsPage-Cqy5ECm4.js.map} +1 -1
  22. package/dashboard/dist/assets/{BotPicker-lj42d48P.js → BotPicker-DBvd1yFL.js} +2 -2
  23. package/dashboard/dist/assets/{BotPicker-lj42d48P.js.map → BotPicker-DBvd1yFL.js.map} +1 -1
  24. package/dashboard/dist/assets/{CapabilitiesPage-Bea9yLmU.js → CapabilitiesPage-BIdG9RTG.js} +2 -2
  25. package/dashboard/dist/assets/{CapabilitiesPage-Bea9yLmU.js.map → CapabilitiesPage-BIdG9RTG.js.map} +1 -1
  26. package/dashboard/dist/assets/{CollapsibleSection-BeBsI1M4.js → CollapsibleSection-CggyKhyZ.js} +2 -2
  27. package/dashboard/dist/assets/{CollapsibleSection-BeBsI1M4.js.map → CollapsibleSection-CggyKhyZ.js.map} +1 -1
  28. package/dashboard/dist/assets/{CredentialsPage-BDzFa3HV.js → CredentialsPage-B4sXGLRY.js} +2 -2
  29. package/dashboard/dist/assets/{CredentialsPage-BDzFa3HV.js.map → CredentialsPage-B4sXGLRY.js.map} +1 -1
  30. package/dashboard/dist/assets/{CronLabel-DeNm1I4r.js → CronLabel-GcS5qzZr.js} +2 -2
  31. package/dashboard/dist/assets/{CronLabel-DeNm1I4r.js.map → CronLabel-GcS5qzZr.js.map} +1 -1
  32. package/dashboard/dist/assets/{CustomDurationPicker-C2OH9YcV.js → CustomDurationPicker-CUYE36-K.js} +2 -2
  33. package/dashboard/dist/assets/{CustomDurationPicker-C2OH9YcV.js.map → CustomDurationPicker-CUYE36-K.js.map} +1 -1
  34. package/dashboard/dist/assets/{ElapsedCell-WdzqA0dR.js → ElapsedCell-BFOMsgwF.js} +2 -2
  35. package/dashboard/dist/assets/{ElapsedCell-WdzqA0dR.js.map → ElapsedCell-BFOMsgwF.js.map} +1 -1
  36. package/dashboard/dist/assets/{EscalationsOverview-BIEeflEV.js → EscalationsOverview-BfjW6haL.js} +2 -2
  37. package/dashboard/dist/assets/{EscalationsOverview-BIEeflEV.js.map → EscalationsOverview-BfjW6haL.js.map} +1 -1
  38. package/dashboard/dist/assets/{EventTable-NIhWP__B.js → EventTable-N0XmLk3C.js} +2 -2
  39. package/dashboard/dist/assets/{EventTable-NIhWP__B.js.map → EventTable-N0XmLk3C.js.map} +1 -1
  40. package/dashboard/dist/assets/{GraphInvokePage-DtAW8ilc.js → GraphInvokePage-DHiGHiCk.js} +2 -2
  41. package/dashboard/dist/assets/{GraphInvokePage-DtAW8ilc.js.map → GraphInvokePage-DHiGHiCk.js.map} +1 -1
  42. package/dashboard/dist/assets/{HomePage-HsO-M8ub.js → HomePage-jckQolbx.js} +2 -2
  43. package/dashboard/dist/assets/{HomePage-HsO-M8ub.js.map → HomePage-jckQolbx.js.map} +1 -1
  44. package/dashboard/dist/assets/{ListToolbar-DqaRlXrF.js → ListToolbar-B71DqaXr.js} +2 -2
  45. package/dashboard/dist/assets/{ListToolbar-DqaRlXrF.js.map → ListToolbar-B71DqaXr.js.map} +1 -1
  46. package/dashboard/dist/assets/{McpOverview-B4GsHxij.js → McpOverview-CPD6NRGt.js} +2 -2
  47. package/dashboard/dist/assets/{McpOverview-B4GsHxij.js.map → McpOverview-CPD6NRGt.js.map} +1 -1
  48. package/dashboard/dist/assets/{McpQueryDetailPage-DZKpzWST.js → McpQueryDetailPage-YyqXw6Dt.js} +2 -2
  49. package/dashboard/dist/assets/{McpQueryDetailPage-DZKpzWST.js.map → McpQueryDetailPage-YyqXw6Dt.js.map} +1 -1
  50. package/dashboard/dist/assets/{McpQueryPage-B-8WR3GV.js → McpQueryPage-BWf45EYD.js} +2 -2
  51. package/dashboard/dist/assets/{McpQueryPage-B-8WR3GV.js.map → McpQueryPage-BWf45EYD.js.map} +1 -1
  52. package/dashboard/dist/assets/{McpRunDetailPage-Dt7Ai3qB.js → McpRunDetailPage-Cv43tkjz.js} +2 -2
  53. package/dashboard/dist/assets/{McpRunDetailPage-Dt7Ai3qB.js.map → McpRunDetailPage-Cv43tkjz.js.map} +1 -1
  54. package/dashboard/dist/assets/{McpRunsPage-Bdke-HoQ.js → McpRunsPage-BFards4K.js} +2 -2
  55. package/dashboard/dist/assets/{McpRunsPage-Bdke-HoQ.js.map → McpRunsPage-BFards4K.js.map} +1 -1
  56. package/dashboard/dist/assets/{OperatorDashboard-VZ97mufd.js → OperatorDashboard-BJZxuOKI.js} +2 -2
  57. package/dashboard/dist/assets/{OperatorDashboard-VZ97mufd.js.map → OperatorDashboard-BJZxuOKI.js.map} +1 -1
  58. package/dashboard/dist/assets/{ProcessDetailPage-BLfLfmWX.js → ProcessDetailPage-BjkLfyrm.js} +2 -2
  59. package/dashboard/dist/assets/{ProcessDetailPage-BLfLfmWX.js.map → ProcessDetailPage-BjkLfyrm.js.map} +1 -1
  60. package/dashboard/dist/assets/{ProcessesListPage-DG54t-Nd.js → ProcessesListPage-DOzeduP3.js} +2 -2
  61. package/dashboard/dist/assets/{ProcessesListPage-DG54t-Nd.js.map → ProcessesListPage-DOzeduP3.js.map} +1 -1
  62. package/dashboard/dist/assets/{RolesPage-Bdlv3DdW.js → RolesPage-DSSQrGTh.js} +2 -2
  63. package/dashboard/dist/assets/{RolesPage-Bdlv3DdW.js.map → RolesPage-DSSQrGTh.js.map} +1 -1
  64. package/dashboard/dist/assets/{RunAsSelector-BFxxMvL3.js → RunAsSelector-CM7lk1YB.js} +2 -2
  65. package/dashboard/dist/assets/{RunAsSelector-BFxxMvL3.js.map → RunAsSelector-CM7lk1YB.js.map} +1 -1
  66. package/dashboard/dist/assets/{SwimlaneTimeline-BZmad7WQ.js → SwimlaneTimeline-DtCgJNpV.js} +2 -2
  67. package/dashboard/dist/assets/{SwimlaneTimeline-BZmad7WQ.js.map → SwimlaneTimeline-DtCgJNpV.js.map} +1 -1
  68. package/dashboard/dist/assets/{TaskDetailPage-CLl2mgBC.js → TaskDetailPage-BC9xnyLT.js} +2 -2
  69. package/dashboard/dist/assets/{TaskDetailPage-CLl2mgBC.js.map → TaskDetailPage-BC9xnyLT.js.map} +1 -1
  70. package/dashboard/dist/assets/{TasksListPage-DoHXxZF-.js → TasksListPage-DtbLfUS-.js} +2 -2
  71. package/dashboard/dist/assets/{TasksListPage-DoHXxZF-.js.map → TasksListPage-DtbLfUS-.js.map} +1 -1
  72. package/dashboard/dist/assets/{TimeAgo-DZF9w8Rl.js → TimeAgo-xrDlFnPy.js} +2 -2
  73. package/dashboard/dist/assets/{TimeAgo-DZF9w8Rl.js.map → TimeAgo-xrDlFnPy.js.map} +1 -1
  74. package/dashboard/dist/assets/{TimestampCell-DPpoTdrw.js → TimestampCell-BneATvjR.js} +2 -2
  75. package/dashboard/dist/assets/{TimestampCell-DPpoTdrw.js.map → TimestampCell-BneATvjR.js.map} +1 -1
  76. package/dashboard/dist/assets/{ToolTestPanel-DtAgJQfr.js → ToolTestPanel-1lQ_GQod.js} +2 -2
  77. package/dashboard/dist/assets/{ToolTestPanel-DtAgJQfr.js.map → ToolTestPanel-1lQ_GQod.js.map} +1 -1
  78. package/dashboard/dist/assets/TopicDetailPage-CDtlXyut.js +9 -0
  79. package/dashboard/dist/assets/TopicDetailPage-CDtlXyut.js.map +1 -0
  80. package/dashboard/dist/assets/{TopicsPage-Bc-4ne6V.js → TopicsPage-COX2kDKK.js} +2 -2
  81. package/dashboard/dist/assets/{TopicsPage-Bc-4ne6V.js.map → TopicsPage-COX2kDKK.js.map} +1 -1
  82. package/dashboard/dist/assets/{UserName-O2Q4-E6E.js → UserName-Cb2WvhCN.js} +2 -2
  83. package/dashboard/dist/assets/{UserName-O2Q4-E6E.js.map → UserName-Cb2WvhCN.js.map} +1 -1
  84. package/dashboard/dist/assets/{WorkflowExecutionPage-DEDsBmn1.js → WorkflowExecutionPage-BWCTOTXF.js} +2 -2
  85. package/dashboard/dist/assets/{WorkflowExecutionPage-DEDsBmn1.js.map → WorkflowExecutionPage-BWCTOTXF.js.map} +1 -1
  86. package/dashboard/dist/assets/{WorkflowsDashboard-Bcf17vCt.js → WorkflowsDashboard-DOaDSC98.js} +2 -2
  87. package/dashboard/dist/assets/{WorkflowsDashboard-Bcf17vCt.js.map → WorkflowsDashboard-DOaDSC98.js.map} +1 -1
  88. package/dashboard/dist/assets/{WorkflowsOverview-Cwo2rqGT.js → WorkflowsOverview-CeE7a51J.js} +2 -2
  89. package/dashboard/dist/assets/{WorkflowsOverview-Cwo2rqGT.js.map → WorkflowsOverview-CeE7a51J.js.map} +1 -1
  90. package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bv8ZFwO-.js → YamlWorkflowDetailPage-cNpR0PHg.js} +2 -2
  91. package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bv8ZFwO-.js.map → YamlWorkflowDetailPage-cNpR0PHg.js.map} +1 -1
  92. package/dashboard/dist/assets/{YamlWorkflowsPage-DOiEQDOq.js → YamlWorkflowsPage-DWvNfzji.js} +2 -2
  93. package/dashboard/dist/assets/{YamlWorkflowsPage-DOiEQDOq.js.map → YamlWorkflowsPage-DWvNfzji.js.map} +1 -1
  94. package/dashboard/dist/assets/{agents-D09G0HCv.js → agents-BvTiBKBQ.js} +2 -2
  95. package/dashboard/dist/assets/{agents-D09G0HCv.js.map → agents-BvTiBKBQ.js.map} +1 -1
  96. package/dashboard/dist/assets/{bots-D0LhyZZM.js → bots-CeMR1Lnp.js} +2 -2
  97. package/dashboard/dist/assets/{bots-D0LhyZZM.js.map → bots-CeMR1Lnp.js.map} +1 -1
  98. package/dashboard/dist/assets/{capabilities-DON4-NXs.js → capabilities-D7uJlnxj.js} +2 -2
  99. package/dashboard/dist/assets/{capabilities-DON4-NXs.js.map → capabilities-D7uJlnxj.js.map} +1 -1
  100. package/dashboard/dist/assets/{controlplane-Bihd1kXf.js → controlplane-Cf6lZsSF.js} +2 -2
  101. package/dashboard/dist/assets/{controlplane-Bihd1kXf.js.map → controlplane-Cf6lZsSF.js.map} +1 -1
  102. package/dashboard/dist/assets/{escalation-CP2XbdXK.js → escalation-BCFBWGP-.js} +2 -2
  103. package/dashboard/dist/assets/{escalation-CP2XbdXK.js.map → escalation-BCFBWGP-.js.map} +1 -1
  104. package/dashboard/dist/assets/{escalation-columns-9aw8Y4qq.js → escalation-columns-Do7k4fte.js} +2 -2
  105. package/dashboard/dist/assets/{escalation-columns-9aw8Y4qq.js.map → escalation-columns-Do7k4fte.js.map} +1 -1
  106. package/dashboard/dist/assets/{index-DgLZ8Ix5.js → index-8C9YLM_u.js} +2 -2
  107. package/dashboard/dist/assets/{index-DgLZ8Ix5.js.map → index-8C9YLM_u.js.map} +1 -1
  108. package/dashboard/dist/assets/{index-CCup2uaP.js → index-B216fPN_.js} +2 -2
  109. package/dashboard/dist/assets/{index-CCup2uaP.js.map → index-B216fPN_.js.map} +1 -1
  110. package/dashboard/dist/assets/index-BCfzac3h.css +1 -0
  111. package/dashboard/dist/assets/{index-Cb7aSzox.js → index-BDhEpETz.js} +2 -2
  112. package/dashboard/dist/assets/{index-Cb7aSzox.js.map → index-BDhEpETz.js.map} +1 -1
  113. package/dashboard/dist/assets/{index-_JsRJPds.js → index-BS5pcmBR.js} +2 -2
  114. package/dashboard/dist/assets/{index-_JsRJPds.js.map → index-BS5pcmBR.js.map} +1 -1
  115. package/dashboard/dist/assets/{index-B-BK3vtk.js → index-BUu3WKvk.js} +2 -2
  116. package/dashboard/dist/assets/{index-B-BK3vtk.js.map → index-BUu3WKvk.js.map} +1 -1
  117. package/dashboard/dist/assets/{index-BTp73vYK.js → index-C1p_rvkY.js} +2 -2
  118. package/dashboard/dist/assets/{index-BTp73vYK.js.map → index-C1p_rvkY.js.map} +1 -1
  119. package/dashboard/dist/assets/{index-CMRW_PE-.js → index-C51Jzvoz.js} +2 -2
  120. package/dashboard/dist/assets/{index-CMRW_PE-.js.map → index-C51Jzvoz.js.map} +1 -1
  121. package/dashboard/dist/assets/{index-BBBGETMs.js → index-D0fjUKvx.js} +2 -2
  122. package/dashboard/dist/assets/{index-BBBGETMs.js.map → index-D0fjUKvx.js.map} +1 -1
  123. package/dashboard/dist/assets/{index-BInTEEIX.js → index-D3SjB3UM.js} +2 -2
  124. package/dashboard/dist/assets/{index-BInTEEIX.js.map → index-D3SjB3UM.js.map} +1 -1
  125. package/dashboard/dist/assets/{index-rbuNUyAh.js → index-DSjbScPX.js} +2 -2
  126. package/dashboard/dist/assets/{index-rbuNUyAh.js.map → index-DSjbScPX.js.map} +1 -1
  127. package/dashboard/dist/assets/{index-B7lEd0cY.js → index-eWNx4UQ8.js} +3 -3
  128. package/dashboard/dist/assets/{index-B7lEd0cY.js.map → index-eWNx4UQ8.js.map} +1 -1
  129. package/dashboard/dist/assets/{index-H5Yb8CY2.js → index-gKm3HAgO.js} +2 -2
  130. package/dashboard/dist/assets/{index-H5Yb8CY2.js.map → index-gKm3HAgO.js.map} +1 -1
  131. package/dashboard/dist/assets/{index-Cjb8ulHm.js → index-oulO44Iy.js} +2 -2
  132. package/dashboard/dist/assets/{index-Cjb8ulHm.js.map → index-oulO44Iy.js.map} +1 -1
  133. package/dashboard/dist/assets/{knowledge-Bl_KaoKJ.js → knowledge-Bje8_ndK.js} +2 -2
  134. package/dashboard/dist/assets/{knowledge-Bl_KaoKJ.js.map → knowledge-Bje8_ndK.js.map} +1 -1
  135. package/dashboard/dist/assets/{mcp-DXbFGoA8.js → mcp-Bpl6p11J.js} +2 -2
  136. package/dashboard/dist/assets/{mcp-DXbFGoA8.js.map → mcp-Bpl6p11J.js.map} +1 -1
  137. package/dashboard/dist/assets/{mcp-query-Bg69DF2x.js → mcp-query-DMXj-t5f.js} +2 -2
  138. package/dashboard/dist/assets/{mcp-query-Bg69DF2x.js.map → mcp-query-DMXj-t5f.js.map} +1 -1
  139. package/dashboard/dist/assets/{pipelines-DK9LTg9F.js → pipelines-CFh9nBxn.js} +2 -2
  140. package/dashboard/dist/assets/{pipelines-DK9LTg9F.js.map → pipelines-CFh9nBxn.js.map} +1 -1
  141. package/dashboard/dist/assets/{roles-BrsBN4hO.js → roles-CtjQQWSQ.js} +2 -2
  142. package/dashboard/dist/assets/{roles-BrsBN4hO.js.map → roles-CtjQQWSQ.js.map} +1 -1
  143. package/dashboard/dist/assets/{tasks-CwjvPECN.js → tasks-Nz9lVCT1.js} +2 -2
  144. package/dashboard/dist/assets/{tasks-CwjvPECN.js.map → tasks-Nz9lVCT1.js.map} +1 -1
  145. package/dashboard/dist/assets/topics-CiG2N8Z_.js +2 -0
  146. package/dashboard/dist/assets/{topics-BrwkmaFR.js.map → topics-CiG2N8Z_.js.map} +1 -1
  147. package/dashboard/dist/assets/{useEventHooks-Cd1GM1NG.js → useEventHooks-Bimqt7hU.js} +2 -2
  148. package/dashboard/dist/assets/{useEventHooks-Cd1GM1NG.js.map → useEventHooks-Bimqt7hU.js.map} +1 -1
  149. package/dashboard/dist/assets/{useNamespace-D9lghZ25.js → useNamespace-FrG0Monz.js} +2 -2
  150. package/dashboard/dist/assets/{useNamespace-D9lghZ25.js.map → useNamespace-FrG0Monz.js.map} +1 -1
  151. package/dashboard/dist/assets/{useYamlActivityEvents-vOhAwmKO.js → useYamlActivityEvents-JmD4dH5n.js} +2 -2
  152. package/dashboard/dist/assets/{useYamlActivityEvents-vOhAwmKO.js.map → useYamlActivityEvents-JmD4dH5n.js.map} +1 -1
  153. package/dashboard/dist/assets/{users-ChQ7soaq.js → users-DgyonYq6.js} +2 -2
  154. package/dashboard/dist/assets/{users-ChQ7soaq.js.map → users-DgyonYq6.js.map} +1 -1
  155. package/dashboard/dist/assets/{workflows-BLKji1_1.js → workflows-BOwGINtF.js} +2 -2
  156. package/dashboard/dist/assets/{workflows-BLKji1_1.js.map → workflows-BOwGINtF.js.map} +1 -1
  157. package/dashboard/dist/assets/{yaml-workflows-BUhMfdaw.js → yaml-workflows-I1tcjCFK.js} +2 -2
  158. package/dashboard/dist/assets/{yaml-workflows-BUhMfdaw.js.map → yaml-workflows-I1tcjCFK.js.map} +1 -1
  159. package/dashboard/dist/index.html +2 -2
  160. package/docs/data.md +61 -38
  161. package/docs/events.md +34 -13
  162. package/package.json +2 -2
  163. package/dashboard/dist/assets/TopicDetailPage-W9RKkNNp.js +0 -9
  164. package/dashboard/dist/assets/TopicDetailPage-W9RKkNNp.js.map +0 -1
  165. package/dashboard/dist/assets/index-BFyzZGtv.css +0 -1
  166. 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).
@@ -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
  };
@@ -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.
@@ -100,12 +100,31 @@ function createLTInterceptor(options) {
100
100
  retry: { maximumAttempts: 3 },
101
101
  });
102
102
  const envelope = (0, state_1.extractEnvelope)(ctx);
103
- // 2. The gate is REGISTRATION in lt_config_workflows, not the runtime
104
- // `certified` flag. Every registered workflow gets task tracking and
105
- // escalation wiring; unregistered ad-hoc durable workflows get
106
- // ToolContext + lifecycle events only. The `certified` flag is
107
- // propagated by executeLT and gates parent/child signal composition
108
- // (services/orchestrator), not task-record creation.
103
+ // 2. Opt-in gate the envelope drives behavior. Certification is opt-in:
104
+ // only workflows explicitly marked `metadata.certified === true` pay for
105
+ // the config lookup, task tracking, and escalation wiring. Every other
106
+ // durable workflow is a pure pass-through (ToolContext for identity +
107
+ // fire-and-forget lifecycle events) NO proxyActivity, NO DB lookup, NO
108
+ // extra durable leg. The invoke layer (services/workflow-invocation)
109
+ // stamps certified:true for certified workflows; executeLT propagates it
110
+ // to children. This keeps plain durable workflows free of interceptor cost.
111
+ if (envelope?.metadata?.certified !== true) {
112
+ const toolCtx = (0, envelope_1.buildToolContextFromEnvelope)(envelope, wf.workflowId, wf.workflowTrace, wf.workflowSpan);
113
+ const taskQueue0 = (0, lifecycle_1.deriveTaskQueue)(wf);
114
+ (0, lifecycle_1.publishStartedEvents)(wf, taskQueue0, undefined, wf.workflowId);
115
+ const result0 = toolCtx ? await (0, context_2.runWithToolContext)(toolCtx, next) : await next();
116
+ (0, publish_1.publishWorkflowEvent)({
117
+ type: 'workflow.completed',
118
+ source: 'interceptor',
119
+ workflowId: wf.workflowId,
120
+ workflowName: wf.workflowName,
121
+ taskQueue: taskQueue0,
122
+ status: 'completed',
123
+ });
124
+ return result0;
125
+ }
126
+ // 3. Opted in (certified === true): load config. A certified workflow with
127
+ // no registered config still falls back to ToolContext + events only.
109
128
  const wfConfig = await activities.ltGetWorkflowConfig(wf.workflowName);
110
129
  if (!wfConfig) {
111
130
  const toolCtx = (0, envelope_1.buildToolContextFromEnvelope)(envelope, wf.workflowId, wf.workflowTrace, wf.workflowSpan);
@@ -50,12 +50,10 @@ function registerTools(srv) {
50
50
  },
51
51
  }, async (args) => {
52
52
  const topic = args.topic.startsWith('app.') ? args.topic : `app.${args.topic}`;
53
+ // Custom `app.*` event: minimal envelope + data, no workflow fields.
53
54
  const event = {
54
55
  type: topic,
55
56
  source: args.source || 'mcp-tool',
56
- workflowId: '',
57
- workflowName: '',
58
- taskQueue: '',
59
57
  data: args.data,
60
58
  timestamp: new Date().toISOString(),
61
59
  };