@hotmeshio/long-tail 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -49
- package/build/api/escalations/index.d.ts +1 -1
- package/build/api/escalations/index.js +2 -1
- package/build/api/escalations/resolve.d.ts +10 -0
- package/build/api/escalations/resolve.js +52 -0
- 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/escalations/resolve.js +15 -3
- package/build/routes/topics.js +2 -4
- package/build/services/agent/input-mapper.js +33 -10
- package/build/services/escalation/crud.d.ts +18 -0
- package/build/services/escalation/crud.js +29 -0
- package/build/services/escalation/map.js +1 -0
- package/build/services/interceptor/index.js +25 -6
- package/build/services/orchestrator/condition.d.ts +30 -25
- package/build/services/orchestrator/condition.js +30 -26
- package/build/system/mcp-servers/events.js +1 -3
- package/build/types/escalation.d.ts +1 -0
- 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/api/http/escalations.md +19 -0
- package/docs/api/sdk/escalations.md +33 -0
- package/docs/data.md +61 -38
- package/docs/events.md +34 -13
- package/docs/hitl-guide.md +44 -1
- 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).
|
|
@@ -3,5 +3,5 @@ export { listEscalations, listAvailableEscalations, listDistinctTypes, getEscala
|
|
|
3
3
|
export { getEscalation, getEscalationsByWorkflowId, escalateToRole } from './single';
|
|
4
4
|
export { claimEscalation, releaseEscalation } from './claim';
|
|
5
5
|
export { releaseExpiredClaims, updatePriority, bulkClaim, bulkAssign, bulkEscalate, bulkTriage } from './bulk';
|
|
6
|
-
export { resolveEscalation } from './resolve';
|
|
6
|
+
export { resolveEscalation, resolveBySignalKey } from './resolve';
|
|
7
7
|
export { findByMetadata, claimByMetadata, resolveByMetadata } from './metadata';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getEscalationStats = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
|
|
3
|
+
exports.resolveByMetadata = exports.claimByMetadata = exports.findByMetadata = exports.resolveBySignalKey = exports.resolveEscalation = exports.bulkTriage = exports.bulkEscalate = exports.bulkAssign = exports.bulkClaim = exports.updatePriority = exports.releaseExpiredClaims = exports.releaseEscalation = exports.claimEscalation = exports.escalateToRole = exports.getEscalationsByWorkflowId = exports.getEscalation = exports.getEscalationStats = exports.listDistinctTypes = exports.listAvailableEscalations = exports.listEscalations = exports.createEscalation = void 0;
|
|
4
4
|
var create_1 = require("./create");
|
|
5
5
|
Object.defineProperty(exports, "createEscalation", { enumerable: true, get: function () { return create_1.createEscalation; } });
|
|
6
6
|
var list_1 = require("./list");
|
|
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "bulkEscalate", { enumerable: true, get: function
|
|
|
24
24
|
Object.defineProperty(exports, "bulkTriage", { enumerable: true, get: function () { return bulk_1.bulkTriage; } });
|
|
25
25
|
var resolve_1 = require("./resolve");
|
|
26
26
|
Object.defineProperty(exports, "resolveEscalation", { enumerable: true, get: function () { return resolve_1.resolveEscalation; } });
|
|
27
|
+
Object.defineProperty(exports, "resolveBySignalKey", { enumerable: true, get: function () { return resolve_1.resolveBySignalKey; } });
|
|
27
28
|
var metadata_1 = require("./metadata");
|
|
28
29
|
Object.defineProperty(exports, "findByMetadata", { enumerable: true, get: function () { return metadata_1.findByMetadata; } });
|
|
29
30
|
Object.defineProperty(exports, "claimByMetadata", { enumerable: true, get: function () { return metadata_1.claimByMetadata; } });
|
|
@@ -16,3 +16,13 @@ export declare function resolveEscalation(input: {
|
|
|
16
16
|
id: string;
|
|
17
17
|
resolverPayload: Record<string, any>;
|
|
18
18
|
}, _auth: LTApiAuth): Promise<LTApiResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve an efficient (atomic) escalation directly by its `signal_key` and
|
|
21
|
+
* resume the waiting workflow in place. For webhook callers that know the
|
|
22
|
+
* deterministic signal id (e.g. `signal-scan-ar-${orderId}`) and want to skip
|
|
23
|
+
* the id lookup. RBAC-scoped to the caller's visible roles.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveBySignalKey(input: {
|
|
26
|
+
signalKey: string;
|
|
27
|
+
resolverPayload: Record<string, any>;
|
|
28
|
+
}, auth: LTApiAuth): Promise<LTApiResult>;
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.resolveEscalation = resolveEscalation;
|
|
37
|
+
exports.resolveBySignalKey = resolveBySignalKey;
|
|
37
38
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
38
39
|
const taskService = __importStar(require("../../services/task"));
|
|
39
40
|
const escalation_strategy_1 = require("../../services/escalation-strategy");
|
|
@@ -41,6 +42,7 @@ const ephemeral_1 = require("../../services/iam/ephemeral");
|
|
|
41
42
|
const deployer_1 = require("../../services/yaml-workflow/deployer");
|
|
42
43
|
const workers_1 = require("../../workers");
|
|
43
44
|
const defaults_1 = require("../../modules/defaults");
|
|
45
|
+
const helpers_1 = require("./helpers");
|
|
44
46
|
// ── Orchestrator ─────────────────────────────────────────────────────────
|
|
45
47
|
/**
|
|
46
48
|
* Resolve a pending escalation with a human-provided payload.
|
|
@@ -76,6 +78,13 @@ async function resolveEscalation(input, _auth) {
|
|
|
76
78
|
if (signalRouting?.signalId) {
|
|
77
79
|
return resolveViaSignalRouting(escalation, resolverPayload);
|
|
78
80
|
}
|
|
81
|
+
// Path 0: efficient (atomic) escalation — signal_key resumes in place.
|
|
82
|
+
// The row was written inside the workflow's Leg1 checkpoint via
|
|
83
|
+
// `condition(signalId, config)`. The SDK's resolve marks it resolved AND
|
|
84
|
+
// delivers the signal to `signal_key`, resuming THIS job — no re-run.
|
|
85
|
+
if (escalation.signal_key) {
|
|
86
|
+
return resolveViaSignalKey(escalation, resolverPayload);
|
|
87
|
+
}
|
|
79
88
|
// Path C: escalation strategy may redirect to triage
|
|
80
89
|
const envelope = await reconstructEnvelope(escalation);
|
|
81
90
|
const strategy = escalation_strategy_1.escalationStrategyRegistry.current;
|
|
@@ -97,6 +106,34 @@ async function resolveEscalation(input, _auth) {
|
|
|
97
106
|
return { status: 500, error: err.message };
|
|
98
107
|
}
|
|
99
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve an efficient (atomic) escalation directly by its `signal_key` and
|
|
111
|
+
* resume the waiting workflow in place. For webhook callers that know the
|
|
112
|
+
* deterministic signal id (e.g. `signal-scan-ar-${orderId}`) and want to skip
|
|
113
|
+
* the id lookup. RBAC-scoped to the caller's visible roles.
|
|
114
|
+
*/
|
|
115
|
+
async function resolveBySignalKey(input, auth) {
|
|
116
|
+
try {
|
|
117
|
+
const { signalKey, resolverPayload } = input;
|
|
118
|
+
if (!signalKey)
|
|
119
|
+
return { status: 400, error: 'signalKey is required' };
|
|
120
|
+
if (!resolverPayload)
|
|
121
|
+
return { status: 400, error: 'resolverPayload is required' };
|
|
122
|
+
const escalation = await escalationService.getEscalationBySignalKey(signalKey);
|
|
123
|
+
if (!escalation)
|
|
124
|
+
return { status: 404, error: 'Escalation not found' };
|
|
125
|
+
if (escalation.status !== 'pending')
|
|
126
|
+
return { status: 409, error: 'Escalation not available for resolution' };
|
|
127
|
+
const visibleRoles = await (0, helpers_1.getVisibleRoles)(auth.userId);
|
|
128
|
+
if (visibleRoles && !visibleRoles.includes(escalation.role)) {
|
|
129
|
+
return { status: 404, error: 'Escalation not found' };
|
|
130
|
+
}
|
|
131
|
+
return resolveViaSignalKey(escalation, resolverPayload);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
return { status: 500, error: err.message };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
100
137
|
// ── Resolution paths ─────────────────────────────────────────────────────
|
|
101
138
|
/** Path A: lightweight conditionLT signal — inject $escalation_id and signal the running workflow. */
|
|
102
139
|
async function resolveViaConditionSignal(escalation, resolverPayload) {
|
|
@@ -107,6 +144,21 @@ async function resolveViaConditionSignal(escalation, resolverPayload) {
|
|
|
107
144
|
// Event published by service layer (services/escalation/crud.ts)
|
|
108
145
|
return signaledResult(escalation, escalation.workflow_id);
|
|
109
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Path 0: efficient escalation — resolve by `signal_key`. The SDK delivers the
|
|
149
|
+
* signal to the waiting `condition()` AND marks the row resolved in one
|
|
150
|
+
* transaction, so the original job resumes in place (no re-run, no separate
|
|
151
|
+
* resolve activity). Password fields are redacted before they enter the signal.
|
|
152
|
+
*/
|
|
153
|
+
async function resolveViaSignalKey(escalation, resolverPayload) {
|
|
154
|
+
const signalPayload = await redactPasswords(resolverPayload, escalation.metadata?.form_schema);
|
|
155
|
+
const resolved = await escalationService.resolveEscalation(escalation.id, signalPayload);
|
|
156
|
+
if (!resolved) {
|
|
157
|
+
return { status: 409, error: 'Escalation not available for resolution' };
|
|
158
|
+
}
|
|
159
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
160
|
+
return signaledResult(escalation, escalation.workflow_id || '');
|
|
161
|
+
}
|
|
110
162
|
/** Path B: waitFor signal escalation — signal via YAML engine or Durable handle. */
|
|
111
163
|
async function resolveViaSignalRouting(escalation, resolverPayload) {
|
|
112
164
|
const signalRouting = escalation.metadata.signal_routing;
|
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
|
};
|
|
@@ -36,11 +36,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.registerResolveRoutes = registerResolveRoutes;
|
|
37
37
|
const api = __importStar(require("../../api/escalations"));
|
|
38
38
|
function registerResolveRoutes(router) {
|
|
39
|
+
/**
|
|
40
|
+
* POST /api/escalations/resolve-by-signal-key
|
|
41
|
+
* Resolve an efficient (atomic) escalation by its signal_key and resume the
|
|
42
|
+
* waiting workflow in place. For webhook callers that know the deterministic
|
|
43
|
+
* signal id. Literal single-segment path — registered before /:id/resolve so
|
|
44
|
+
* it is never shadowed by the parameterized route.
|
|
45
|
+
* Body: { signalKey: string, resolverPayload: Record<string, any> }
|
|
46
|
+
*/
|
|
47
|
+
router.post('/resolve-by-signal-key', async (req, res) => {
|
|
48
|
+
const result = await api.resolveBySignalKey({ signalKey: req.body?.signalKey, resolverPayload: req.body?.resolverPayload }, req.auth);
|
|
49
|
+
res.status(result.status).json(result.data ?? { error: result.error });
|
|
50
|
+
});
|
|
39
51
|
/**
|
|
40
52
|
* POST /api/escalations/:id/resolve
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
53
|
+
* Resolve a pending escalation with a human-provided payload. Routes by
|
|
54
|
+
* escalation shape: efficient (signal_key) resumes the job in place; legacy
|
|
55
|
+
* paths signal via routing metadata or re-run the original workflow.
|
|
44
56
|
* Body: { resolverPayload: Record<string, any> }
|
|
45
57
|
*/
|
|
46
58
|
router.post('/:id/resolve', async (req, res) => {
|
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.
|