@bratsos/workflow-engine 0.6.0 → 0.8.0
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 +93 -0
- package/dist/{chunk-2MWO6UVR.js → chunk-NANAXHS5.js} +2 -2
- package/dist/chunk-NANAXHS5.js.map +1 -0
- package/dist/{chunk-DIADEUGZ.js → chunk-TWYPSP7P.js} +99 -7
- package/dist/chunk-TWYPSP7P.js.map +1 -0
- package/dist/{chunk-HKGZ2WHJ.js → chunk-WWK2SPN7.js} +16 -9
- package/dist/chunk-WWK2SPN7.js.map +1 -0
- package/dist/{chunk-HOGDFLCG.js → chunk-XPWAEYOO.js} +449 -59
- package/dist/chunk-XPWAEYOO.js.map +1 -0
- package/dist/{client-llB6XpHS.d.ts → client-YFKVt4p7.d.ts} +10 -21
- package/dist/client.d.ts +4 -4
- package/dist/client.js +1 -1
- package/dist/conventions/index.d.ts +114 -0
- package/dist/conventions/index.js +95 -0
- package/dist/conventions/index.js.map +1 -0
- package/dist/{events-D_P24UaY.d.ts → events-B3XPPu0c.d.ts} +23 -1
- package/dist/{index-sGgV8JNu.d.ts → index-CL0KmlyW.d.ts} +10 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +5 -5
- package/dist/{interface-DCdddCe0.d.ts → interface-BPz138Hf.d.ts} +110 -2
- package/dist/kernel/index.d.ts +6 -6
- package/dist/kernel/index.js +2 -2
- package/dist/kernel/testing/index.d.ts +3 -3
- package/dist/persistence/index.d.ts +2 -2
- package/dist/persistence/index.js +2 -2
- package/dist/persistence/prisma/index.d.ts +2 -2
- package/dist/persistence/prisma/index.js +2 -2
- package/dist/{plugins-Oyo_iu0l.d.ts → plugins-zT-aIcEZ.d.ts} +63 -4
- package/dist/{ports-ChGnJcn2.d.ts → ports-DUL4hlQr.d.ts} +11 -2
- package/dist/{stage-_7BKqqUG.d.ts → stage-WuK0mfrC.d.ts} +81 -1
- package/dist/testing/index.d.ts +8 -1
- package/dist/testing/index.js +88 -2
- package/dist/testing/index.js.map +1 -1
- package/package.json +6 -1
- package/skills/workflow-engine/SKILL.md +58 -1
- package/skills/workflow-engine/migrations/README.md +275 -0
- package/skills/workflow-engine/migrations/migrate-0.7-to-0.8.md +528 -0
- package/skills/workflow-engine/references/10-annotations.md +479 -0
- package/dist/chunk-2MWO6UVR.js.map +0 -1
- package/dist/chunk-DIADEUGZ.js.map +0 -1
- package/dist/chunk-HKGZ2WHJ.js.map +0 -1
- package/dist/chunk-HOGDFLCG.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StaleVersionError } from './chunk-
|
|
1
|
+
import { StaleVersionError } from './chunk-NANAXHS5.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
z.object({
|
|
@@ -28,6 +28,16 @@ var IdempotencyInProgressError = class extends Error {
|
|
|
28
28
|
this.name = "IdempotencyInProgressError";
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
+
var RunNotRunningError = class extends Error {
|
|
32
|
+
constructor(workflowRunId, currentStatus) {
|
|
33
|
+
super(
|
|
34
|
+
`Run ${workflowRunId} is ${currentStatus}, not RUNNING \u2014 transactional write aborted`
|
|
35
|
+
);
|
|
36
|
+
this.workflowRunId = workflowRunId;
|
|
37
|
+
this.currentStatus = currentStatus;
|
|
38
|
+
this.name = "RunNotRunningError";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
31
41
|
|
|
32
42
|
// src/kernel/helpers/load-workflow-context.ts
|
|
33
43
|
async function loadWorkflowContext(workflowRunId, deps) {
|
|
@@ -56,6 +66,76 @@ async function saveStageOutput(runId, workflowType, stageId, output, deps) {
|
|
|
56
66
|
return key;
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
// src/kernel/helpers/annotation-buffer.ts
|
|
70
|
+
function normalizeAnnotateArgs(args) {
|
|
71
|
+
const first = args[0];
|
|
72
|
+
if (typeof first === "string") {
|
|
73
|
+
return [
|
|
74
|
+
{ key: first, value: args[1], opts: args[2] }
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
if (first !== null && typeof first === "object" && "key" in first && typeof first.key === "string" && !("attributes" in first)) {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
key: first.key,
|
|
81
|
+
value: args[1],
|
|
82
|
+
opts: args[2]
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
const batch = first;
|
|
87
|
+
const attributes = batch?.attributes ?? {};
|
|
88
|
+
const opts = {
|
|
89
|
+
actor: batch.actor,
|
|
90
|
+
payload: batch.payload,
|
|
91
|
+
idempotencyKey: batch.idempotencyKey,
|
|
92
|
+
emitEvent: batch.emitEvent
|
|
93
|
+
};
|
|
94
|
+
return Object.entries(attributes).map(([key, value]) => ({
|
|
95
|
+
key,
|
|
96
|
+
value,
|
|
97
|
+
opts
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
var AnnotationBuffer = class {
|
|
101
|
+
items = [];
|
|
102
|
+
push(input) {
|
|
103
|
+
this.items.push(input);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Return all buffered items and clear the buffer. Idempotent — calling
|
|
107
|
+
* flush again returns an empty array.
|
|
108
|
+
*/
|
|
109
|
+
flush() {
|
|
110
|
+
const out = this.items;
|
|
111
|
+
this.items = [];
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
size() {
|
|
115
|
+
return this.items.length;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function createAnnotationBuffer() {
|
|
119
|
+
return new AnnotationBuffer();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/kernel/helpers/annotation-events.ts
|
|
123
|
+
function buildAnnotationEvents(inputs, now) {
|
|
124
|
+
return inputs.filter((input) => input.emitEvent === true).map((input) => ({
|
|
125
|
+
type: "annotation:created",
|
|
126
|
+
timestamp: now,
|
|
127
|
+
workflowRunId: input.workflowRunId,
|
|
128
|
+
key: input.key,
|
|
129
|
+
value: input.value,
|
|
130
|
+
scope: input.scope,
|
|
131
|
+
scopeId: input.scopeId ?? void 0,
|
|
132
|
+
attempt: input.attempt,
|
|
133
|
+
actorKind: input.actor?.kind,
|
|
134
|
+
actorId: input.actor?.id,
|
|
135
|
+
actorVersion: input.actor?.version
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
|
|
59
139
|
// src/kernel/helpers/create-storage-shim.ts
|
|
60
140
|
function createStorageShim(workflowRunId, workflowType, deps) {
|
|
61
141
|
return {
|
|
@@ -78,6 +158,60 @@ function createStorageShim(workflowRunId, workflowType, deps) {
|
|
|
78
158
|
};
|
|
79
159
|
}
|
|
80
160
|
|
|
161
|
+
// src/kernel/helpers/legacy-metadata-shim.ts
|
|
162
|
+
function synthesizeLegacyMetadata(run, filters = {}) {
|
|
163
|
+
if (!run.metadata || typeof run.metadata !== "object") return [];
|
|
164
|
+
if (Array.isArray(run.metadata)) return [];
|
|
165
|
+
if (filters.scope !== void 0 && filters.scope !== "run") return [];
|
|
166
|
+
if (filters.scopeId !== void 0 && filters.scopeId !== null) return [];
|
|
167
|
+
if (filters.actorId !== void 0) return [];
|
|
168
|
+
if (filters.actorKind !== void 0) return [];
|
|
169
|
+
if (filters.attempt !== void 0 && filters.attempt !== 0) return [];
|
|
170
|
+
if (filters.since !== void 0 && run.createdAt < filters.since) return [];
|
|
171
|
+
if (filters.until !== void 0 && run.createdAt > filters.until) return [];
|
|
172
|
+
const rows = [];
|
|
173
|
+
for (const [originalKey, value] of Object.entries(
|
|
174
|
+
run.metadata
|
|
175
|
+
)) {
|
|
176
|
+
const key = `legacy.metadata.${originalKey}`;
|
|
177
|
+
if (filters.key !== void 0 && filters.key !== key) continue;
|
|
178
|
+
if (filters.keyPrefix !== void 0 && !key.startsWith(filters.keyPrefix))
|
|
179
|
+
continue;
|
|
180
|
+
rows.push({
|
|
181
|
+
id: `synthesized:${run.id}:${originalKey}`,
|
|
182
|
+
createdAt: run.createdAt,
|
|
183
|
+
workflowRunId: run.id,
|
|
184
|
+
workflowStageRecordId: null,
|
|
185
|
+
attempt: 0,
|
|
186
|
+
scope: "run",
|
|
187
|
+
scopeId: null,
|
|
188
|
+
actorKind: null,
|
|
189
|
+
actorId: null,
|
|
190
|
+
actorVersion: null,
|
|
191
|
+
key,
|
|
192
|
+
value,
|
|
193
|
+
payload: null,
|
|
194
|
+
idempotencyKey: null
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return rows;
|
|
198
|
+
}
|
|
199
|
+
function filterCouldMatchLegacy(filters) {
|
|
200
|
+
if (filters.scope !== void 0 && filters.scope !== "run") return false;
|
|
201
|
+
if (filters.scopeId !== void 0 && filters.scopeId !== null) return false;
|
|
202
|
+
if (filters.actorId !== void 0) return false;
|
|
203
|
+
if (filters.actorKind !== void 0) return false;
|
|
204
|
+
if (filters.attempt !== void 0 && filters.attempt !== 0) return false;
|
|
205
|
+
if (filters.key !== void 0 && !filters.key.startsWith("legacy.metadata."))
|
|
206
|
+
return false;
|
|
207
|
+
if (filters.keyPrefix !== void 0) {
|
|
208
|
+
if (!"legacy.metadata.".startsWith(filters.keyPrefix) && !filters.keyPrefix.startsWith("legacy.metadata.")) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
81
215
|
// src/kernel/helpers/resolve-execution-group-output.ts
|
|
82
216
|
function resolveExecutionGroupOutput(workflow, groupIndex, workflowContext) {
|
|
83
217
|
const stages = workflow.getStagesInExecutionGroup(groupIndex);
|
|
@@ -181,6 +315,7 @@ async function handleJobExecute(command, deps) {
|
|
|
181
315
|
return record;
|
|
182
316
|
});
|
|
183
317
|
const progressEvents = [];
|
|
318
|
+
const annotationBuffer = createAnnotationBuffer();
|
|
184
319
|
try {
|
|
185
320
|
const rawInput = resolveStageInput(
|
|
186
321
|
workflow,
|
|
@@ -206,6 +341,27 @@ async function handleJobExecute(command, deps) {
|
|
|
206
341
|
}).catch(() => {
|
|
207
342
|
});
|
|
208
343
|
};
|
|
344
|
+
const annotateFn = ((...args) => {
|
|
345
|
+
const stageScopeFields = {
|
|
346
|
+
workflowRunId,
|
|
347
|
+
workflowStageRecordId: stageRecord.id,
|
|
348
|
+
attempt: stageRecord.attempt,
|
|
349
|
+
scope: "stage",
|
|
350
|
+
scopeId: stageId
|
|
351
|
+
};
|
|
352
|
+
for (const { key, value, opts } of normalizeAnnotateArgs(args)) {
|
|
353
|
+
if (value === void 0 || value === null) continue;
|
|
354
|
+
annotationBuffer.push({
|
|
355
|
+
...stageScopeFields,
|
|
356
|
+
actor: opts?.actor,
|
|
357
|
+
key,
|
|
358
|
+
value,
|
|
359
|
+
payload: opts?.payload,
|
|
360
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
361
|
+
emitEvent: opts?.emitEvent
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
209
365
|
const context = {
|
|
210
366
|
workflowRunId,
|
|
211
367
|
stageId,
|
|
@@ -228,6 +384,7 @@ async function handleJobExecute(command, deps) {
|
|
|
228
384
|
},
|
|
229
385
|
onLog: logFn,
|
|
230
386
|
log: logFn,
|
|
387
|
+
annotate: annotateFn,
|
|
231
388
|
storage: createStorageShim(workflowRunId, workflowRun.workflowType, deps),
|
|
232
389
|
workflowContext
|
|
233
390
|
};
|
|
@@ -246,30 +403,54 @@ async function handleJobExecute(command, deps) {
|
|
|
246
403
|
const nextPollAt = new Date(
|
|
247
404
|
pollConfig.nextPollAt?.getTime() ?? deps.clock.now().getTime() + (pollConfig.pollInterval || 6e4)
|
|
248
405
|
);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
406
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
407
|
+
try {
|
|
408
|
+
await deps.persistence.withTransaction(async (tx) => {
|
|
409
|
+
const currentStatus = await tx.getRunStatus(workflowRunId);
|
|
410
|
+
if (currentStatus !== "RUNNING") {
|
|
411
|
+
throw new RunNotRunningError(
|
|
412
|
+
workflowRunId,
|
|
413
|
+
currentStatus ?? "DELETED"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
await tx.updateStage(stageRecord.id, {
|
|
417
|
+
status: "SUSPENDED",
|
|
418
|
+
suspendedState: state,
|
|
419
|
+
nextPollAt,
|
|
420
|
+
pollInterval: pollConfig.pollInterval,
|
|
421
|
+
maxWaitUntil: pollConfig.maxWaitTime ? new Date(deps.clock.now().getTime() + pollConfig.maxWaitTime) : void 0,
|
|
422
|
+
metrics
|
|
423
|
+
});
|
|
424
|
+
if (bufferedAnnotations.length > 0) {
|
|
425
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
426
|
+
}
|
|
427
|
+
const suspendedEvent = {
|
|
428
|
+
type: "stage:suspended",
|
|
429
|
+
timestamp: deps.clock.now(),
|
|
430
|
+
workflowRunId,
|
|
431
|
+
stageId,
|
|
432
|
+
stageName: stageDef.name,
|
|
433
|
+
nextPollAt
|
|
434
|
+
};
|
|
435
|
+
await tx.appendOutboxEvents(
|
|
436
|
+
toOutboxEvents(workflowRunId, causationId, [
|
|
437
|
+
...progressEvents,
|
|
438
|
+
suspendedEvent,
|
|
439
|
+
...buildAnnotationEvents(bufferedAnnotations, deps.clock.now())
|
|
440
|
+
])
|
|
441
|
+
);
|
|
257
442
|
});
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
suspendedEvent
|
|
270
|
-
])
|
|
271
|
-
);
|
|
272
|
-
});
|
|
443
|
+
} catch (txError) {
|
|
444
|
+
if (txError instanceof RunNotRunningError) {
|
|
445
|
+
return {
|
|
446
|
+
outcome: "failed",
|
|
447
|
+
ghost: true,
|
|
448
|
+
error: txError.message,
|
|
449
|
+
_events: []
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
throw txError;
|
|
453
|
+
}
|
|
273
454
|
return { outcome: "suspended", nextPollAt, _events: [] };
|
|
274
455
|
} else {
|
|
275
456
|
const duration = deps.clock.now().getTime() - startTime;
|
|
@@ -287,33 +468,57 @@ async function handleJobExecute(command, deps) {
|
|
|
287
468
|
result.artifacts,
|
|
288
469
|
deps
|
|
289
470
|
) : void 0;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
471
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
472
|
+
try {
|
|
473
|
+
await deps.persistence.withTransaction(async (tx) => {
|
|
474
|
+
const currentStatus = await tx.getRunStatus(workflowRunId);
|
|
475
|
+
if (currentStatus !== "RUNNING") {
|
|
476
|
+
throw new RunNotRunningError(
|
|
477
|
+
workflowRunId,
|
|
478
|
+
currentStatus ?? "DELETED"
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
await tx.updateStage(stageRecord.id, {
|
|
482
|
+
status: "COMPLETED",
|
|
483
|
+
completedAt: deps.clock.now(),
|
|
484
|
+
duration,
|
|
485
|
+
outputData: {
|
|
486
|
+
_artifactKey: outputKey,
|
|
487
|
+
...artifactKeys ? { _artifactKeys: artifactKeys } : {}
|
|
488
|
+
},
|
|
489
|
+
metrics: result.metrics,
|
|
490
|
+
embeddingInfo: result.embeddings
|
|
491
|
+
});
|
|
492
|
+
if (bufferedAnnotations.length > 0) {
|
|
493
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
494
|
+
}
|
|
495
|
+
const completedEvent = {
|
|
496
|
+
type: "stage:completed",
|
|
497
|
+
timestamp: deps.clock.now(),
|
|
498
|
+
workflowRunId,
|
|
499
|
+
stageId,
|
|
500
|
+
stageName: stageDef.name,
|
|
501
|
+
duration
|
|
502
|
+
};
|
|
503
|
+
await tx.appendOutboxEvents(
|
|
504
|
+
toOutboxEvents(workflowRunId, causationId, [
|
|
505
|
+
...progressEvents,
|
|
506
|
+
completedEvent,
|
|
507
|
+
...buildAnnotationEvents(bufferedAnnotations, deps.clock.now())
|
|
508
|
+
])
|
|
509
|
+
);
|
|
301
510
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
completedEvent
|
|
314
|
-
])
|
|
315
|
-
);
|
|
316
|
-
});
|
|
511
|
+
} catch (txError) {
|
|
512
|
+
if (txError instanceof RunNotRunningError) {
|
|
513
|
+
return {
|
|
514
|
+
outcome: "failed",
|
|
515
|
+
ghost: true,
|
|
516
|
+
error: txError.message,
|
|
517
|
+
_events: []
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
throw txError;
|
|
521
|
+
}
|
|
317
522
|
return {
|
|
318
523
|
outcome: "completed",
|
|
319
524
|
output: result.output,
|
|
@@ -323,14 +528,25 @@ async function handleJobExecute(command, deps) {
|
|
|
323
528
|
} catch (error) {
|
|
324
529
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
325
530
|
const duration = deps.clock.now().getTime() - startTime;
|
|
531
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
326
532
|
try {
|
|
327
533
|
await deps.persistence.withTransaction(async (tx) => {
|
|
534
|
+
const currentStatus = await tx.getRunStatus(workflowRunId);
|
|
535
|
+
if (currentStatus !== "RUNNING") {
|
|
536
|
+
throw new RunNotRunningError(
|
|
537
|
+
workflowRunId,
|
|
538
|
+
currentStatus ?? "DELETED"
|
|
539
|
+
);
|
|
540
|
+
}
|
|
328
541
|
await tx.updateStage(stageRecord.id, {
|
|
329
542
|
status: "FAILED",
|
|
330
543
|
completedAt: deps.clock.now(),
|
|
331
544
|
duration,
|
|
332
545
|
errorMessage
|
|
333
546
|
});
|
|
547
|
+
if (bufferedAnnotations.length > 0) {
|
|
548
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
549
|
+
}
|
|
334
550
|
const failedEvent = {
|
|
335
551
|
type: "stage:failed",
|
|
336
552
|
timestamp: deps.clock.now(),
|
|
@@ -342,11 +558,20 @@ async function handleJobExecute(command, deps) {
|
|
|
342
558
|
await tx.appendOutboxEvents(
|
|
343
559
|
toOutboxEvents(workflowRunId, causationId, [
|
|
344
560
|
...progressEvents,
|
|
345
|
-
failedEvent
|
|
561
|
+
failedEvent,
|
|
562
|
+
...buildAnnotationEvents(bufferedAnnotations, deps.clock.now())
|
|
346
563
|
])
|
|
347
564
|
);
|
|
348
565
|
});
|
|
349
|
-
} catch {
|
|
566
|
+
} catch (txError) {
|
|
567
|
+
if (txError instanceof RunNotRunningError) {
|
|
568
|
+
return {
|
|
569
|
+
outcome: "failed",
|
|
570
|
+
ghost: true,
|
|
571
|
+
error: txError.message,
|
|
572
|
+
_events: []
|
|
573
|
+
};
|
|
574
|
+
}
|
|
350
575
|
throw error;
|
|
351
576
|
}
|
|
352
577
|
await deps.persistence.createLog({
|
|
@@ -614,6 +839,31 @@ async function handleRunCreate(command, deps) {
|
|
|
614
839
|
priority,
|
|
615
840
|
metadata: command.metadata
|
|
616
841
|
});
|
|
842
|
+
const annotationEvents = [];
|
|
843
|
+
if (command.annotations && command.annotations.length > 0) {
|
|
844
|
+
const annotationInputs = [];
|
|
845
|
+
for (const entry of command.annotations) {
|
|
846
|
+
for (const [key, value] of Object.entries(entry.attributes)) {
|
|
847
|
+
if (value === void 0 || value === null) continue;
|
|
848
|
+
annotationInputs.push({
|
|
849
|
+
workflowRunId: run.id,
|
|
850
|
+
scope: "run",
|
|
851
|
+
actor: entry.actor,
|
|
852
|
+
key,
|
|
853
|
+
value,
|
|
854
|
+
payload: entry.payload,
|
|
855
|
+
idempotencyKey: entry.idempotencyKey,
|
|
856
|
+
emitEvent: entry.emitEvent
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (annotationInputs.length > 0) {
|
|
861
|
+
await deps.persistence.appendAnnotations(annotationInputs);
|
|
862
|
+
annotationEvents.push(
|
|
863
|
+
...buildAnnotationEvents(annotationInputs, deps.clock.now())
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
617
867
|
return {
|
|
618
868
|
workflowRunId: run.id,
|
|
619
869
|
status: "PENDING",
|
|
@@ -623,7 +873,8 @@ async function handleRunCreate(command, deps) {
|
|
|
623
873
|
timestamp: deps.clock.now(),
|
|
624
874
|
workflowRunId: run.id,
|
|
625
875
|
workflowId: command.workflowId
|
|
626
|
-
}
|
|
876
|
+
},
|
|
877
|
+
...annotationEvents
|
|
627
878
|
]
|
|
628
879
|
};
|
|
629
880
|
}
|
|
@@ -703,6 +954,11 @@ async function handleRunRerunFrom(command, deps) {
|
|
|
703
954
|
(s) => s.executionGroup >= targetGroup
|
|
704
955
|
);
|
|
705
956
|
const deletedStageIds = stagesToDelete.map((s) => s.stageId);
|
|
957
|
+
const priorMaxAttempt = stagesToDelete.reduce(
|
|
958
|
+
(max, s) => s.attempt > max ? s.attempt : max,
|
|
959
|
+
0
|
|
960
|
+
);
|
|
961
|
+
const newAttempt = priorMaxAttempt + 1;
|
|
706
962
|
for (const stage of stagesToDelete) {
|
|
707
963
|
const prefix = `workflow-v2/${run.workflowType}/${workflowRunId}/${stage.stageId}/`;
|
|
708
964
|
const keys = await deps.blobStore.list(prefix).catch(() => []);
|
|
@@ -731,6 +987,7 @@ async function handleRunRerunFrom(command, deps) {
|
|
|
731
987
|
stageName: stage.name,
|
|
732
988
|
stageNumber: workflow.getStageIndex(stage.id) + 1,
|
|
733
989
|
executionGroup: targetGroup,
|
|
990
|
+
attempt: newAttempt,
|
|
734
991
|
status: "PENDING",
|
|
735
992
|
config: run.config?.[stage.id] || {}
|
|
736
993
|
});
|
|
@@ -775,6 +1032,11 @@ async function claimRunTransition(run, deps) {
|
|
|
775
1032
|
}
|
|
776
1033
|
async function enqueueExecutionGroup(run, workflow, groupIndex, deps) {
|
|
777
1034
|
const stages = workflow.getStagesInExecutionGroup(groupIndex);
|
|
1035
|
+
const existingStages = await deps.persistence.getStagesByRun(run.id);
|
|
1036
|
+
const currentAttempt = existingStages.reduce(
|
|
1037
|
+
(max, s) => s.attempt > max ? s.attempt : max,
|
|
1038
|
+
0
|
|
1039
|
+
);
|
|
778
1040
|
const stagesToEnqueue = [];
|
|
779
1041
|
for (const stage of stages) {
|
|
780
1042
|
const record = await deps.persistence.upsertStage({
|
|
@@ -786,6 +1048,7 @@ async function enqueueExecutionGroup(run, workflow, groupIndex, deps) {
|
|
|
786
1048
|
stageName: stage.name,
|
|
787
1049
|
stageNumber: workflow.getStageIndex(stage.id) + 1,
|
|
788
1050
|
executionGroup: groupIndex,
|
|
1051
|
+
attempt: currentAttempt,
|
|
789
1052
|
status: "PENDING",
|
|
790
1053
|
config: run.config?.[stage.id] || {}
|
|
791
1054
|
},
|
|
@@ -932,6 +1195,10 @@ async function markStageCancelled(stageId, deps) {
|
|
|
932
1195
|
async function withClaimedRun(workflowRunId, expectedVersion, deps, fn) {
|
|
933
1196
|
try {
|
|
934
1197
|
const value = await deps.persistence.withTransaction(async (tx) => {
|
|
1198
|
+
const runStatus = await tx.getRunStatus(workflowRunId);
|
|
1199
|
+
if (runStatus !== "RUNNING") {
|
|
1200
|
+
throw new RunNotRunningError(workflowRunId, runStatus ?? "DELETED");
|
|
1201
|
+
}
|
|
935
1202
|
await tx.updateRun(workflowRunId, {
|
|
936
1203
|
expectedVersion
|
|
937
1204
|
});
|
|
@@ -942,6 +1209,9 @@ async function withClaimedRun(workflowRunId, expectedVersion, deps, fn) {
|
|
|
942
1209
|
if (error instanceof StaleVersionError) {
|
|
943
1210
|
return { status: "stale" };
|
|
944
1211
|
}
|
|
1212
|
+
if (error instanceof RunNotRunningError) {
|
|
1213
|
+
return { status: "cancelled", runStatus: error.currentStatus };
|
|
1214
|
+
}
|
|
945
1215
|
throw error;
|
|
946
1216
|
}
|
|
947
1217
|
}
|
|
@@ -1027,6 +1297,28 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1027
1297
|
}).catch(() => {
|
|
1028
1298
|
});
|
|
1029
1299
|
};
|
|
1300
|
+
const annotationBuffer = createAnnotationBuffer();
|
|
1301
|
+
const annotateFn = ((...args) => {
|
|
1302
|
+
const stageScopeFields = {
|
|
1303
|
+
workflowRunId: stageRecord.workflowRunId,
|
|
1304
|
+
workflowStageRecordId: stageRecord.id,
|
|
1305
|
+
attempt: stageRecord.attempt,
|
|
1306
|
+
scope: "stage",
|
|
1307
|
+
scopeId: stageRecord.stageId
|
|
1308
|
+
};
|
|
1309
|
+
for (const { key, value, opts } of normalizeAnnotateArgs(args)) {
|
|
1310
|
+
if (value === void 0 || value === null) continue;
|
|
1311
|
+
annotationBuffer.push({
|
|
1312
|
+
...stageScopeFields,
|
|
1313
|
+
actor: opts?.actor,
|
|
1314
|
+
key,
|
|
1315
|
+
value,
|
|
1316
|
+
payload: opts?.payload,
|
|
1317
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
1318
|
+
emitEvent: opts?.emitEvent
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1030
1322
|
const checkContext = {
|
|
1031
1323
|
workflowRunId: run.id,
|
|
1032
1324
|
stageId: stageRecord.stageId,
|
|
@@ -1034,6 +1326,7 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1034
1326
|
config: stageRecord.config || {},
|
|
1035
1327
|
log: logFn,
|
|
1036
1328
|
onLog: logFn,
|
|
1329
|
+
annotate: annotateFn,
|
|
1037
1330
|
storage
|
|
1038
1331
|
};
|
|
1039
1332
|
try {
|
|
@@ -1042,6 +1335,7 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1042
1335
|
checkContext
|
|
1043
1336
|
);
|
|
1044
1337
|
if (checkResult.error) {
|
|
1338
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
1045
1339
|
const claimResult = await withClaimedRun(
|
|
1046
1340
|
stageRecord.workflowRunId,
|
|
1047
1341
|
run.version,
|
|
@@ -1057,6 +1351,9 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1057
1351
|
status: "FAILED",
|
|
1058
1352
|
completedAt: deps.clock.now()
|
|
1059
1353
|
});
|
|
1354
|
+
if (bufferedAnnotations.length > 0) {
|
|
1355
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
1356
|
+
}
|
|
1060
1357
|
await tx.appendOutboxEvents(
|
|
1061
1358
|
toOutboxEvents2(stageRecord.workflowRunId, [
|
|
1062
1359
|
{
|
|
@@ -1072,7 +1369,8 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1072
1369
|
timestamp: deps.clock.now(),
|
|
1073
1370
|
workflowRunId: stageRecord.workflowRunId,
|
|
1074
1371
|
error: checkResult.error
|
|
1075
|
-
}
|
|
1372
|
+
},
|
|
1373
|
+
...buildAnnotationEvents(bufferedAnnotations, deps.clock.now())
|
|
1076
1374
|
])
|
|
1077
1375
|
);
|
|
1078
1376
|
}
|
|
@@ -1086,6 +1384,10 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1086
1384
|
}
|
|
1087
1385
|
continue;
|
|
1088
1386
|
}
|
|
1387
|
+
if (claimResult.status === "cancelled") {
|
|
1388
|
+
await markStageCancelled(stageRecord.id, deps);
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1089
1391
|
failed++;
|
|
1090
1392
|
} else if (checkResult.ready) {
|
|
1091
1393
|
let outputRef;
|
|
@@ -1105,6 +1407,7 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1105
1407
|
outputRef = { _artifactKey: outputKey };
|
|
1106
1408
|
}
|
|
1107
1409
|
const duration = deps.clock.now().getTime() - (stageRecord.startedAt?.getTime() ?? deps.clock.now().getTime());
|
|
1410
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
1108
1411
|
const claimResult = await withClaimedRun(
|
|
1109
1412
|
stageRecord.workflowRunId,
|
|
1110
1413
|
run.version,
|
|
@@ -1119,6 +1422,9 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1119
1422
|
metrics: checkResult.metrics,
|
|
1120
1423
|
embeddingInfo: checkResult.embeddings
|
|
1121
1424
|
});
|
|
1425
|
+
if (bufferedAnnotations.length > 0) {
|
|
1426
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
1427
|
+
}
|
|
1122
1428
|
await tx.appendOutboxEvents(
|
|
1123
1429
|
toOutboxEvents2(stageRecord.workflowRunId, [
|
|
1124
1430
|
{
|
|
@@ -1128,7 +1434,8 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1128
1434
|
stageId: stageRecord.stageId,
|
|
1129
1435
|
stageName: stageRecord.stageName,
|
|
1130
1436
|
duration
|
|
1131
|
-
}
|
|
1437
|
+
},
|
|
1438
|
+
...buildAnnotationEvents(bufferedAnnotations, deps.clock.now())
|
|
1132
1439
|
])
|
|
1133
1440
|
);
|
|
1134
1441
|
}
|
|
@@ -1142,11 +1449,16 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1142
1449
|
}
|
|
1143
1450
|
continue;
|
|
1144
1451
|
}
|
|
1452
|
+
if (claimResult.status === "cancelled") {
|
|
1453
|
+
await markStageCancelled(stageRecord.id, deps);
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1145
1456
|
resumed++;
|
|
1146
1457
|
resumedWorkflowRunIds.add(stageRecord.workflowRunId);
|
|
1147
1458
|
} else {
|
|
1148
1459
|
const pollInterval = checkResult.nextCheckIn ?? stageRecord.pollInterval ?? 6e4;
|
|
1149
1460
|
const nextPollAt = new Date(deps.clock.now().getTime() + pollInterval);
|
|
1461
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
1150
1462
|
const claimResult = await withClaimedRun(
|
|
1151
1463
|
stageRecord.workflowRunId,
|
|
1152
1464
|
run.version,
|
|
@@ -1155,6 +1467,9 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1155
1467
|
await tx.updateStage(stageRecord.id, {
|
|
1156
1468
|
nextPollAt
|
|
1157
1469
|
});
|
|
1470
|
+
if (bufferedAnnotations.length > 0) {
|
|
1471
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
1472
|
+
}
|
|
1158
1473
|
}
|
|
1159
1474
|
);
|
|
1160
1475
|
if (claimResult.status === "stale") {
|
|
@@ -1166,9 +1481,14 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1166
1481
|
}
|
|
1167
1482
|
continue;
|
|
1168
1483
|
}
|
|
1484
|
+
if (claimResult.status === "cancelled") {
|
|
1485
|
+
await markStageCancelled(stageRecord.id, deps);
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1169
1488
|
}
|
|
1170
1489
|
} catch (error) {
|
|
1171
1490
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1491
|
+
const bufferedAnnotations = annotationBuffer.flush();
|
|
1172
1492
|
const claimResult = await withClaimedRun(
|
|
1173
1493
|
stageRecord.workflowRunId,
|
|
1174
1494
|
run.version,
|
|
@@ -1184,6 +1504,9 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1184
1504
|
status: "FAILED",
|
|
1185
1505
|
completedAt: deps.clock.now()
|
|
1186
1506
|
});
|
|
1507
|
+
if (bufferedAnnotations.length > 0) {
|
|
1508
|
+
await tx.appendAnnotations(bufferedAnnotations);
|
|
1509
|
+
}
|
|
1187
1510
|
await tx.appendOutboxEvents(
|
|
1188
1511
|
toOutboxEvents2(stageRecord.workflowRunId, [
|
|
1189
1512
|
{
|
|
@@ -1199,7 +1522,8 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1199
1522
|
timestamp: deps.clock.now(),
|
|
1200
1523
|
workflowRunId: stageRecord.workflowRunId,
|
|
1201
1524
|
error: errorMessage
|
|
1202
|
-
}
|
|
1525
|
+
},
|
|
1526
|
+
...buildAnnotationEvents(bufferedAnnotations, deps.clock.now())
|
|
1203
1527
|
])
|
|
1204
1528
|
);
|
|
1205
1529
|
}
|
|
@@ -1213,6 +1537,10 @@ async function handleStagePollSuspended(command, deps) {
|
|
|
1213
1537
|
}
|
|
1214
1538
|
continue;
|
|
1215
1539
|
}
|
|
1540
|
+
if (claimResult.status === "cancelled") {
|
|
1541
|
+
await markStageCancelled(stageRecord.id, deps);
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1216
1544
|
failed++;
|
|
1217
1545
|
}
|
|
1218
1546
|
}
|
|
@@ -1407,7 +1735,69 @@ function createKernel(config) {
|
|
|
1407
1735
|
throw error;
|
|
1408
1736
|
}
|
|
1409
1737
|
}
|
|
1410
|
-
|
|
1738
|
+
const annotations = {
|
|
1739
|
+
async attach(workflowRunId, input) {
|
|
1740
|
+
const scope = input.scope ?? "run";
|
|
1741
|
+
const inputs = [];
|
|
1742
|
+
for (const [key, value] of Object.entries(input.attributes)) {
|
|
1743
|
+
if (value === void 0 || value === null) continue;
|
|
1744
|
+
inputs.push({
|
|
1745
|
+
workflowRunId,
|
|
1746
|
+
workflowStageRecordId: input.workflowStageRecordId ?? null,
|
|
1747
|
+
attempt: input.attempt,
|
|
1748
|
+
scope,
|
|
1749
|
+
scopeId: input.scopeId ?? null,
|
|
1750
|
+
actor: input.actor,
|
|
1751
|
+
key,
|
|
1752
|
+
value,
|
|
1753
|
+
payload: input.payload,
|
|
1754
|
+
idempotencyKey: input.idempotencyKey,
|
|
1755
|
+
emitEvent: input.emitEvent
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
if (inputs.length === 0) return;
|
|
1759
|
+
const causationId = input.idempotencyKey ?? crypto.randomUUID();
|
|
1760
|
+
await persistence.withTransaction(async (tx) => {
|
|
1761
|
+
await tx.appendAnnotations(inputs);
|
|
1762
|
+
const events = buildAnnotationEvents(inputs, clock.now());
|
|
1763
|
+
if (events.length > 0) {
|
|
1764
|
+
await tx.appendOutboxEvents(
|
|
1765
|
+
events.map((event) => ({
|
|
1766
|
+
workflowRunId: event.workflowRunId,
|
|
1767
|
+
eventType: event.type,
|
|
1768
|
+
payload: event,
|
|
1769
|
+
causationId,
|
|
1770
|
+
occurredAt: event.timestamp
|
|
1771
|
+
}))
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
},
|
|
1776
|
+
async list(workflowRunId, filters) {
|
|
1777
|
+
const persisted = await persistence.listAnnotations(
|
|
1778
|
+
workflowRunId,
|
|
1779
|
+
filters
|
|
1780
|
+
);
|
|
1781
|
+
if (!filterCouldMatchLegacy(filters ?? {})) return persisted;
|
|
1782
|
+
const migrationCheck = await persistence.listAnnotations(workflowRunId, {
|
|
1783
|
+
keyPrefix: "legacy.metadata.",
|
|
1784
|
+
limit: 1
|
|
1785
|
+
});
|
|
1786
|
+
if (migrationCheck.length > 0) return persisted;
|
|
1787
|
+
const run = await persistence.getRun(workflowRunId);
|
|
1788
|
+
if (!run) return persisted;
|
|
1789
|
+
const synthesized = synthesizeLegacyMetadata(run, filters);
|
|
1790
|
+
if (synthesized.length === 0) return persisted;
|
|
1791
|
+
const merged = [...persisted, ...synthesized].sort((a, b) => {
|
|
1792
|
+
const cmp = a.createdAt.getTime() - b.createdAt.getTime();
|
|
1793
|
+
if (cmp !== 0) return cmp;
|
|
1794
|
+
return a.id.localeCompare(b.id);
|
|
1795
|
+
});
|
|
1796
|
+
const limit = filters?.limit ?? 1e3;
|
|
1797
|
+
return merged.slice(0, limit);
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
return { dispatch, annotations };
|
|
1411
1801
|
}
|
|
1412
1802
|
|
|
1413
1803
|
// src/kernel/plugins.ts
|
|
@@ -1437,5 +1827,5 @@ function createPluginRunner(config) {
|
|
|
1437
1827
|
}
|
|
1438
1828
|
|
|
1439
1829
|
export { IdempotencyInProgressError, createKernel, createPluginRunner, definePlugin, loadWorkflowContext, saveStageOutput };
|
|
1440
|
-
//# sourceMappingURL=chunk-
|
|
1441
|
-
//# sourceMappingURL=chunk-
|
|
1830
|
+
//# sourceMappingURL=chunk-XPWAEYOO.js.map
|
|
1831
|
+
//# sourceMappingURL=chunk-XPWAEYOO.js.map
|