@hotmeshio/long-tail 0.4.14 → 0.4.15
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/build/api/escalations/claim.js +1 -10
- package/build/api/escalations/create.js +1 -11
- package/build/api/escalations/metadata.d.ts +2 -0
- package/build/api/escalations/metadata.js +6 -12
- package/build/api/escalations/resolve.js +4 -23
- package/build/bin/ltc.js +2 -0
- package/build/lib/cli/commands/escalations.d.ts +2 -0
- package/build/lib/cli/commands/escalations.js +4 -0
- package/build/lib/events/publish.js +2 -0
- package/build/routes/escalations/metadata.js +4 -2
- package/build/sdk/index.d.ts +2 -0
- package/build/services/escalation/crud.d.ts +1 -1
- package/build/services/escalation/crud.js +56 -6
- package/build/services/escalation/sql.d.ts +2 -2
- package/build/services/escalation/sql.js +5 -2
- package/build/services/interceptor/activities/escalation.js +1 -11
- package/build/services/interceptor/activities/task.js +2 -23
- package/build/services/interceptor/completion.js +2 -22
- package/build/services/interceptor/escalation.js +5 -44
- package/build/services/interceptor/index.js +26 -2
- package/build/services/interceptor/lifecycle.d.ts +1 -1
- package/build/services/interceptor/lifecycle.js +18 -32
- package/build/services/task/crud.js +36 -2
- package/docs/api/http/escalations.md +6 -4
- package/docs/api/sdk/escalations.md +6 -4
- package/package.json +1 -1
|
@@ -71,16 +71,7 @@ async function claimEscalation(input, auth) {
|
|
|
71
71
|
if (!result) {
|
|
72
72
|
return { status: 409, error: 'Escalation not available for claim' };
|
|
73
73
|
}
|
|
74
|
-
(
|
|
75
|
-
type: 'escalation.claimed',
|
|
76
|
-
source: 'api',
|
|
77
|
-
workflowId: escalation.workflow_id || '',
|
|
78
|
-
workflowName: escalation.workflow_type || '',
|
|
79
|
-
taskQueue: escalation.task_queue || '',
|
|
80
|
-
escalationId: id,
|
|
81
|
-
status: 'claimed',
|
|
82
|
-
data: { assigned_to: auth.userId },
|
|
83
|
-
});
|
|
74
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
84
75
|
return { status: 200, data: result };
|
|
85
76
|
}
|
|
86
77
|
catch (err) {
|
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.createEscalation = createEscalation;
|
|
37
37
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
38
38
|
const userService = __importStar(require("../../services/user"));
|
|
39
|
-
const publish_1 = require("../../lib/events/publish");
|
|
40
39
|
// ── Create ────────────────────────────────────────────────────────────────
|
|
41
40
|
/**
|
|
42
41
|
* Create a standalone escalation (not tied to a workflow).
|
|
@@ -83,16 +82,7 @@ async function createEscalation(input, auth) {
|
|
|
83
82
|
metadata: input.metadata,
|
|
84
83
|
escalation_payload: input.escalation_payload,
|
|
85
84
|
});
|
|
86
|
-
(
|
|
87
|
-
type: 'escalation.created',
|
|
88
|
-
source: 'api',
|
|
89
|
-
workflowId: '',
|
|
90
|
-
workflowName: '',
|
|
91
|
-
taskQueue: '',
|
|
92
|
-
escalationId: escalation.id,
|
|
93
|
-
status: 'pending',
|
|
94
|
-
data: { type: input.type, role },
|
|
95
|
-
});
|
|
85
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
96
86
|
return { status: 201, data: escalation };
|
|
97
87
|
}
|
|
98
88
|
catch (err) {
|
|
@@ -35,6 +35,7 @@ export declare function claimByMetadata(input: {
|
|
|
35
35
|
value: string;
|
|
36
36
|
durationMinutes?: number;
|
|
37
37
|
assignee?: string;
|
|
38
|
+
metadata?: Record<string, any>;
|
|
38
39
|
}, auth: LTApiAuth): Promise<LTApiResult>;
|
|
39
40
|
/**
|
|
40
41
|
* Resolve an escalation by metadata key-value pair.
|
|
@@ -53,4 +54,5 @@ export declare function resolveByMetadata(input: {
|
|
|
53
54
|
value: string;
|
|
54
55
|
resolverPayload: Record<string, any>;
|
|
55
56
|
assignee?: string;
|
|
57
|
+
metadata?: Record<string, any>;
|
|
56
58
|
}, auth: LTApiAuth): Promise<LTApiResult>;
|
|
@@ -38,7 +38,6 @@ exports.claimByMetadata = claimByMetadata;
|
|
|
38
38
|
exports.resolveByMetadata = resolveByMetadata;
|
|
39
39
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
40
40
|
const userService = __importStar(require("../../services/user"));
|
|
41
|
-
const publish_1 = require("../../lib/events/publish");
|
|
42
41
|
const helpers_1 = require("./helpers");
|
|
43
42
|
/**
|
|
44
43
|
* Find escalations by a metadata key-value pair.
|
|
@@ -105,20 +104,11 @@ async function claimByMetadata(input, auth) {
|
|
|
105
104
|
return { status: 403, error: `User must have the "${candidate.role}" role to claim this escalation` };
|
|
106
105
|
}
|
|
107
106
|
}
|
|
108
|
-
const result = await escalationService.claimByMetadata(input.key, input.value, claimUserId, input.durationMinutes);
|
|
107
|
+
const result = await escalationService.claimByMetadata(input.key, input.value, claimUserId, input.durationMinutes, input.metadata);
|
|
109
108
|
if (!result) {
|
|
110
109
|
return { status: 409, error: 'Escalation not available for claim' };
|
|
111
110
|
}
|
|
112
|
-
(
|
|
113
|
-
type: 'escalation.claimed',
|
|
114
|
-
source: 'api',
|
|
115
|
-
workflowId: result.escalation.workflow_id || '',
|
|
116
|
-
workflowName: result.escalation.workflow_type || '',
|
|
117
|
-
taskQueue: result.escalation.task_queue || '',
|
|
118
|
-
escalationId: result.escalation.id,
|
|
119
|
-
status: 'claimed',
|
|
120
|
-
data: { assigned_to: claimUserId },
|
|
121
|
-
});
|
|
111
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
122
112
|
return { status: 200, data: result };
|
|
123
113
|
}
|
|
124
114
|
catch (err) {
|
|
@@ -161,6 +151,10 @@ async function resolveByMetadata(input, auth) {
|
|
|
161
151
|
return { status: 403, error: `User must have the "${escalation.role}" role` };
|
|
162
152
|
}
|
|
163
153
|
}
|
|
154
|
+
// Merge additional metadata if provided
|
|
155
|
+
if (input.metadata && Object.keys(input.metadata).length > 0) {
|
|
156
|
+
await escalationService.updateEscalationMetadata(escalation.id, input.metadata);
|
|
157
|
+
}
|
|
164
158
|
// Auto-claim if unclaimed or expired
|
|
165
159
|
const isClaimed = escalation.assigned_to &&
|
|
166
160
|
escalation.assigned_until &&
|
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.resolveEscalation = resolveEscalation;
|
|
37
37
|
const escalationService = __importStar(require("../../services/escalation"));
|
|
38
38
|
const taskService = __importStar(require("../../services/task"));
|
|
39
|
-
const publish_1 = require("../../lib/events/publish");
|
|
40
39
|
const escalation_strategy_1 = require("../../services/escalation-strategy");
|
|
41
40
|
const ephemeral_1 = require("../../services/iam/ephemeral");
|
|
42
41
|
const deployer_1 = require("../../services/yaml-workflow/deployer");
|
|
@@ -105,7 +104,7 @@ async function resolveViaConditionSignal(escalation, resolverPayload) {
|
|
|
105
104
|
const client = (0, workers_1.createClient)();
|
|
106
105
|
const handle = await client.workflow.getHandle(escalation.task_queue, escalation.workflow_type, escalation.workflow_id);
|
|
107
106
|
await handle.signal(signalId, { ...resolverPayload, $escalation_id: escalation.id });
|
|
108
|
-
|
|
107
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
109
108
|
return signaledResult(escalation, escalation.workflow_id);
|
|
110
109
|
}
|
|
111
110
|
/** Path B: waitFor signal escalation — signal via YAML engine or Durable handle. */
|
|
@@ -129,12 +128,7 @@ async function resolveViaSignalRouting(escalation, resolverPayload) {
|
|
|
129
128
|
if (signalRouting.engine !== 'yaml') {
|
|
130
129
|
await escalationService.resolveEscalation(escalation.id, resolverPayload);
|
|
131
130
|
}
|
|
132
|
-
|
|
133
|
-
workflowId: escalation.workflow_id || signalRouting.workflowId,
|
|
134
|
-
workflowName: escalation.workflow_type || signalRouting.workflowType,
|
|
135
|
-
taskQueue: escalation.task_queue || signalRouting.taskQueue || signalRouting.appId,
|
|
136
|
-
status: signalRouting.engine === 'yaml' ? 'signaled' : 'resolved',
|
|
137
|
-
});
|
|
131
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
138
132
|
return signaledResult(escalation, signalRouting.workflowId || signalRouting.appId);
|
|
139
133
|
}
|
|
140
134
|
/** Path C: escalation strategy directed triage — start a triage workflow. */
|
|
@@ -165,7 +159,7 @@ async function resolveViaTriage(escalation, resolverPayload, triageEnvelope) {
|
|
|
165
159
|
...resolverPayload,
|
|
166
160
|
_lt: { ...resolverPayload._lt, triaged: true, triageWorkflowId },
|
|
167
161
|
});
|
|
168
|
-
|
|
162
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
169
163
|
return {
|
|
170
164
|
status: 200,
|
|
171
165
|
data: { started: true, escalationId: escalation.id, workflowId: triageWorkflowId, triage: true },
|
|
@@ -184,26 +178,13 @@ async function resolveViaRerun(escalation, envelope, resolverPayload) {
|
|
|
184
178
|
workflowId: newWorkflowId,
|
|
185
179
|
expire: 180,
|
|
186
180
|
});
|
|
187
|
-
|
|
181
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
188
182
|
return {
|
|
189
183
|
status: 200,
|
|
190
184
|
data: { started: true, escalationId: escalation.id, workflowId: newWorkflowId },
|
|
191
185
|
};
|
|
192
186
|
}
|
|
193
187
|
// ── Shared helpers ───────────────────────────────────────────────────────
|
|
194
|
-
function publishResolvedEvent(escalation, overrides) {
|
|
195
|
-
(0, publish_1.publishEscalationEvent)({
|
|
196
|
-
type: 'escalation.resolved',
|
|
197
|
-
source: 'api',
|
|
198
|
-
workflowId: overrides?.workflowId || escalation.workflow_id || '',
|
|
199
|
-
workflowName: overrides?.workflowName || escalation.workflow_type || '',
|
|
200
|
-
taskQueue: overrides?.taskQueue || escalation.task_queue || '',
|
|
201
|
-
taskId: escalation.task_id,
|
|
202
|
-
escalationId: escalation.id,
|
|
203
|
-
originId: escalation.origin_id ?? undefined,
|
|
204
|
-
status: overrides?.status || 'resolved',
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
188
|
function signaledResult(escalation, workflowId) {
|
|
208
189
|
return {
|
|
209
190
|
status: 200,
|
package/build/bin/ltc.js
CHANGED
|
@@ -145,11 +145,13 @@ escCmd.command('claim-by-meta <key> <value>')
|
|
|
145
145
|
.description('Claim an escalation by metadata key-value pair')
|
|
146
146
|
.option('--duration <minutes>', 'Claim duration in minutes')
|
|
147
147
|
.option('--assignee <external_id>', 'Claim on behalf of user (external_id)')
|
|
148
|
+
.option('--meta <json>', 'Merge metadata (JSON object, e.g. \'{"claimedBy":"jimbo"}\')')
|
|
148
149
|
.action(wrap(esc.claimByMetadata));
|
|
149
150
|
escCmd.command('resolve-by-meta <key> <value>')
|
|
150
151
|
.description('Resolve an escalation by metadata key-value pair')
|
|
151
152
|
.option('--data <json>', 'Resolver payload (JSON string)')
|
|
152
153
|
.option('--assignee <external_id>', 'Resolve on behalf of user (external_id)')
|
|
154
|
+
.option('--meta <json>', 'Merge metadata (JSON object)')
|
|
153
155
|
.action(wrap(esc.resolveByMetadata));
|
|
154
156
|
// ── Workflows ────────────────────────────────────────────────────────────
|
|
155
157
|
const wfCmd = commander_1.program.command('workflows').alias('wf').description('Manage durable workflows');
|
|
@@ -20,9 +20,11 @@ export declare function findByMetadata(key: string, value: string, opts: ListOpt
|
|
|
20
20
|
export declare function claimByMetadata(key: string, value: string, opts: {
|
|
21
21
|
duration?: string;
|
|
22
22
|
assignee?: string;
|
|
23
|
+
meta?: string;
|
|
23
24
|
}): Promise<void>;
|
|
24
25
|
export declare function resolveByMetadata(key: string, value: string, opts: {
|
|
25
26
|
data?: string;
|
|
26
27
|
assignee?: string;
|
|
28
|
+
meta?: string;
|
|
27
29
|
}): Promise<void>;
|
|
28
30
|
export {};
|
|
@@ -86,6 +86,8 @@ async function claimByMetadata(key, value, opts) {
|
|
|
86
86
|
body.durationMinutes = parseInt(opts.duration, 10);
|
|
87
87
|
if (opts.assignee)
|
|
88
88
|
body.assignee = opts.assignee;
|
|
89
|
+
if (opts.meta)
|
|
90
|
+
body.metadata = JSON.parse(opts.meta);
|
|
89
91
|
const data = await (0, client_1.apiFetch)('/escalations/claim-by-metadata', {
|
|
90
92
|
method: 'POST',
|
|
91
93
|
body: JSON.stringify(body),
|
|
@@ -97,6 +99,8 @@ async function resolveByMetadata(key, value, opts) {
|
|
|
97
99
|
const body = { key, value, resolverPayload };
|
|
98
100
|
if (opts.assignee)
|
|
99
101
|
body.assignee = opts.assignee;
|
|
102
|
+
if (opts.meta)
|
|
103
|
+
body.metadata = JSON.parse(opts.meta);
|
|
100
104
|
await (0, client_1.apiFetch)('/escalations/resolve-by-metadata', {
|
|
101
105
|
method: 'POST',
|
|
102
106
|
body: JSON.stringify(body),
|
|
@@ -9,12 +9,14 @@ exports.publishFileEvent = publishFileEvent;
|
|
|
9
9
|
exports.publishAgentEvent = publishAgentEvent;
|
|
10
10
|
exports.publishWorkflowEvent = publishWorkflowEvent;
|
|
11
11
|
const index_1 = require("./index");
|
|
12
|
+
const logger_1 = require("../logger");
|
|
12
13
|
/**
|
|
13
14
|
* Fire-and-forget publish helper. Swallows errors (best-effort).
|
|
14
15
|
*/
|
|
15
16
|
function fireAndForget(event) {
|
|
16
17
|
if (!index_1.eventRegistry.hasAdapters)
|
|
17
18
|
return Promise.resolve();
|
|
19
|
+
logger_1.loggerRegistry.info(`[lt-pub] ${event.type} ${event.workflowId || ''} ${event.escalationId || event.taskId || ''}`);
|
|
18
20
|
return index_1.eventRegistry.publish(event).catch(() => { });
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
@@ -58,7 +58,7 @@ function registerMetadataRoutes(router) {
|
|
|
58
58
|
/**
|
|
59
59
|
* POST /api/escalations/claim-by-metadata
|
|
60
60
|
* Find and claim an escalation by metadata key-value pair.
|
|
61
|
-
* Body: { key, value, durationMinutes?, assignee? }
|
|
61
|
+
* Body: { key, value, durationMinutes?, assignee?, metadata? }
|
|
62
62
|
*/
|
|
63
63
|
router.post('/claim-by-metadata', async (req, res) => {
|
|
64
64
|
const result = await api.claimByMetadata({
|
|
@@ -66,13 +66,14 @@ function registerMetadataRoutes(router) {
|
|
|
66
66
|
value: req.body?.value,
|
|
67
67
|
durationMinutes: req.body?.durationMinutes,
|
|
68
68
|
assignee: req.body?.assignee,
|
|
69
|
+
metadata: req.body?.metadata,
|
|
69
70
|
}, req.auth);
|
|
70
71
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
71
72
|
});
|
|
72
73
|
/**
|
|
73
74
|
* POST /api/escalations/resolve-by-metadata
|
|
74
75
|
* Find and resolve an escalation by metadata key-value pair.
|
|
75
|
-
* Body: { key, value, resolverPayload, assignee? }
|
|
76
|
+
* Body: { key, value, resolverPayload, assignee?, metadata? }
|
|
76
77
|
*/
|
|
77
78
|
router.post('/resolve-by-metadata', async (req, res) => {
|
|
78
79
|
const result = await api.resolveByMetadata({
|
|
@@ -80,6 +81,7 @@ function registerMetadataRoutes(router) {
|
|
|
80
81
|
value: req.body?.value,
|
|
81
82
|
resolverPayload: req.body?.resolverPayload,
|
|
82
83
|
assignee: req.body?.assignee,
|
|
84
|
+
metadata: req.body?.metadata,
|
|
83
85
|
}, req.auth);
|
|
84
86
|
res.status(result.status).json(result.data ?? { error: result.error });
|
|
85
87
|
});
|
package/build/sdk/index.d.ts
CHANGED
|
@@ -162,12 +162,14 @@ export declare function createClient(options?: LTClientOptions): {
|
|
|
162
162
|
value: string;
|
|
163
163
|
durationMinutes?: number;
|
|
164
164
|
assignee?: string;
|
|
165
|
+
metadata?: Record<string, any>;
|
|
165
166
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
166
167
|
resolveByMetadata: (input: {
|
|
167
168
|
key: string;
|
|
168
169
|
value: string;
|
|
169
170
|
resolverPayload: Record<string, any>;
|
|
170
171
|
assignee?: string;
|
|
172
|
+
metadata?: Record<string, any>;
|
|
171
173
|
}, auth?: LTApiAuth) => Promise<LTApiResult<any>>;
|
|
172
174
|
};
|
|
173
175
|
workflows: {
|
|
@@ -49,4 +49,4 @@ export declare function findByMetadata(key: string, value: string, status?: stri
|
|
|
49
49
|
escalations: LTEscalationRecord[];
|
|
50
50
|
total: number;
|
|
51
51
|
}>;
|
|
52
|
-
export declare function claimByMetadata(key: string, value: string, userId: string, durationMinutes?: number): Promise<ClaimResult | null>;
|
|
52
|
+
export declare function claimByMetadata(key: string, value: string, userId: string, durationMinutes?: number, metadata?: Record<string, any>): Promise<ClaimResult | null>;
|
|
@@ -17,8 +17,11 @@ exports.getEscalationsByOriginId = getEscalationsByOriginId;
|
|
|
17
17
|
exports.findByMetadata = findByMetadata;
|
|
18
18
|
exports.claimByMetadata = claimByMetadata;
|
|
19
19
|
const db_1 = require("../../lib/db");
|
|
20
|
+
const publish_1 = require("../../lib/events/publish");
|
|
21
|
+
const logger_1 = require("../../lib/logger");
|
|
20
22
|
const sql_1 = require("./sql");
|
|
21
23
|
async function createEscalation(input) {
|
|
24
|
+
logger_1.loggerRegistry.info(`[escalation-crud] createEscalation called for wf=${input.workflow_id} type=${input.type} caller=${new Error().stack?.split('\n')[2]?.trim()}`);
|
|
22
25
|
const pool = (0, db_1.getPool)();
|
|
23
26
|
// Ensure the role exists in lt_roles (FK constraint)
|
|
24
27
|
await pool.query(sql_1.ENSURE_ROLE_EXISTS, [input.role]);
|
|
@@ -40,7 +43,18 @@ async function createEscalation(input) {
|
|
|
40
43
|
input.trace_id || null,
|
|
41
44
|
input.span_id || null,
|
|
42
45
|
]);
|
|
43
|
-
|
|
46
|
+
const escalation = rows[0];
|
|
47
|
+
(0, publish_1.publishEscalationEvent)({
|
|
48
|
+
type: 'escalation.created',
|
|
49
|
+
source: 'service',
|
|
50
|
+
workflowId: escalation.workflow_id || '',
|
|
51
|
+
workflowName: escalation.workflow_type || '',
|
|
52
|
+
taskQueue: escalation.task_queue || '',
|
|
53
|
+
escalationId: escalation.id,
|
|
54
|
+
status: 'pending',
|
|
55
|
+
data: { type: input.type, role: input.role },
|
|
56
|
+
});
|
|
57
|
+
return escalation;
|
|
44
58
|
}
|
|
45
59
|
/**
|
|
46
60
|
* Atomic claim operation. Does NOT change status — "claimed" is implicit
|
|
@@ -58,15 +72,39 @@ async function claimEscalation(id, userId, durationMinutes = 30) {
|
|
|
58
72
|
if (rows.length === 0)
|
|
59
73
|
return null;
|
|
60
74
|
const row = rows[0];
|
|
75
|
+
const escalation = row;
|
|
76
|
+
(0, publish_1.publishEscalationEvent)({
|
|
77
|
+
type: 'escalation.claimed',
|
|
78
|
+
source: 'service',
|
|
79
|
+
workflowId: escalation.workflow_id || '',
|
|
80
|
+
workflowName: escalation.workflow_type || '',
|
|
81
|
+
taskQueue: escalation.task_queue || '',
|
|
82
|
+
escalationId: escalation.id,
|
|
83
|
+
status: 'claimed',
|
|
84
|
+
data: { assigned_to: userId },
|
|
85
|
+
});
|
|
61
86
|
return {
|
|
62
|
-
escalation
|
|
87
|
+
escalation,
|
|
63
88
|
isExtension: row.prev_assigned_to === userId,
|
|
64
89
|
};
|
|
65
90
|
}
|
|
66
91
|
async function resolveEscalation(id, resolverPayload) {
|
|
67
92
|
const pool = (0, db_1.getPool)();
|
|
68
93
|
const { rows } = await pool.query(sql_1.RESOLVE_ESCALATION, [id, JSON.stringify(resolverPayload)]);
|
|
69
|
-
|
|
94
|
+
const escalation = rows[0] || null;
|
|
95
|
+
if (escalation) {
|
|
96
|
+
(0, publish_1.publishEscalationEvent)({
|
|
97
|
+
type: 'escalation.resolved',
|
|
98
|
+
source: 'service',
|
|
99
|
+
workflowId: escalation.workflow_id || '',
|
|
100
|
+
workflowName: escalation.workflow_type || '',
|
|
101
|
+
taskQueue: escalation.task_queue || '',
|
|
102
|
+
escalationId: escalation.id,
|
|
103
|
+
status: 'resolved',
|
|
104
|
+
data: {},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return escalation;
|
|
70
108
|
}
|
|
71
109
|
/**
|
|
72
110
|
* Bulk update priority for a set of escalations.
|
|
@@ -163,15 +201,27 @@ async function findByMetadata(key, value, status, limit = 50, offset = 0) {
|
|
|
163
201
|
total: parseInt(countResult.rows[0].count, 10),
|
|
164
202
|
};
|
|
165
203
|
}
|
|
166
|
-
async function claimByMetadata(key, value, userId, durationMinutes = 30) {
|
|
204
|
+
async function claimByMetadata(key, value, userId, durationMinutes = 30, metadata) {
|
|
167
205
|
const pool = (0, db_1.getPool)();
|
|
168
206
|
const filter = JSON.stringify({ [key]: value });
|
|
169
|
-
const
|
|
207
|
+
const metaPatch = metadata ? JSON.stringify(metadata) : null;
|
|
208
|
+
const { rows } = await pool.query(sql_1.CLAIM_BY_METADATA, [filter, userId, durationMinutes, metaPatch]);
|
|
170
209
|
if (rows.length === 0)
|
|
171
210
|
return null;
|
|
172
211
|
const row = rows[0];
|
|
212
|
+
const escalation = row;
|
|
213
|
+
(0, publish_1.publishEscalationEvent)({
|
|
214
|
+
type: 'escalation.claimed',
|
|
215
|
+
source: 'service',
|
|
216
|
+
workflowId: escalation.workflow_id || '',
|
|
217
|
+
workflowName: escalation.workflow_type || '',
|
|
218
|
+
taskQueue: escalation.task_queue || '',
|
|
219
|
+
escalationId: escalation.id,
|
|
220
|
+
status: 'claimed',
|
|
221
|
+
data: { assigned_to: userId },
|
|
222
|
+
});
|
|
173
223
|
return {
|
|
174
|
-
escalation
|
|
224
|
+
escalation,
|
|
175
225
|
isExtension: row.prev_assigned_to === userId,
|
|
176
226
|
};
|
|
177
227
|
}
|
|
@@ -22,5 +22,5 @@ export declare const LIST_DISTINCT_TYPES = "SELECT DISTINCT type FROM lt_escalat
|
|
|
22
22
|
/** Find escalations by a single metadata key-value pair. */
|
|
23
23
|
export declare const FIND_BY_METADATA = "SELECT * FROM lt_escalations\nWHERE metadata @> $1::jsonb\n AND ($2::text IS NULL OR status = $2)\nORDER BY priority ASC, created_at ASC\nLIMIT $3 OFFSET $4";
|
|
24
24
|
export declare const COUNT_BY_METADATA = "SELECT COUNT(*) FROM lt_escalations\nWHERE metadata @> $1::jsonb\n AND ($2::text IS NULL OR status = $2)";
|
|
25
|
-
/** Atomic claim by metadata: find one available escalation and
|
|
26
|
-
export declare const CLAIM_BY_METADATA = "WITH target AS (\n SELECT id, assigned_to\n FROM lt_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND (\n assigned_to IS NULL\n OR assigned_until <= NOW()\n OR assigned_to = $2\n )\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE SKIP LOCKED\n),\nupdated AS (\n UPDATE lt_escalations e\n SET assigned_to = $2,\n claimed_at = NOW(),\n assigned_until = NOW() + INTERVAL '1 minute' * $3\n FROM target t\n WHERE e.id = t.id\n RETURNING e.*, t.assigned_to AS prev_assigned_to\n)\nSELECT * FROM updated";
|
|
25
|
+
/** Atomic claim by metadata: find one available escalation, claim it, and optionally merge metadata. */
|
|
26
|
+
export declare const CLAIM_BY_METADATA = "WITH target AS (\n SELECT id, assigned_to\n FROM lt_escalations\n WHERE metadata @> $1::jsonb\n AND status = 'pending'\n AND (\n assigned_to IS NULL\n OR assigned_until <= NOW()\n OR assigned_to = $2\n )\n ORDER BY priority ASC, created_at ASC\n LIMIT 1\n FOR UPDATE SKIP LOCKED\n),\nupdated AS (\n UPDATE lt_escalations e\n SET assigned_to = $2,\n claimed_at = NOW(),\n assigned_until = NOW() + INTERVAL '1 minute' * $3,\n metadata = CASE WHEN $4::jsonb IS NOT NULL\n THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb\n ELSE e.metadata END\n FROM target t\n WHERE e.id = t.id\n RETURNING e.*, t.assigned_to AS prev_assigned_to\n)\nSELECT * FROM updated";
|
|
@@ -145,7 +145,7 @@ exports.COUNT_BY_METADATA = `\
|
|
|
145
145
|
SELECT COUNT(*) FROM lt_escalations
|
|
146
146
|
WHERE metadata @> $1::jsonb
|
|
147
147
|
AND ($2::text IS NULL OR status = $2)`;
|
|
148
|
-
/** Atomic claim by metadata: find one available escalation and
|
|
148
|
+
/** Atomic claim by metadata: find one available escalation, claim it, and optionally merge metadata. */
|
|
149
149
|
exports.CLAIM_BY_METADATA = `\
|
|
150
150
|
WITH target AS (
|
|
151
151
|
SELECT id, assigned_to
|
|
@@ -165,7 +165,10 @@ updated AS (
|
|
|
165
165
|
UPDATE lt_escalations e
|
|
166
166
|
SET assigned_to = $2,
|
|
167
167
|
claimed_at = NOW(),
|
|
168
|
-
assigned_until = NOW() + INTERVAL '1 minute' * $3
|
|
168
|
+
assigned_until = NOW() + INTERVAL '1 minute' * $3,
|
|
169
|
+
metadata = CASE WHEN $4::jsonb IS NOT NULL
|
|
170
|
+
THEN COALESCE(e.metadata, '{}'::jsonb) || $4::jsonb
|
|
171
|
+
ELSE e.metadata END
|
|
169
172
|
FROM target t
|
|
170
173
|
WHERE e.id = t.id
|
|
171
174
|
RETURNING e.*, t.assigned_to AS prev_assigned_to
|
|
@@ -39,7 +39,6 @@ exports.ltEnrichEscalationRouting = ltEnrichEscalationRouting;
|
|
|
39
39
|
exports.ltCreateEscalation = ltCreateEscalation;
|
|
40
40
|
const escalationService = __importStar(require("../../escalation"));
|
|
41
41
|
const logger_1 = require("../../../lib/logger");
|
|
42
|
-
const publish_1 = require("../../../lib/events/publish");
|
|
43
42
|
/**
|
|
44
43
|
* Resolve an escalation record. Called by the interceptor after
|
|
45
44
|
* detecting a re-run (resolver data present in the envelope).
|
|
@@ -107,15 +106,6 @@ async function ltCreateEscalation(input) {
|
|
|
107
106
|
trace_id: input.traceId,
|
|
108
107
|
span_id: input.spanId,
|
|
109
108
|
});
|
|
110
|
-
(
|
|
111
|
-
type: 'escalation.created',
|
|
112
|
-
source: 'interceptor',
|
|
113
|
-
workflowId: input.workflowId || '',
|
|
114
|
-
workflowName: input.workflowType || '',
|
|
115
|
-
taskQueue: input.taskQueue || '',
|
|
116
|
-
escalationId: escalation.id,
|
|
117
|
-
status: 'pending',
|
|
118
|
-
data: { type: input.type, role: input.role },
|
|
119
|
-
});
|
|
109
|
+
// Event published by service layer (services/escalation/crud.ts)
|
|
120
110
|
return escalation.id;
|
|
121
111
|
}
|
|
@@ -81,16 +81,7 @@ async function ltCreateTask(input) {
|
|
|
81
81
|
executing_as: input.executingAs,
|
|
82
82
|
status: input.status,
|
|
83
83
|
});
|
|
84
|
-
(
|
|
85
|
-
type: 'task.created',
|
|
86
|
-
source: 'interceptor',
|
|
87
|
-
workflowId: input.workflowId,
|
|
88
|
-
workflowName: input.workflowType,
|
|
89
|
-
taskQueue: input.taskQueue || 'unknown',
|
|
90
|
-
taskId: task.id,
|
|
91
|
-
originId: input.originId,
|
|
92
|
-
status: input.status || 'pending',
|
|
93
|
-
});
|
|
84
|
+
// Event published by service layer (services/task/crud.ts)
|
|
94
85
|
return task.id;
|
|
95
86
|
}
|
|
96
87
|
/**
|
|
@@ -109,19 +100,7 @@ async function ltCompleteTask(input) {
|
|
|
109
100
|
data: input.data,
|
|
110
101
|
milestones: input.milestones,
|
|
111
102
|
});
|
|
112
|
-
//
|
|
113
|
-
if (input.workflowId) {
|
|
114
|
-
(0, publish_1.publishTaskEvent)({
|
|
115
|
-
type: 'task.completed',
|
|
116
|
-
source: 'orchestrator',
|
|
117
|
-
workflowId: input.workflowId,
|
|
118
|
-
workflowName: input.workflowName || 'unknown',
|
|
119
|
-
taskQueue: input.taskQueue || 'unknown',
|
|
120
|
-
taskId: input.taskId,
|
|
121
|
-
status: 'completed',
|
|
122
|
-
milestones: input.milestones,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
103
|
+
// task.completed event published by service layer (services/task/crud.ts)
|
|
125
104
|
// Publish milestone event from orchestrator context
|
|
126
105
|
if (input.milestones?.length && input.workflowId) {
|
|
127
106
|
(0, publish_1.publishMilestoneEvent)({
|
|
@@ -115,28 +115,8 @@ async function createAdvisoryEscalation(state, result) {
|
|
|
115
115
|
traceId: state.traceId,
|
|
116
116
|
spanId: state.spanId,
|
|
117
117
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
source: 'interceptor',
|
|
121
|
-
workflowId: state.workflowId,
|
|
122
|
-
workflowName: state.workflowName,
|
|
123
|
-
taskQueue: state.taskQueue,
|
|
124
|
-
taskId: state.taskId,
|
|
125
|
-
escalationId,
|
|
126
|
-
originId: state.envelope?.lt?.originId,
|
|
127
|
-
status: 'pending',
|
|
128
|
-
data: result.data,
|
|
129
|
-
});
|
|
130
|
-
(0, publish_1.publishTaskEvent)({
|
|
131
|
-
type: 'task.escalated',
|
|
132
|
-
source: 'interceptor',
|
|
133
|
-
workflowId: state.workflowId,
|
|
134
|
-
workflowName: state.workflowName,
|
|
135
|
-
taskQueue: state.taskQueue,
|
|
136
|
-
taskId: state.taskId,
|
|
137
|
-
originId: state.envelope?.lt?.originId,
|
|
138
|
-
status: 'needs_intervention',
|
|
139
|
-
});
|
|
118
|
+
// escalation.created event published by service layer (services/escalation/crud.ts)
|
|
119
|
+
// task.escalated event published by service layer (services/task/crud.ts)
|
|
140
120
|
// Auto-claim to submitting user if known
|
|
141
121
|
const userId = state.envelope?.lt?.userId;
|
|
142
122
|
if (userId) {
|
|
@@ -41,28 +41,8 @@ async function handleEscalation(state, result) {
|
|
|
41
41
|
traceId: state.traceId,
|
|
42
42
|
spanId: state.spanId,
|
|
43
43
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
source: 'interceptor',
|
|
47
|
-
workflowId: state.workflowId,
|
|
48
|
-
workflowName: state.workflowName,
|
|
49
|
-
taskQueue: state.taskQueue,
|
|
50
|
-
taskId: state.taskId,
|
|
51
|
-
escalationId,
|
|
52
|
-
originId: state.envelope?.lt?.originId,
|
|
53
|
-
status: 'pending',
|
|
54
|
-
data: result.data,
|
|
55
|
-
});
|
|
56
|
-
(0, publish_1.publishTaskEvent)({
|
|
57
|
-
type: 'task.escalated',
|
|
58
|
-
source: 'interceptor',
|
|
59
|
-
workflowId: state.workflowId,
|
|
60
|
-
workflowName: state.workflowName,
|
|
61
|
-
taskQueue: state.taskQueue,
|
|
62
|
-
taskId: state.taskId,
|
|
63
|
-
originId: state.envelope?.lt?.originId,
|
|
64
|
-
status: 'needs_intervention',
|
|
65
|
-
});
|
|
44
|
+
// escalation.created event published by service layer (services/escalation/crud.ts)
|
|
45
|
+
// task.escalated event published by service layer (services/task/crud.ts)
|
|
66
46
|
return result;
|
|
67
47
|
}
|
|
68
48
|
/**
|
|
@@ -109,28 +89,9 @@ async function handleErrorEscalation(state, err) {
|
|
|
109
89
|
traceId: state.traceId,
|
|
110
90
|
spanId: state.spanId,
|
|
111
91
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
workflowId: state.workflowId,
|
|
116
|
-
workflowName: state.workflowName,
|
|
117
|
-
taskQueue: state.taskQueue,
|
|
118
|
-
taskId: state.taskId,
|
|
119
|
-
escalationId: errorEscalationId,
|
|
120
|
-
originId: state.envelope?.lt?.originId,
|
|
121
|
-
status: 'pending',
|
|
122
|
-
data: { error: err.message },
|
|
123
|
-
});
|
|
124
|
-
(0, publish_1.publishTaskEvent)({
|
|
125
|
-
type: 'task.escalated',
|
|
126
|
-
source: 'interceptor',
|
|
127
|
-
workflowId: state.workflowId,
|
|
128
|
-
workflowName: state.workflowName,
|
|
129
|
-
taskQueue: state.taskQueue,
|
|
130
|
-
taskId: state.taskId,
|
|
131
|
-
originId: state.envelope?.lt?.originId,
|
|
132
|
-
status: 'needs_intervention',
|
|
133
|
-
});
|
|
92
|
+
// escalation.created event published by service layer (services/escalation/crud.ts)
|
|
93
|
+
// task.escalated event published by service layer (services/task/crud.ts)
|
|
94
|
+
// Publish workflow.failed event (error path only runs once — no replay guard needed)
|
|
134
95
|
(0, publish_1.publishWorkflowEvent)({
|
|
135
96
|
type: 'workflow.failed',
|
|
136
97
|
source: 'interceptor',
|
|
@@ -44,6 +44,7 @@ const escalation_1 = require("./escalation");
|
|
|
44
44
|
const completion_1 = require("./completion");
|
|
45
45
|
const lifecycle_1 = require("./lifecycle");
|
|
46
46
|
const context_2 = require("../iam/context");
|
|
47
|
+
const publish_1 = require("../../lib/events/publish");
|
|
47
48
|
const envelope_1 = require("../iam/envelope");
|
|
48
49
|
const DEFAULT_ACTIVITY_QUEUE = 'lt-interceptor';
|
|
49
50
|
/**
|
|
@@ -104,13 +105,36 @@ function createLTInterceptor(options) {
|
|
|
104
105
|
// task creation, and escalation wiring.
|
|
105
106
|
if (envelope?.metadata?.certified !== true) {
|
|
106
107
|
const toolCtx = (0, envelope_1.buildToolContextFromEnvelope)(envelope, wf.workflowId, wf.workflowTrace, wf.workflowSpan);
|
|
107
|
-
|
|
108
|
+
// Publish workflow events even for uncertified workflows
|
|
109
|
+
const taskQueue = (0, lifecycle_1.deriveTaskQueue)(wf);
|
|
110
|
+
(0, lifecycle_1.publishStartedEvents)(wf, taskQueue, undefined, wf.workflowId);
|
|
111
|
+
const result = toolCtx ? await (0, context_2.runWithToolContext)(toolCtx, next) : await next();
|
|
112
|
+
(0, publish_1.publishWorkflowEvent)({
|
|
113
|
+
type: 'workflow.completed',
|
|
114
|
+
source: 'interceptor',
|
|
115
|
+
workflowId: wf.workflowId,
|
|
116
|
+
workflowName: wf.workflowName,
|
|
117
|
+
taskQueue,
|
|
118
|
+
status: 'completed',
|
|
119
|
+
});
|
|
120
|
+
return result;
|
|
108
121
|
}
|
|
109
122
|
// 3. Load config — unregistered/uncertified workflows get ToolContext only
|
|
110
123
|
const wfConfig = await activities.ltGetWorkflowConfig(wf.workflowName);
|
|
111
124
|
if (!wfConfig) {
|
|
112
125
|
const toolCtx = (0, envelope_1.buildToolContextFromEnvelope)(envelope, wf.workflowId, wf.workflowTrace, wf.workflowSpan);
|
|
113
|
-
|
|
126
|
+
const taskQueue2 = (0, lifecycle_1.deriveTaskQueue)(wf);
|
|
127
|
+
(0, lifecycle_1.publishStartedEvents)(wf, taskQueue2, undefined, wf.workflowId);
|
|
128
|
+
const result2 = toolCtx ? await (0, context_2.runWithToolContext)(toolCtx, next) : await next();
|
|
129
|
+
(0, publish_1.publishWorkflowEvent)({
|
|
130
|
+
type: 'workflow.completed',
|
|
131
|
+
source: 'interceptor',
|
|
132
|
+
workflowId: wf.workflowId,
|
|
133
|
+
workflowName: wf.workflowName,
|
|
134
|
+
taskQueue: taskQueue2,
|
|
135
|
+
status: 'completed',
|
|
136
|
+
});
|
|
137
|
+
return result2;
|
|
114
138
|
}
|
|
115
139
|
const taskQueue = (0, lifecycle_1.deriveTaskQueue)(wf);
|
|
116
140
|
// 3. Find existing task and handle re-run escalation resolution
|
|
@@ -37,7 +37,7 @@ export declare function resolveReRun(activities: ProxiedActivities, envelope: LT
|
|
|
37
37
|
* Also injects originId back into the envelope for downstream consistency.
|
|
38
38
|
*/
|
|
39
39
|
export declare function ensureTaskWithRouting(activities: ProxiedActivities, wf: WorkflowIdentity, envelope: LTEnvelope | undefined, existingTask: any | null, taskQueue: string, reRun: ReRunContext): Promise<TaskContext>;
|
|
40
|
-
/** Publish workflow.started
|
|
40
|
+
/** Publish workflow.started event (guarded against replay). */
|
|
41
41
|
export declare function publishStartedEvents(wf: WorkflowIdentity, taskQueue: string, taskId: string | undefined, originId: string): void;
|
|
42
42
|
/** Complete a task and signal parent for plain (non-LTReturn) results. */
|
|
43
43
|
export declare function completePlainResult(activities: ProxiedActivities, wf: WorkflowIdentity, taskQueue: string, taskId: string | undefined, routing: Record<string, any> | null, result: any): Promise<void>;
|
|
@@ -13,6 +13,7 @@ exports.resolveReRun = resolveReRun;
|
|
|
13
13
|
exports.ensureTaskWithRouting = ensureTaskWithRouting;
|
|
14
14
|
exports.publishStartedEvents = publishStartedEvents;
|
|
15
15
|
exports.completePlainResult = completePlainResult;
|
|
16
|
+
const hotmesh_1 = require("@hotmeshio/hotmesh");
|
|
16
17
|
const publish_1 = require("../../lib/events/publish");
|
|
17
18
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
18
19
|
/** Extract workflow identity fields from the HotMesh context map. */
|
|
@@ -56,17 +57,7 @@ async function resolveReRun(activities, envelope, existingTask, wf, taskQueue) {
|
|
|
56
57
|
escalationId: escalationId,
|
|
57
58
|
resolverPayload: envelope.resolver,
|
|
58
59
|
});
|
|
59
|
-
|
|
60
|
-
type: 'escalation.resolved',
|
|
61
|
-
source: 'interceptor',
|
|
62
|
-
workflowId: wf.workflowId,
|
|
63
|
-
workflowName: wf.workflowName,
|
|
64
|
-
taskQueue,
|
|
65
|
-
taskId: task?.id || existingTask?.id,
|
|
66
|
-
escalationId: escalationId,
|
|
67
|
-
originId: envelope?.lt?.originId,
|
|
68
|
-
status: 'resolved',
|
|
69
|
-
});
|
|
60
|
+
// escalation.resolved event published by service layer (services/escalation/crud.ts)
|
|
70
61
|
}
|
|
71
62
|
return { isReRun, task, metadata };
|
|
72
63
|
}
|
|
@@ -128,28 +119,23 @@ async function ensureTaskWithRouting(activities, wf, envelope, existingTask, tas
|
|
|
128
119
|
}
|
|
129
120
|
return { taskId, routing, originId };
|
|
130
121
|
}
|
|
131
|
-
/** Publish workflow.started
|
|
122
|
+
/** Publish workflow.started event (guarded against replay). */
|
|
132
123
|
function publishStartedEvents(wf, taskQueue, taskId, originId) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
taskQueue,
|
|
149
|
-
taskId: taskId,
|
|
150
|
-
originId,
|
|
151
|
-
status: 'in_progress',
|
|
152
|
-
});
|
|
124
|
+
const { replay } = hotmesh_1.Durable.workflow.workflowInfo();
|
|
125
|
+
const isFirstExecution = Object.keys(replay).length === 0;
|
|
126
|
+
if (isFirstExecution) {
|
|
127
|
+
(0, publish_1.publishWorkflowEvent)({
|
|
128
|
+
type: 'workflow.started',
|
|
129
|
+
source: 'interceptor',
|
|
130
|
+
workflowId: wf.workflowId,
|
|
131
|
+
workflowName: wf.workflowName,
|
|
132
|
+
taskQueue,
|
|
133
|
+
taskId,
|
|
134
|
+
originId,
|
|
135
|
+
status: 'running',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// task.started event published by service layer (services/task/crud.ts)
|
|
153
139
|
}
|
|
154
140
|
/** Complete a task and signal parent for plain (non-LTReturn) results. */
|
|
155
141
|
async function completePlainResult(activities, wf, taskQueue, taskId, routing, result) {
|
|
@@ -8,6 +8,7 @@ exports.getTaskBySignalId = getTaskBySignalId;
|
|
|
8
8
|
exports.getTaskByWorkflowId = getTaskByWorkflowId;
|
|
9
9
|
exports.listTasks = listTasks;
|
|
10
10
|
const db_1 = require("../../lib/db");
|
|
11
|
+
const publish_1 = require("../../lib/events/publish");
|
|
11
12
|
const sql_1 = require("./sql");
|
|
12
13
|
async function createTask(input) {
|
|
13
14
|
const pool = (0, db_1.getPool)();
|
|
@@ -30,7 +31,18 @@ async function createTask(input) {
|
|
|
30
31
|
input.executing_as || null,
|
|
31
32
|
input.status || null,
|
|
32
33
|
]);
|
|
33
|
-
|
|
34
|
+
const task = rows[0];
|
|
35
|
+
(0, publish_1.publishTaskEvent)({
|
|
36
|
+
type: 'task.created',
|
|
37
|
+
source: 'service',
|
|
38
|
+
workflowId: task.workflow_id || '',
|
|
39
|
+
workflowName: task.workflow_type || '',
|
|
40
|
+
taskQueue: task.task_queue || '',
|
|
41
|
+
taskId: task.id,
|
|
42
|
+
originId: task.origin_id || undefined,
|
|
43
|
+
status: task.status || 'pending',
|
|
44
|
+
});
|
|
45
|
+
return task;
|
|
34
46
|
}
|
|
35
47
|
async function updateTask(id, input) {
|
|
36
48
|
const pool = (0, db_1.getPool)();
|
|
@@ -59,7 +71,29 @@ async function updateTask(id, input) {
|
|
|
59
71
|
}
|
|
60
72
|
values.push(id);
|
|
61
73
|
const { rows } = await pool.query(`UPDATE lt_tasks SET ${sets.join(', ')} WHERE id = $${idx} RETURNING *`, values);
|
|
62
|
-
|
|
74
|
+
const task = rows[0];
|
|
75
|
+
// Publish status-driven events from the single write path
|
|
76
|
+
if (input.status && task) {
|
|
77
|
+
const STATUS_EVENTS = {
|
|
78
|
+
in_progress: 'task.started',
|
|
79
|
+
completed: 'task.completed',
|
|
80
|
+
needs_intervention: 'task.escalated',
|
|
81
|
+
};
|
|
82
|
+
const eventType = STATUS_EVENTS[input.status];
|
|
83
|
+
if (eventType) {
|
|
84
|
+
(0, publish_1.publishTaskEvent)({
|
|
85
|
+
type: eventType,
|
|
86
|
+
source: 'service',
|
|
87
|
+
workflowId: task.workflow_id || '',
|
|
88
|
+
workflowName: task.workflow_type || '',
|
|
89
|
+
taskQueue: task.task_queue || '',
|
|
90
|
+
taskId: task.id,
|
|
91
|
+
originId: task.origin_id || undefined,
|
|
92
|
+
status: input.status,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return task;
|
|
63
97
|
}
|
|
64
98
|
async function appendMilestones(id, milestones) {
|
|
65
99
|
const pool = (0, db_1.getPool)();
|
|
@@ -719,7 +719,7 @@ Finds one available (pending + unassigned/expired) escalation matching the metad
|
|
|
719
719
|
"key": "orderId",
|
|
720
720
|
"value": "order-123",
|
|
721
721
|
"durationMinutes": 30,
|
|
722
|
-
"
|
|
722
|
+
"metadata": { "claimedBy": "jimbo", "station": "scanning" }
|
|
723
723
|
}
|
|
724
724
|
```
|
|
725
725
|
|
|
@@ -728,7 +728,8 @@ Finds one available (pending + unassigned/expired) escalation matching the metad
|
|
|
728
728
|
| `key` | `string` | **Required.** Metadata field name |
|
|
729
729
|
| `value` | `string` | **Required.** Metadata field value |
|
|
730
730
|
| `durationMinutes` | `number` | Claim duration (default 30) |
|
|
731
|
-
| `assignee` | `string` |
|
|
731
|
+
| `assignee` | `string` | Claim as a Long Tail user (resolved via `getUserByExternalId`) |
|
|
732
|
+
| `metadata` | `object` | Additional metadata to merge (new keys added, existing overwritten) |
|
|
732
733
|
|
|
733
734
|
**Response 200:**
|
|
734
735
|
|
|
@@ -756,7 +757,7 @@ Finds the pending escalation, auto-claims if unclaimed, then resolves it. Suppor
|
|
|
756
757
|
"key": "orderId",
|
|
757
758
|
"value": "order-123",
|
|
758
759
|
"resolverPayload": { "approved": true, "targetStatus": "completed" },
|
|
759
|
-
"
|
|
760
|
+
"metadata": { "completedBy": "jimbo" }
|
|
760
761
|
}
|
|
761
762
|
```
|
|
762
763
|
|
|
@@ -765,6 +766,7 @@ Finds the pending escalation, auto-claims if unclaimed, then resolves it. Suppor
|
|
|
765
766
|
| `key` | `string` | **Required.** Metadata field name |
|
|
766
767
|
| `value` | `string` | **Required.** Metadata field value |
|
|
767
768
|
| `resolverPayload` | `object` | **Required.** Resolution data passed to the workflow |
|
|
768
|
-
| `assignee` | `string` |
|
|
769
|
+
| `assignee` | `string` | Resolve as a Long Tail user (resolved via `getUserByExternalId`) |
|
|
770
|
+
| `metadata` | `object` | Additional metadata to merge (new keys added, existing overwritten) |
|
|
769
771
|
|
|
770
772
|
**Response 200:** Same as standard resolve endpoint.
|
|
@@ -549,7 +549,7 @@ const result = await lt.escalations.claimByMetadata({
|
|
|
549
549
|
key: 'orderId',
|
|
550
550
|
value: 'order-123',
|
|
551
551
|
durationMinutes: 30,
|
|
552
|
-
|
|
552
|
+
metadata: { claimedBy: 'jimbo', station: 'scanning' },
|
|
553
553
|
});
|
|
554
554
|
```
|
|
555
555
|
|
|
@@ -560,7 +560,8 @@ const result = await lt.escalations.claimByMetadata({
|
|
|
560
560
|
| `key` | `string` | Yes | Metadata field name |
|
|
561
561
|
| `value` | `string` | Yes | Metadata field value |
|
|
562
562
|
| `durationMinutes` | `number` | No | Claim duration (default: 30) |
|
|
563
|
-
| `assignee` | `string` | No |
|
|
563
|
+
| `assignee` | `string` | No | Claim as a Long Tail user (resolved via `getUserByExternalId`) |
|
|
564
|
+
| `metadata` | `object` | No | Merge into escalation metadata (single atomic SQL call with the claim) |
|
|
564
565
|
|
|
565
566
|
**Returns:** `LTApiResult<{ escalation, isExtension }>` -- 404 if no match, 409 if already claimed.
|
|
566
567
|
|
|
@@ -577,7 +578,7 @@ const result = await lt.escalations.resolveByMetadata({
|
|
|
577
578
|
key: 'orderId',
|
|
578
579
|
value: 'order-123',
|
|
579
580
|
resolverPayload: { approved: true, targetStatus: 'completed' },
|
|
580
|
-
|
|
581
|
+
metadata: { completedBy: 'jimbo' },
|
|
581
582
|
});
|
|
582
583
|
```
|
|
583
584
|
|
|
@@ -588,7 +589,8 @@ const result = await lt.escalations.resolveByMetadata({
|
|
|
588
589
|
| `key` | `string` | Yes | Metadata field name |
|
|
589
590
|
| `value` | `string` | Yes | Metadata field value |
|
|
590
591
|
| `resolverPayload` | `object` | Yes | Resolution data passed to the workflow |
|
|
591
|
-
| `assignee` | `string` | No |
|
|
592
|
+
| `assignee` | `string` | No | Resolve as a Long Tail user (resolved via `getUserByExternalId`) |
|
|
593
|
+
| `metadata` | `object` | No | Merge into escalation metadata before resolving |
|
|
592
594
|
|
|
593
595
|
**Returns:** Same as `resolve` -- 404 if no match.
|
|
594
596
|
|
package/package.json
CHANGED