@hotmeshio/long-tail 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -50
- package/build/api/topics.d.ts +14 -4
- package/build/api/topics.js +9 -9
- package/build/lib/events/index.js +6 -0
- package/build/lib/events/publish.js +3 -7
- package/build/lib/events/system-events.js +5 -6
- package/build/routes/topics.js +2 -4
- package/build/services/agent/input-mapper.js +33 -10
- package/build/services/interceptor/index.js +25 -6
- package/build/system/mcp-servers/events.js +1 -3
- package/build/types/events.d.ts +90 -23
- package/dashboard/dist/assets/{AdminDashboard-CMzR4d-w.js → AdminDashboard-DIQEViVY.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-CMzR4d-w.js.map → AdminDashboard-DIQEViVY.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-DHCpN4xk.js → AgentConfigPage-Caf5-th-.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-DHCpN4xk.js.map → AgentConfigPage-Caf5-th-.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-CSrejvSl.js → AgentDetailPage-_a4tKaM3.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-CSrejvSl.js.map → AgentDetailPage-_a4tKaM3.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-ntuzWHCD.js → AgentsPage-BNlAkEIK.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-ntuzWHCD.js.map → AgentsPage-BNlAkEIK.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-Ci24fdxC.js → AvailableEscalationsPage-Cqy5ECm4.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-Ci24fdxC.js.map → AvailableEscalationsPage-Cqy5ECm4.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-lj42d48P.js → BotPicker-DBvd1yFL.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-lj42d48P.js.map → BotPicker-DBvd1yFL.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-Bea9yLmU.js → CapabilitiesPage-BIdG9RTG.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-Bea9yLmU.js.map → CapabilitiesPage-BIdG9RTG.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-BeBsI1M4.js → CollapsibleSection-CggyKhyZ.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-BeBsI1M4.js.map → CollapsibleSection-CggyKhyZ.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-BDzFa3HV.js → CredentialsPage-B4sXGLRY.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-BDzFa3HV.js.map → CredentialsPage-B4sXGLRY.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-DeNm1I4r.js → CronLabel-GcS5qzZr.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-DeNm1I4r.js.map → CronLabel-GcS5qzZr.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-C2OH9YcV.js → CustomDurationPicker-CUYE36-K.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-C2OH9YcV.js.map → CustomDurationPicker-CUYE36-K.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-WdzqA0dR.js → ElapsedCell-BFOMsgwF.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-WdzqA0dR.js.map → ElapsedCell-BFOMsgwF.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-BIEeflEV.js → EscalationsOverview-BfjW6haL.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-BIEeflEV.js.map → EscalationsOverview-BfjW6haL.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-NIhWP__B.js → EventTable-N0XmLk3C.js} +2 -2
- package/dashboard/dist/assets/{EventTable-NIhWP__B.js.map → EventTable-N0XmLk3C.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-DtAW8ilc.js → GraphInvokePage-DHiGHiCk.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-DtAW8ilc.js.map → GraphInvokePage-DHiGHiCk.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-HsO-M8ub.js → HomePage-jckQolbx.js} +2 -2
- package/dashboard/dist/assets/{HomePage-HsO-M8ub.js.map → HomePage-jckQolbx.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-DqaRlXrF.js → ListToolbar-B71DqaXr.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-DqaRlXrF.js.map → ListToolbar-B71DqaXr.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-B4GsHxij.js → McpOverview-CPD6NRGt.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-B4GsHxij.js.map → McpOverview-CPD6NRGt.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-DZKpzWST.js → McpQueryDetailPage-YyqXw6Dt.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-DZKpzWST.js.map → McpQueryDetailPage-YyqXw6Dt.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-B-8WR3GV.js → McpQueryPage-BWf45EYD.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-B-8WR3GV.js.map → McpQueryPage-BWf45EYD.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-Dt7Ai3qB.js → McpRunDetailPage-Cv43tkjz.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-Dt7Ai3qB.js.map → McpRunDetailPage-Cv43tkjz.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-Bdke-HoQ.js → McpRunsPage-BFards4K.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-Bdke-HoQ.js.map → McpRunsPage-BFards4K.js.map} +1 -1
- package/dashboard/dist/assets/{OperatorDashboard-VZ97mufd.js → OperatorDashboard-BJZxuOKI.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-VZ97mufd.js.map → OperatorDashboard-BJZxuOKI.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-BLfLfmWX.js → ProcessDetailPage-BjkLfyrm.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-BLfLfmWX.js.map → ProcessDetailPage-BjkLfyrm.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-DG54t-Nd.js → ProcessesListPage-DOzeduP3.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-DG54t-Nd.js.map → ProcessesListPage-DOzeduP3.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-Bdlv3DdW.js → RolesPage-DSSQrGTh.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-Bdlv3DdW.js.map → RolesPage-DSSQrGTh.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-BFxxMvL3.js → RunAsSelector-CM7lk1YB.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-BFxxMvL3.js.map → RunAsSelector-CM7lk1YB.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-BZmad7WQ.js → SwimlaneTimeline-DtCgJNpV.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-BZmad7WQ.js.map → SwimlaneTimeline-DtCgJNpV.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-CLl2mgBC.js → TaskDetailPage-BC9xnyLT.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CLl2mgBC.js.map → TaskDetailPage-BC9xnyLT.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-DoHXxZF-.js → TasksListPage-DtbLfUS-.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-DoHXxZF-.js.map → TasksListPage-DtbLfUS-.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-DZF9w8Rl.js → TimeAgo-xrDlFnPy.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-DZF9w8Rl.js.map → TimeAgo-xrDlFnPy.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-DPpoTdrw.js → TimestampCell-BneATvjR.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-DPpoTdrw.js.map → TimestampCell-BneATvjR.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-DtAgJQfr.js → ToolTestPanel-1lQ_GQod.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-DtAgJQfr.js.map → ToolTestPanel-1lQ_GQod.js.map} +1 -1
- package/dashboard/dist/assets/TopicDetailPage-CDtlXyut.js +9 -0
- package/dashboard/dist/assets/TopicDetailPage-CDtlXyut.js.map +1 -0
- package/dashboard/dist/assets/{TopicsPage-Bc-4ne6V.js → TopicsPage-COX2kDKK.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-Bc-4ne6V.js.map → TopicsPage-COX2kDKK.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-O2Q4-E6E.js → UserName-Cb2WvhCN.js} +2 -2
- package/dashboard/dist/assets/{UserName-O2Q4-E6E.js.map → UserName-Cb2WvhCN.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-DEDsBmn1.js → WorkflowExecutionPage-BWCTOTXF.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-DEDsBmn1.js.map → WorkflowExecutionPage-BWCTOTXF.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsDashboard-Bcf17vCt.js → WorkflowsDashboard-DOaDSC98.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-Bcf17vCt.js.map → WorkflowsDashboard-DOaDSC98.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-Cwo2rqGT.js → WorkflowsOverview-CeE7a51J.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-Cwo2rqGT.js.map → WorkflowsOverview-CeE7a51J.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bv8ZFwO-.js → YamlWorkflowDetailPage-cNpR0PHg.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-Bv8ZFwO-.js.map → YamlWorkflowDetailPage-cNpR0PHg.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-DOiEQDOq.js → YamlWorkflowsPage-DWvNfzji.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-DOiEQDOq.js.map → YamlWorkflowsPage-DWvNfzji.js.map} +1 -1
- package/dashboard/dist/assets/{agents-D09G0HCv.js → agents-BvTiBKBQ.js} +2 -2
- package/dashboard/dist/assets/{agents-D09G0HCv.js.map → agents-BvTiBKBQ.js.map} +1 -1
- package/dashboard/dist/assets/{bots-D0LhyZZM.js → bots-CeMR1Lnp.js} +2 -2
- package/dashboard/dist/assets/{bots-D0LhyZZM.js.map → bots-CeMR1Lnp.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-DON4-NXs.js → capabilities-D7uJlnxj.js} +2 -2
- package/dashboard/dist/assets/{capabilities-DON4-NXs.js.map → capabilities-D7uJlnxj.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-Bihd1kXf.js → controlplane-Cf6lZsSF.js} +2 -2
- package/dashboard/dist/assets/{controlplane-Bihd1kXf.js.map → controlplane-Cf6lZsSF.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-CP2XbdXK.js → escalation-BCFBWGP-.js} +2 -2
- package/dashboard/dist/assets/{escalation-CP2XbdXK.js.map → escalation-BCFBWGP-.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-9aw8Y4qq.js → escalation-columns-Do7k4fte.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-9aw8Y4qq.js.map → escalation-columns-Do7k4fte.js.map} +1 -1
- package/dashboard/dist/assets/{index-DgLZ8Ix5.js → index-8C9YLM_u.js} +2 -2
- package/dashboard/dist/assets/{index-DgLZ8Ix5.js.map → index-8C9YLM_u.js.map} +1 -1
- package/dashboard/dist/assets/{index-CCup2uaP.js → index-B216fPN_.js} +2 -2
- package/dashboard/dist/assets/{index-CCup2uaP.js.map → index-B216fPN_.js.map} +1 -1
- package/dashboard/dist/assets/index-BCfzac3h.css +1 -0
- package/dashboard/dist/assets/{index-Cb7aSzox.js → index-BDhEpETz.js} +2 -2
- package/dashboard/dist/assets/{index-Cb7aSzox.js.map → index-BDhEpETz.js.map} +1 -1
- package/dashboard/dist/assets/{index-_JsRJPds.js → index-BS5pcmBR.js} +2 -2
- package/dashboard/dist/assets/{index-_JsRJPds.js.map → index-BS5pcmBR.js.map} +1 -1
- package/dashboard/dist/assets/{index-B-BK3vtk.js → index-BUu3WKvk.js} +2 -2
- package/dashboard/dist/assets/{index-B-BK3vtk.js.map → index-BUu3WKvk.js.map} +1 -1
- package/dashboard/dist/assets/{index-BTp73vYK.js → index-C1p_rvkY.js} +2 -2
- package/dashboard/dist/assets/{index-BTp73vYK.js.map → index-C1p_rvkY.js.map} +1 -1
- package/dashboard/dist/assets/{index-CMRW_PE-.js → index-C51Jzvoz.js} +2 -2
- package/dashboard/dist/assets/{index-CMRW_PE-.js.map → index-C51Jzvoz.js.map} +1 -1
- package/dashboard/dist/assets/{index-BBBGETMs.js → index-D0fjUKvx.js} +2 -2
- package/dashboard/dist/assets/{index-BBBGETMs.js.map → index-D0fjUKvx.js.map} +1 -1
- package/dashboard/dist/assets/{index-BInTEEIX.js → index-D3SjB3UM.js} +2 -2
- package/dashboard/dist/assets/{index-BInTEEIX.js.map → index-D3SjB3UM.js.map} +1 -1
- package/dashboard/dist/assets/{index-rbuNUyAh.js → index-DSjbScPX.js} +2 -2
- package/dashboard/dist/assets/{index-rbuNUyAh.js.map → index-DSjbScPX.js.map} +1 -1
- package/dashboard/dist/assets/{index-B7lEd0cY.js → index-eWNx4UQ8.js} +3 -3
- package/dashboard/dist/assets/{index-B7lEd0cY.js.map → index-eWNx4UQ8.js.map} +1 -1
- package/dashboard/dist/assets/{index-H5Yb8CY2.js → index-gKm3HAgO.js} +2 -2
- package/dashboard/dist/assets/{index-H5Yb8CY2.js.map → index-gKm3HAgO.js.map} +1 -1
- package/dashboard/dist/assets/{index-Cjb8ulHm.js → index-oulO44Iy.js} +2 -2
- package/dashboard/dist/assets/{index-Cjb8ulHm.js.map → index-oulO44Iy.js.map} +1 -1
- package/dashboard/dist/assets/{knowledge-Bl_KaoKJ.js → knowledge-Bje8_ndK.js} +2 -2
- package/dashboard/dist/assets/{knowledge-Bl_KaoKJ.js.map → knowledge-Bje8_ndK.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-DXbFGoA8.js → mcp-Bpl6p11J.js} +2 -2
- package/dashboard/dist/assets/{mcp-DXbFGoA8.js.map → mcp-Bpl6p11J.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-Bg69DF2x.js → mcp-query-DMXj-t5f.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-Bg69DF2x.js.map → mcp-query-DMXj-t5f.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-DK9LTg9F.js → pipelines-CFh9nBxn.js} +2 -2
- package/dashboard/dist/assets/{pipelines-DK9LTg9F.js.map → pipelines-CFh9nBxn.js.map} +1 -1
- package/dashboard/dist/assets/{roles-BrsBN4hO.js → roles-CtjQQWSQ.js} +2 -2
- package/dashboard/dist/assets/{roles-BrsBN4hO.js.map → roles-CtjQQWSQ.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-CwjvPECN.js → tasks-Nz9lVCT1.js} +2 -2
- package/dashboard/dist/assets/{tasks-CwjvPECN.js.map → tasks-Nz9lVCT1.js.map} +1 -1
- package/dashboard/dist/assets/topics-CiG2N8Z_.js +2 -0
- package/dashboard/dist/assets/{topics-BrwkmaFR.js.map → topics-CiG2N8Z_.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-Cd1GM1NG.js → useEventHooks-Bimqt7hU.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-Cd1GM1NG.js.map → useEventHooks-Bimqt7hU.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-D9lghZ25.js → useNamespace-FrG0Monz.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-D9lghZ25.js.map → useNamespace-FrG0Monz.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-vOhAwmKO.js → useYamlActivityEvents-JmD4dH5n.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-vOhAwmKO.js.map → useYamlActivityEvents-JmD4dH5n.js.map} +1 -1
- package/dashboard/dist/assets/{users-ChQ7soaq.js → users-DgyonYq6.js} +2 -2
- package/dashboard/dist/assets/{users-ChQ7soaq.js.map → users-DgyonYq6.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-BLKji1_1.js → workflows-BOwGINtF.js} +2 -2
- package/dashboard/dist/assets/{workflows-BLKji1_1.js.map → workflows-BOwGINtF.js.map} +1 -1
- package/dashboard/dist/assets/{yaml-workflows-BUhMfdaw.js → yaml-workflows-I1tcjCFK.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-BUhMfdaw.js.map → yaml-workflows-I1tcjCFK.js.map} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/docs/data.md +61 -38
- package/docs/events.md +34 -13
- package/package.json +2 -2
- package/dashboard/dist/assets/TopicDetailPage-W9RKkNNp.js +0 -9
- package/dashboard/dist/assets/TopicDetailPage-W9RKkNNp.js.map +0 -1
- package/dashboard/dist/assets/index-BFyzZGtv.css +0 -1
- package/dashboard/dist/assets/topics-BrwkmaFR.js +0 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Long Tail
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Durable workflow for humans in the loop.
|
|
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
|
-
|
|
11
|
+
Route work to machines or people interchangeably with ease.
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { Durable } from '@hotmeshio/hotmesh';
|
|
@@ -18,23 +18,32 @@ 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
|
+
// method calls are checkpointed and crash safe
|
|
21
22
|
const analysis = await analyzeContent(envelope.data.content);
|
|
22
23
|
|
|
23
24
|
if (analysis.confidence >= 0.85) {
|
|
24
|
-
return {
|
|
25
|
+
return { data: { approved: true, analysis } };
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
//role-based escalations are baked-in. create HITL escalations with one call
|
|
29
|
+
const { workflowId } = Durable.workflow.workflowInfo();
|
|
30
|
+
const decision = await Durable.workflow.condition<{ approved: boolean; notes?: string }>(
|
|
31
|
+
`review-${workflowId}`,
|
|
32
|
+
{
|
|
33
|
+
role: 'reviewer',
|
|
34
|
+
type: 'content-review',
|
|
35
|
+
priority: 2,
|
|
36
|
+
description: `Confidence ${analysis.confidence} — needs a human`,
|
|
37
|
+
metadata: { contentId: envelope.data.contentId },
|
|
38
|
+
envelope: { data: envelope.data, analysis },
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return { data: { approved: decision.approved, analysis } };
|
|
34
43
|
}
|
|
35
44
|
```
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
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
47
|
|
|
39
48
|
Activities are plain functions:
|
|
40
49
|
|
|
@@ -62,6 +71,8 @@ Dashboard at [http://localhost:3000](http://localhost:3000). The [boilerplate](h
|
|
|
62
71
|
|
|
63
72
|
## The pattern
|
|
64
73
|
|
|
74
|
+
Four moves, from a plan you write to a plan that keeps up with reality.
|
|
75
|
+
|
|
65
76
|
**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
77
|
|
|
67
78
|
**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 +83,7 @@ curl -X PUT http://localhost:3000/api/workflows/reviewContent/config \
|
|
|
72
83
|
-d '{ "invocable": true, "task_queue": "default", "default_role": "reviewer" }'
|
|
73
84
|
```
|
|
74
85
|
|
|
75
|
-
**Step 3 — React to events.** Workflows publish topics
|
|
86
|
+
**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
87
|
|
|
77
88
|
**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
89
|
|
|
@@ -134,7 +145,7 @@ const lt = await start({
|
|
|
134
145
|
});
|
|
135
146
|
```
|
|
136
147
|
|
|
137
|
-
All three paths produce the same outcome: tools callable as durable activities. See the [MCP guide](docs/mcp.md).
|
|
148
|
+
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
149
|
|
|
139
150
|
## Compile workflows
|
|
140
151
|
|
|
@@ -145,44 +156,62 @@ export ANTHROPIC_API_KEY=sk-ant-...
|
|
|
145
156
|
npx ltc compile workflows/
|
|
146
157
|
```
|
|
147
158
|
|
|
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).
|
|
159
|
+
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
160
|
|
|
150
161
|
## Register a graph flow by hand
|
|
151
162
|
|
|
152
|
-
`graphWorkflows` is the graph-form peer of `workers`: hand-author the HotMesh YAML and it's created, deployed, and activated at startup.
|
|
163
|
+
`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
164
|
|
|
154
165
|
```typescript
|
|
155
166
|
const lt = await start({
|
|
156
167
|
database: { connectionString: process.env.DATABASE_URL },
|
|
157
168
|
graphWorkflows: [{
|
|
158
|
-
name: '
|
|
169
|
+
name: 'order_approval',
|
|
159
170
|
namespace: 'graph',
|
|
160
|
-
inputSchema: { type: 'object', properties: {
|
|
171
|
+
inputSchema: { type: 'object', properties: { orderId: { type: 'string' }, region: { type: 'string' } }, required: ['orderId'] },
|
|
161
172
|
yaml: `
|
|
162
173
|
app:
|
|
163
174
|
id: graph
|
|
164
175
|
version: '1'
|
|
165
176
|
graphs:
|
|
166
|
-
- subscribes:
|
|
167
|
-
publishes:
|
|
168
|
-
input: { schema: { type: object, properties: {
|
|
169
|
-
output: { schema: { type: object, properties: {
|
|
177
|
+
- subscribes: order_approval
|
|
178
|
+
publishes: order_approval.done
|
|
179
|
+
input: { schema: { type: object, properties: { orderId: { type: string }, region: { type: string } } } }
|
|
180
|
+
output: { schema: { type: object, properties: { approved: { type: boolean } } } }
|
|
170
181
|
activities:
|
|
171
182
|
trigger:
|
|
172
183
|
type: trigger
|
|
184
|
+
review:
|
|
185
|
+
type: hook
|
|
186
|
+
escalation:
|
|
187
|
+
role: approver
|
|
188
|
+
type: order-approval
|
|
189
|
+
priority: 2
|
|
190
|
+
description: Approve order for dispatch
|
|
191
|
+
metadata:
|
|
192
|
+
orderId: '{trigger.output.data.orderId}'
|
|
193
|
+
region: '{trigger.output.data.region}'
|
|
194
|
+
envelope:
|
|
195
|
+
instructions: Review and approve or reject
|
|
173
196
|
job:
|
|
174
197
|
maps:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
approved: '{review.hook.data.approved}'
|
|
199
|
+
transitions:
|
|
200
|
+
trigger:
|
|
201
|
+
- to: review
|
|
202
|
+
hooks:
|
|
203
|
+
order_approval.approve:
|
|
204
|
+
- to: review
|
|
205
|
+
conditions:
|
|
206
|
+
match:
|
|
207
|
+
- expected: '{$job.metadata.jid}'
|
|
208
|
+
actual: '{$self.hook.data.id}'
|
|
180
209
|
`,
|
|
181
210
|
}],
|
|
182
211
|
});
|
|
183
212
|
```
|
|
184
213
|
|
|
185
|
-
It appears under **Orchestrate › Graph** and
|
|
214
|
+
It appears under **Orchestrate › Graph** and routes to a person exactly like the procedural `condition()` above — the 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
215
|
|
|
187
216
|
## Full configuration
|
|
188
217
|
|
|
@@ -240,7 +269,7 @@ client.events.on('task.completed', (event) => console.log('done:', event.workflo
|
|
|
240
269
|
client.events.on('escalation.*', (event) => notifyTeam(event));
|
|
241
270
|
```
|
|
242
271
|
|
|
243
|
-
Every SDK call returns an `LTApiResult` — same status codes, same validation, same RBAC. See the [SDK guide](docs/sdk.md).
|
|
272
|
+
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
273
|
|
|
245
274
|
## Deployment
|
|
246
275
|
|
|
@@ -258,31 +287,31 @@ await start({ database: { connectionString: process.env.DATABASE_URL }, server:
|
|
|
258
287
|
const lt = createClient({ auth: { userId: 'service' } });
|
|
259
288
|
```
|
|
260
289
|
|
|
261
|
-
All modes share PostgreSQL and scale independently. See [Cloud Deployment](docs/cloud.md).
|
|
290
|
+
All modes share PostgreSQL and scale independently. See [Cloud Deployment](https://github.com/hotmeshio/long-tail/blob/main/docs/cloud.md).
|
|
262
291
|
|
|
263
292
|
## Docs
|
|
264
293
|
|
|
265
294
|
| Guide | What it covers |
|
|
266
295
|
|-------|---------------|
|
|
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)
|
|
296
|
+
| [The Long Tail Story](https://github.com/hotmeshio/long-tail/blob/main/docs/story.md) | Why this exists, what accumulates over time |
|
|
297
|
+
| [Workflows](https://github.com/hotmeshio/long-tail/blob/main/docs/workflows.md) | Activities, interceptor, escalation lifecycle, composition |
|
|
298
|
+
| [IAM](https://github.com/hotmeshio/long-tail/blob/main/docs/iam.md) | Identity propagation, service accounts, credential exchange |
|
|
299
|
+
| [Dashboard](https://github.com/hotmeshio/long-tail/blob/main/docs/dashboard.md) | Navigation, key pages, event feed |
|
|
300
|
+
| [MCP](https://github.com/hotmeshio/long-tail/blob/main/docs/mcp.md) | Server registration, tool calls, human queue |
|
|
301
|
+
| [Compilation](https://github.com/hotmeshio/long-tail/blob/main/docs/compilation.md) | Dynamic to deterministic pipeline wizard |
|
|
302
|
+
| [Compiler](https://github.com/hotmeshio/long-tail/blob/main/docs/compiler.md) | `ltc compile` — durable TypeScript to YAML DAGs |
|
|
303
|
+
| [CLI](https://github.com/hotmeshio/long-tail/blob/main/docs/cli.md) | `ltc` — terminal access to workflows, escalations, knowledge, MCP |
|
|
304
|
+
| [Escalation Strategies](https://github.com/hotmeshio/long-tail/blob/main/docs/escalation-strategies.md) | Default, MCP triage, custom handlers |
|
|
305
|
+
| [SDK](https://github.com/hotmeshio/long-tail/blob/main/docs/sdk.md) | Embedded usage, `createClient`, event subscriptions |
|
|
306
|
+
| [Architecture](https://github.com/hotmeshio/long-tail/blob/main/docs/architecture.md) | Project structure, conventions, discovery |
|
|
307
|
+
| [Cloud](https://github.com/hotmeshio/long-tail/blob/main/docs/cloud.md) | AWS ECS, GCP Cloud Run, Docker |
|
|
308
|
+
| [Data Model](https://github.com/hotmeshio/long-tail/blob/main/docs/data.md) | Database schema |
|
|
309
|
+
|
|
310
|
+
**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)
|
|
311
|
+
|
|
312
|
+
**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)
|
|
313
|
+
|
|
314
|
+
**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
315
|
|
|
287
316
|
## Contributing
|
|
288
317
|
|
|
@@ -301,8 +330,8 @@ Open [http://localhost:3000](http://localhost:3000). Example workflows seed the
|
|
|
301
330
|
| `engineer` | `l0ngt@1l` | engineer |
|
|
302
331
|
| `reviewer` | `l0ngt@1l` | reviewer |
|
|
303
332
|
|
|
304
|
-
See [Contributing](docs/contributing.md).
|
|
333
|
+
See [Contributing](https://github.com/hotmeshio/long-tail/blob/main/docs/contributing.md).
|
|
305
334
|
|
|
306
335
|
## License
|
|
307
336
|
|
|
308
|
-
See [LICENSE](LICENSE).
|
|
337
|
+
See [LICENSE](https://github.com/hotmeshio/long-tail/blob/main/LICENSE).
|
package/build/api/topics.d.ts
CHANGED
|
@@ -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
|
-
|
|
33
|
-
eventId?: string;
|
|
34
|
-
data: Record<string, any>;
|
|
35
|
-
source?: string;
|
|
45
|
+
event?: PublishEventInput;
|
|
36
46
|
}): Promise<LTApiResult>;
|
package/build/api/topics.js
CHANGED
|
@@ -129,19 +129,19 @@ function isValidVariant(pattern, subject) {
|
|
|
129
129
|
}
|
|
130
130
|
async function publishTopic(input) {
|
|
131
131
|
try {
|
|
132
|
-
const
|
|
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 (
|
|
135
|
-
return { status: 400, error: `Subject "${
|
|
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
|
-
|
|
141
|
+
...fields,
|
|
139
142
|
type: publishSubject,
|
|
140
|
-
source:
|
|
141
|
-
|
|
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
|
-
|
|
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
|
};
|
package/build/routes/topics.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
6
|
-
* Returns the resolved value, or
|
|
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
|
|
9
|
-
const
|
|
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
|
|
11
|
+
for (const segment of segments) {
|
|
15
12
|
if (current == null || typeof current !== 'object')
|
|
16
|
-
return
|
|
13
|
+
return undefined;
|
|
17
14
|
current = current[segment];
|
|
18
15
|
}
|
|
19
|
-
return current
|
|
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.
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// (services/
|
|
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
|
};
|