@kb-labs/workflow-daemon 2.116.14 → 2.118.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/dist/index.js +109 -7
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { randomUUID } from 'crypto';
|
|
|
19
19
|
async function createWorkflowWorker(options) {
|
|
20
20
|
const {
|
|
21
21
|
engine,
|
|
22
|
+
workflowService,
|
|
22
23
|
cliApi,
|
|
23
24
|
logger,
|
|
24
25
|
platform,
|
|
@@ -46,6 +47,58 @@ async function createWorkflowWorker(options) {
|
|
|
46
47
|
workspaceRoot,
|
|
47
48
|
defaultTimeout
|
|
48
49
|
});
|
|
50
|
+
async function invokeChildWorkflow(input) {
|
|
51
|
+
if (!workflowService) {
|
|
52
|
+
throw new Error("Workflow invocation is unavailable: WorkflowService was not configured");
|
|
53
|
+
}
|
|
54
|
+
if (!input.workflowRef.startsWith("workspace:")) {
|
|
55
|
+
throw new Error(`Unsupported child workflow reference: workflow:${input.workflowRef}`);
|
|
56
|
+
}
|
|
57
|
+
const workflowId = input.workflowRef.slice("workspace:".length);
|
|
58
|
+
const parentWorkflowId = input.parentRun.metadata?.["workflowId"] ?? input.parentRun.name;
|
|
59
|
+
const ancestors = input.parentRun.metadata?.workflowAncestors ?? [parentWorkflowId];
|
|
60
|
+
if (ancestors.includes(workflowId)) {
|
|
61
|
+
throw new Error(`Child workflow cycle detected: ${[...ancestors, workflowId].join(" -> ")}`);
|
|
62
|
+
}
|
|
63
|
+
const parentDepth = input.parentRun.metadata?.workflowDepth ?? 0;
|
|
64
|
+
if (parentDepth >= engine.maxWorkflowDepth) {
|
|
65
|
+
throw new Error(`Child workflow depth exceeds maxWorkflowDepth=${engine.maxWorkflowDepth}`);
|
|
66
|
+
}
|
|
67
|
+
const workflow = await workflowService.get(workflowId);
|
|
68
|
+
const spec = workflow?.input;
|
|
69
|
+
if (!workflow || !spec?.jobs) {
|
|
70
|
+
throw new Error(`Child workflow not found or is not executable: ${workflowId}`);
|
|
71
|
+
}
|
|
72
|
+
const child = await engine.runFromSpec(spec, {
|
|
73
|
+
trigger: {
|
|
74
|
+
type: "workflow",
|
|
75
|
+
actor: input.parentRun.trigger.actor,
|
|
76
|
+
payload: input.inputs,
|
|
77
|
+
parentRunId: input.parentRun.id,
|
|
78
|
+
parentJobId: input.parentJob.id,
|
|
79
|
+
parentStepId: input.parentStep.id,
|
|
80
|
+
invokedByWorkflowId: parentWorkflowId
|
|
81
|
+
},
|
|
82
|
+
inputs: input.inputs,
|
|
83
|
+
env: input.parentRun.env,
|
|
84
|
+
metadata: {
|
|
85
|
+
workflowId,
|
|
86
|
+
parentRunId: input.parentRun.id,
|
|
87
|
+
parentJobId: input.parentJob.id,
|
|
88
|
+
parentStepId: input.parentStep.id,
|
|
89
|
+
workflowDepth: parentDepth + 1,
|
|
90
|
+
rootRunId: input.parentRun.metadata?.rootRunId ?? input.parentRun.id,
|
|
91
|
+
workflowAncestors: [...ancestors, workflowId],
|
|
92
|
+
// A composed quality workflow must inspect and repair the exact change
|
|
93
|
+
// prepared by its parent, not a fresh worktree at main. The owner keeps
|
|
94
|
+
// lifecycle responsibility; children only borrow this binding.
|
|
95
|
+
executionWorkspace: input.parentRun.metadata?.["executionWorkspace"],
|
|
96
|
+
workspaceOwnerRunId: input.parentRun.metadata?.["workspaceOwnerRunId"] ?? input.parentRun.id
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
await engine.markStepWaitingChild(input.parentRun.id, input.parentJob.id, input.parentStep.id, child.id);
|
|
100
|
+
await engine.reconcileChildInvocation(child.id);
|
|
101
|
+
}
|
|
49
102
|
async function processJob() {
|
|
50
103
|
const entry = await engine.nextJob();
|
|
51
104
|
if (!entry) {
|
|
@@ -118,7 +171,17 @@ async function createWorkflowWorker(options) {
|
|
|
118
171
|
const wsProvider2 = platform.getAdapter("workspace");
|
|
119
172
|
let runWorkspace = wsProvider2 ? workspaceRoot : process.env["KB_PROJECT_ROOT"] ?? workspaceRoot;
|
|
120
173
|
let provisionedWorkspaceId;
|
|
121
|
-
|
|
174
|
+
const workspaceMetadata = run.metadata?.["executionWorkspace"];
|
|
175
|
+
const inheritedWorkspace = typeof run.metadata?.["workspaceOwnerRunId"] === "string" && run.metadata?.["workspaceOwnerRunId"] !== run.id;
|
|
176
|
+
if (workspaceMetadata && typeof workspaceMetadata.rootPath === "string" && typeof workspaceMetadata.workspaceId === "string") {
|
|
177
|
+
runWorkspace = workspaceMetadata.rootPath;
|
|
178
|
+
provisionedWorkspaceId = inheritedWorkspace ? void 0 : workspaceMetadata.workspaceId;
|
|
179
|
+
jobLogger.info("Workspace binding restored", {
|
|
180
|
+
workspaceId: workspaceMetadata.workspaceId,
|
|
181
|
+
ownerRunId: run.metadata?.["workspaceOwnerRunId"] ?? run.id,
|
|
182
|
+
inherited: inheritedWorkspace
|
|
183
|
+
});
|
|
184
|
+
} else if (wsProvider2) {
|
|
122
185
|
const wsId = `wt_${run.id.slice(0, 8)}`;
|
|
123
186
|
try {
|
|
124
187
|
const ws = await wsProvider2.materialize({
|
|
@@ -133,8 +196,16 @@ async function createWorkflowWorker(options) {
|
|
|
133
196
|
}
|
|
134
197
|
});
|
|
135
198
|
if (ws.rootPath) {
|
|
136
|
-
|
|
199
|
+
const rootPath = ws.rootPath;
|
|
200
|
+
runWorkspace = rootPath;
|
|
137
201
|
provisionedWorkspaceId = ws.workspaceId;
|
|
202
|
+
await engine.getStateStore().updateRun(run.id, (draft) => {
|
|
203
|
+
draft.metadata = {
|
|
204
|
+
...draft.metadata ?? {},
|
|
205
|
+
executionWorkspace: { workspaceId: ws.workspaceId, rootPath },
|
|
206
|
+
workspaceOwnerRunId: run.id
|
|
207
|
+
};
|
|
208
|
+
});
|
|
138
209
|
jobLogger.info("Workspace provisioned", {
|
|
139
210
|
workspaceId: ws.workspaceId,
|
|
140
211
|
provider: ws.provider,
|
|
@@ -174,6 +245,9 @@ async function createWorkflowWorker(options) {
|
|
|
174
245
|
if (step.status === "success") {
|
|
175
246
|
continue;
|
|
176
247
|
}
|
|
248
|
+
if (step.status === "waiting_child") {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
177
251
|
const freshRun = await engine.getRun(run.id);
|
|
178
252
|
if (freshRun?.status === "cancelled") {
|
|
179
253
|
jobLogger.info("[worker] Run cancelled \u2014 stopping step execution", {
|
|
@@ -332,6 +406,16 @@ async function createWorkflowWorker(options) {
|
|
|
332
406
|
}
|
|
333
407
|
continue;
|
|
334
408
|
}
|
|
409
|
+
if (step.spec.uses?.startsWith("workflow:")) {
|
|
410
|
+
await invokeChildWorkflow({
|
|
411
|
+
parentRun: run,
|
|
412
|
+
parentJob: job,
|
|
413
|
+
parentStep: step,
|
|
414
|
+
workflowRef: step.spec.uses.slice("workflow:".length),
|
|
415
|
+
inputs: interpolatedWith ?? {}
|
|
416
|
+
});
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
335
419
|
if (step.spec.uses === "builtin:gate") {
|
|
336
420
|
const gateInput = interpolatedWith ?? {};
|
|
337
421
|
const currentIteration = step.metadata?.["iterations"] ?? 0;
|
|
@@ -1957,7 +2041,11 @@ var WorkflowHostService = class {
|
|
|
1957
2041
|
actor: request.trigger?.user,
|
|
1958
2042
|
payload: userInputs
|
|
1959
2043
|
},
|
|
1960
|
-
inputs: resolvedInputs
|
|
2044
|
+
inputs: resolvedInputs,
|
|
2045
|
+
metadata: {
|
|
2046
|
+
workflowId: id,
|
|
2047
|
+
workflowAncestors: [id]
|
|
2048
|
+
}
|
|
1961
2049
|
});
|
|
1962
2050
|
return {
|
|
1963
2051
|
runId: run.id,
|
|
@@ -1990,7 +2078,11 @@ var WorkflowHostService = class {
|
|
|
1990
2078
|
actor: "cli-rerun",
|
|
1991
2079
|
payload: resolvedInputs
|
|
1992
2080
|
},
|
|
1993
|
-
inputs: resolvedInputs
|
|
2081
|
+
inputs: resolvedInputs,
|
|
2082
|
+
metadata: {
|
|
2083
|
+
workflowId,
|
|
2084
|
+
workflowAncestors: [workflowId]
|
|
2085
|
+
}
|
|
1994
2086
|
});
|
|
1995
2087
|
return {
|
|
1996
2088
|
runId: run.id,
|
|
@@ -2168,7 +2260,7 @@ var WorkflowHostService = class {
|
|
|
2168
2260
|
(run) => run.name === workflowId || workflowName && run.name === workflowName
|
|
2169
2261
|
);
|
|
2170
2262
|
if (filters?.status) {
|
|
2171
|
-
runs = runs.filter((run) => run.status === filters.status);
|
|
2263
|
+
runs = runs.filter((run) => mapRunStatusToJobStatus(run.status) === filters.status);
|
|
2172
2264
|
}
|
|
2173
2265
|
runs.sort((a, b) => new Date(b.createdAt ?? 0).getTime() - new Date(a.createdAt ?? 0).getTime());
|
|
2174
2266
|
const start = filters?.offset ?? 0;
|
|
@@ -2179,7 +2271,7 @@ var WorkflowHostService = class {
|
|
|
2179
2271
|
runs: page.map((run) => ({
|
|
2180
2272
|
id: run.id,
|
|
2181
2273
|
workflowId,
|
|
2182
|
-
status: run.status,
|
|
2274
|
+
status: mapRunStatusToJobStatus(run.status),
|
|
2183
2275
|
trigger: {
|
|
2184
2276
|
type: run.trigger?.type ?? "manual",
|
|
2185
2277
|
user: run.trigger?.actor
|
|
@@ -2222,7 +2314,11 @@ var WorkflowHostService = class {
|
|
|
2222
2314
|
if (!resolved) {
|
|
2223
2315
|
return null;
|
|
2224
2316
|
}
|
|
2225
|
-
|
|
2317
|
+
const run = await this.options.engine.getRun(resolved);
|
|
2318
|
+
if (!run) {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
return { ...run, status: mapRunStatusToJobStatus(run.status) };
|
|
2226
2322
|
}
|
|
2227
2323
|
async listRuns(filters) {
|
|
2228
2324
|
const allRuns = await this.options.engine.getAllRuns();
|
|
@@ -3249,6 +3345,11 @@ async function bootstrap(_cwd = process.cwd()) {
|
|
|
3249
3345
|
await engine.cleanupStaleRuns();
|
|
3250
3346
|
bootstrapLogger.info("Resuming interrupted jobs");
|
|
3251
3347
|
await engine.resumeInterruptedJobs();
|
|
3348
|
+
bootstrapLogger.info("Reconciling persisted child workflow invocations");
|
|
3349
|
+
const reconciledChildren = await engine.reconcileChildInvocations();
|
|
3350
|
+
if (reconciledChildren > 0) {
|
|
3351
|
+
bootstrapLogger.info("Reconciled child workflow invocations", { count: reconciledChildren });
|
|
3352
|
+
}
|
|
3252
3353
|
bootstrapLogger.info("Creating JobBroker");
|
|
3253
3354
|
const jobBroker = new JobBroker(
|
|
3254
3355
|
engine,
|
|
@@ -3311,6 +3412,7 @@ async function bootstrap(_cwd = process.cwd()) {
|
|
|
3311
3412
|
bootstrapLogger.info("Creating WorkflowWorker");
|
|
3312
3413
|
const worker = await createWorkflowWorker({
|
|
3313
3414
|
engine,
|
|
3415
|
+
workflowService,
|
|
3314
3416
|
cliApi,
|
|
3315
3417
|
logger: createWorkflowLogger("worker", "workflow.worker"),
|
|
3316
3418
|
analytics: platform.analytics,
|