@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.
- package/README.md +79 -49
- 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
|
+
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
|
-
|
|
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
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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: '
|
|
170
|
+
name: 'order_approval',
|
|
159
171
|
namespace: 'graph',
|
|
160
|
-
inputSchema: { type: 'object', properties: {
|
|
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:
|
|
167
|
-
publishes:
|
|
168
|
-
input: { schema: { type: object, properties: {
|
|
169
|
-
output: { schema: { type: object, properties: {
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
215
|
+
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
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).
|
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
|
};
|