@codemation/core 0.11.1 → 0.12.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/CHANGELOG.md +6 -0
- package/dist/{CostCatalogContract-DZgcUBE4.d.cts → CostCatalogContract-DD7fQ4FF.d.cts} +2 -2
- package/dist/{EngineRuntimeRegistration.types-Cggm5GVY.d.cts → EngineRuntimeRegistration.types-DTV5_7Jw.d.cts} +3 -3
- package/dist/{EngineRuntimeRegistration.types-BQbS9_gs.d.ts → EngineRuntimeRegistration.types-Dl92Hdoi.d.ts} +2 -2
- package/dist/InMemoryRunDataFactory-qMiYjhCK.d.cts +202 -0
- package/dist/{ItemsInputNormalizer-D-MH8MBs.js → ItemsInputNormalizer-BhuxvZh5.js} +2 -2
- package/dist/{ItemsInputNormalizer-D-MH8MBs.js.map → ItemsInputNormalizer-BhuxvZh5.js.map} +1 -1
- package/dist/{ItemsInputNormalizer-_Mfcd3YU.d.ts → ItemsInputNormalizer-C09a7iFP.d.ts} +2 -2
- package/dist/{ItemsInputNormalizer-C_dpn76M.d.cts → ItemsInputNormalizer-DLaD6rTl.d.cts} +3 -3
- package/dist/{ItemsInputNormalizer-CwdOhSAK.cjs → ItemsInputNormalizer-Div-fb6a.cjs} +2 -2
- package/dist/{ItemsInputNormalizer-CwdOhSAK.cjs.map → ItemsInputNormalizer-Div-fb6a.cjs.map} +1 -1
- package/dist/{RunIntentService-BVur7x9n.d.ts → RunIntentService-BOSGwmqn.d.ts} +18 -4
- package/dist/{RunIntentService-CEF-sFfI.d.cts → RunIntentService-CWMMrAP4.d.cts} +18 -4
- package/dist/{agentMcpTypes-ZiNbNsEi.d.cts → agentMcpTypes-DUmniLOY.d.cts} +183 -4
- package/dist/bootstrap/index.cjs +3 -3
- package/dist/bootstrap/index.d.cts +63 -7
- package/dist/bootstrap/index.d.ts +5 -5
- package/dist/bootstrap/index.js +3 -3
- package/dist/{bootstrap-D_Yyi0wL.js → bootstrap-CKTMMNmL.js} +173 -4
- package/dist/bootstrap-CKTMMNmL.js.map +1 -0
- package/dist/{bootstrap-BxuTFTLB.cjs → bootstrap-D460dCgS.cjs} +175 -4
- package/dist/bootstrap-D460dCgS.cjs.map +1 -0
- package/dist/browser.cjs +3 -2
- package/dist/browser.d.cts +4 -4
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +3 -3
- package/dist/contracts.d.cts +5 -5
- package/dist/contracts.d.ts +2 -2
- package/dist/{di-0Wop7z1y.js → di-DdsgWfVy.js} +31 -2
- package/dist/di-DdsgWfVy.js.map +1 -0
- package/dist/{di-BlEKdoZS.cjs → di-tO6R7VJV.cjs} +36 -1
- package/dist/di-tO6R7VJV.cjs.map +1 -0
- package/dist/{executionPersistenceContracts-BgZMRsTa.d.cts → executionPersistenceContracts-DenJJK2T.d.cts} +2 -2
- package/dist/{index-62Ba9f7D.d.ts → index-BZDhEQ6W.d.ts} +277 -101
- package/dist/{index-zWGtEhrf.d.ts → index-CSKKuK60.d.ts} +441 -5
- package/dist/index.cjs +71 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +395 -97
- package/dist/index.d.ts +5 -5
- package/dist/index.js +56 -159
- package/dist/index.js.map +1 -1
- package/dist/{params-B5SENSzZ.d.cts → params-DqRvku2h.d.cts} +2 -2
- package/dist/{runtime-cxmUkk0l.js → runtime-BPZgnZ9G.js} +611 -16
- package/dist/runtime-BPZgnZ9G.js.map +1 -0
- package/dist/{runtime-DBzq5YBi.cjs → runtime-CyW9c9XM.cjs} +670 -15
- package/dist/runtime-CyW9c9XM.cjs.map +1 -0
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +3 -3
- package/package.json +1 -1
- package/src/authoring/defineHumanApprovalNode.types.ts +379 -0
- package/src/authoring/index.ts +6 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +29 -0
- package/src/contracts/CodemationTelemetryAttributeNames.ts +10 -0
- package/src/contracts/credentialTypes.ts +10 -0
- package/src/contracts/hitlSeamTypes.ts +34 -0
- package/src/contracts/humanTaskStoreTypes.ts +48 -0
- package/src/contracts/inboxChannelTypes.ts +58 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/runTypes.ts +61 -3
- package/src/contracts/runtimeTypes.ts +112 -0
- package/src/credentials/CredentialMaterialProvider.types.ts +61 -0
- package/src/credentials/ManagedCredentialMaterialWriteError.ts +14 -0
- package/src/credentials/ManagedMaterialFetchError.ts +16 -0
- package/src/execution/ActivationEnqueueService.ts +16 -0
- package/src/execution/DefaultExecutionContextFactory.ts +11 -0
- package/src/execution/NodeExecutionSnapshotFactory.ts +7 -1
- package/src/execution/NodeExecutor.ts +60 -1
- package/src/execution/NodeExecutorFactory.ts +12 -2
- package/src/execution/NodeSuspensionHandler.ts +220 -0
- package/src/execution/PersistedRunStateTerminalBuilder.ts +5 -2
- package/src/execution/RunStateSemantics.ts +5 -0
- package/src/execution/RunSuspendedError.ts +21 -0
- package/src/index.ts +40 -0
- package/src/orchestration/Engine.ts +12 -2
- package/src/orchestration/EngineWaiters.ts +1 -1
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +25 -2
- package/src/orchestration/RunContinuationService.ts +226 -2
- package/src/orchestration/TestSuiteOrchestrator.ts +5 -4
- package/src/runtime/RunIntentService.ts +3 -0
- package/src/workflow/dsl/ChainCursorResolver.ts +36 -0
- package/dist/InMemoryRunDataFactory-C7YItvHG.d.cts +0 -123
- package/dist/bootstrap-BxuTFTLB.cjs.map +0 -1
- package/dist/bootstrap-D_Yyi0wL.js.map +0 -1
- package/dist/di-0Wop7z1y.js.map +0 -1
- package/dist/di-BlEKdoZS.cjs.map +0 -1
- package/dist/runtime-DBzq5YBi.cjs.map +0 -1
- package/dist/runtime-cxmUkk0l.js.map +0 -1
|
@@ -1,8 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as resolveItemExprsForExecution, T as ConnectionNodeIdFactory, _ as NodeIterationIdFactory, a as injectable, b as AgentConfigInspector, g as CredentialUnboundError, h as SuspensionRequest, v as AgentConnectionNodeCollector } from "./di-DdsgWfVy.js";
|
|
2
2
|
import { ZodError, z } from "zod";
|
|
3
3
|
import { ReadableStream } from "node:stream/web";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
|
|
6
|
+
//#region src/contracts/humanTaskStoreTypes.ts
|
|
7
|
+
const HumanTaskStoreToken = Symbol.for("codemation.core.HumanTaskStore");
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/contracts/hitlSeamTypes.ts
|
|
11
|
+
const HitlResumeTokenSignerToken = Symbol.for("codemation.core.HitlResumeTokenSigner");
|
|
12
|
+
const HitlTimeoutJobSchedulerToken = Symbol.for("codemation.core.HitlTimeoutJobScheduler");
|
|
13
|
+
/**
|
|
14
|
+
* Optional workspace ID injected into NodeSuspensionHandler in managed mode (T7 security fix).
|
|
15
|
+
* Allows the handler to stamp the workspaceId on each HumanTaskRecord so HitlCallbackHandler
|
|
16
|
+
* can assert workspace identity independently of the HMAC middleware.
|
|
17
|
+
* Not registered in non-managed mode; NodeSuspensionHandler defaults to null.
|
|
18
|
+
*/
|
|
19
|
+
const HitlWorkspaceIdToken = Symbol.for("codemation.core.HitlWorkspaceId");
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/authoring/DefinedNodeRegistry.ts
|
|
23
|
+
var DefinedNodeRegistry = class {
|
|
24
|
+
static definitions = /* @__PURE__ */ new Map();
|
|
25
|
+
static register(definition) {
|
|
26
|
+
this.definitions.set(definition.key, definition);
|
|
27
|
+
}
|
|
28
|
+
static resolve(key) {
|
|
29
|
+
return this.definitions.get(key);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
6
34
|
//#region src/runtime-types/persistedRuntimeTypeModelRegistry.ts
|
|
7
35
|
/** Shared metadata key used to attach persisted runtime-type information to decorated classes. */
|
|
8
36
|
const persistedRuntimeTypeMetadataKey = Symbol.for("codemation.core.persistedRuntimeTypeMetadata");
|
|
@@ -111,6 +139,305 @@ function chatModel(options = {}) {
|
|
|
111
139
|
return InjectableRuntimeDecoratorComposer.compose("chatModel", options, import.meta.url);
|
|
112
140
|
}
|
|
113
141
|
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/authoring/defineNode.types.ts
|
|
144
|
+
const definedNodeCredentialRequirementFactory = {
|
|
145
|
+
create(bindings) {
|
|
146
|
+
if (!bindings) return [];
|
|
147
|
+
return Object.entries(bindings).map(([slotKey, binding]) => {
|
|
148
|
+
if (typeof binding === "string" || this.isCredentialType(binding)) return {
|
|
149
|
+
slotKey,
|
|
150
|
+
label: this.humanize(slotKey),
|
|
151
|
+
acceptedTypes: [this.resolveTypeId(binding)]
|
|
152
|
+
};
|
|
153
|
+
const types = Array.isArray(binding.type) ? binding.type : [binding.type];
|
|
154
|
+
return {
|
|
155
|
+
slotKey,
|
|
156
|
+
label: binding.label ?? this.humanize(slotKey),
|
|
157
|
+
acceptedTypes: types.map((entry) => this.resolveTypeId(entry)),
|
|
158
|
+
optional: binding.optional,
|
|
159
|
+
helpText: binding.helpText,
|
|
160
|
+
helpUrl: binding.helpUrl
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
isCredentialType(value) {
|
|
165
|
+
return Boolean(value) && typeof value === "object" && "definition" in value && typeof value.definition?.typeId === "string";
|
|
166
|
+
},
|
|
167
|
+
resolveTypeId(type) {
|
|
168
|
+
return typeof type === "string" ? type : type.definition.typeId;
|
|
169
|
+
},
|
|
170
|
+
humanize(key) {
|
|
171
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_.]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (character) => character.toUpperCase());
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const definedNodeCredentialAccessorFactory = { create(bindings, ctx) {
|
|
175
|
+
if (!bindings) return {};
|
|
176
|
+
const entries = Object.keys(bindings).map((slotKey) => [slotKey, () => ctx.getCredential(slotKey)]);
|
|
177
|
+
return Object.fromEntries(entries);
|
|
178
|
+
} };
|
|
179
|
+
function defineNode(options) {
|
|
180
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
181
|
+
const DefinedNodeRuntime = class {
|
|
182
|
+
kind = "node";
|
|
183
|
+
outputPorts = ["main"];
|
|
184
|
+
inputSchema = options.inputSchema;
|
|
185
|
+
async execute(args) {
|
|
186
|
+
const ctx = args.ctx;
|
|
187
|
+
const context = {
|
|
188
|
+
config: ctx.config.config,
|
|
189
|
+
credentials: definedNodeCredentialAccessorFactory.create(options.credentials, ctx),
|
|
190
|
+
execution: ctx
|
|
191
|
+
};
|
|
192
|
+
const payload = {
|
|
193
|
+
input: args.input,
|
|
194
|
+
item: args.item,
|
|
195
|
+
itemIndex: args.itemIndex,
|
|
196
|
+
items: args.items,
|
|
197
|
+
ctx
|
|
198
|
+
};
|
|
199
|
+
return await options.execute(payload, context);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
node({ name: options.key })(DefinedNodeRuntime);
|
|
203
|
+
const DefinedRunnableNodeConfig = class {
|
|
204
|
+
kind = "node";
|
|
205
|
+
type = DefinedNodeRuntime;
|
|
206
|
+
icon = options.icon;
|
|
207
|
+
inputSchema = options.inputSchema;
|
|
208
|
+
keepBinaries = options.keepBinaries ?? false;
|
|
209
|
+
constructor(name, config, id) {
|
|
210
|
+
this.name = name;
|
|
211
|
+
this.id = id;
|
|
212
|
+
this.config = config;
|
|
213
|
+
}
|
|
214
|
+
config;
|
|
215
|
+
getCredentialRequirements() {
|
|
216
|
+
return credentialRequirements;
|
|
217
|
+
}
|
|
218
|
+
inspectorSummary() {
|
|
219
|
+
return options.inspectorSummary?.({ config: this.config });
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const definition = {
|
|
223
|
+
kind: "defined-node",
|
|
224
|
+
key: options.key,
|
|
225
|
+
title: options.title,
|
|
226
|
+
description: options.description,
|
|
227
|
+
create(config, name = options.title, id) {
|
|
228
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
229
|
+
},
|
|
230
|
+
register(context) {
|
|
231
|
+
context.registerNode(DefinedNodeRuntime);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
DefinedNodeRegistry.register(definition);
|
|
235
|
+
return definition;
|
|
236
|
+
}
|
|
237
|
+
function defineBatchNode(options) {
|
|
238
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
239
|
+
const DefinedNodeRuntime = class {
|
|
240
|
+
kind = "node";
|
|
241
|
+
outputPorts = ["main"];
|
|
242
|
+
async execute(args) {
|
|
243
|
+
if (args.itemIndex !== args.items.length - 1) return [];
|
|
244
|
+
const ctx = args.ctx;
|
|
245
|
+
const context = {
|
|
246
|
+
config: ctx.config.config,
|
|
247
|
+
credentials: definedNodeCredentialAccessorFactory.create(options.credentials, ctx),
|
|
248
|
+
execution: ctx
|
|
249
|
+
};
|
|
250
|
+
return [...await options.run(args.items.map((item) => item.json), context)];
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
node({ name: options.key })(DefinedNodeRuntime);
|
|
254
|
+
const DefinedRunnableNodeConfig = class {
|
|
255
|
+
kind = "node";
|
|
256
|
+
type = DefinedNodeRuntime;
|
|
257
|
+
icon = options.icon;
|
|
258
|
+
constructor(name, config, id) {
|
|
259
|
+
this.name = name;
|
|
260
|
+
this.id = id;
|
|
261
|
+
this.config = config;
|
|
262
|
+
}
|
|
263
|
+
config;
|
|
264
|
+
getCredentialRequirements() {
|
|
265
|
+
return credentialRequirements;
|
|
266
|
+
}
|
|
267
|
+
inspectorSummary() {
|
|
268
|
+
return options.inspectorSummary?.({ config: this.config });
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const definition = {
|
|
272
|
+
kind: "defined-node",
|
|
273
|
+
key: options.key,
|
|
274
|
+
title: options.title,
|
|
275
|
+
description: options.description,
|
|
276
|
+
create(config, name = options.title, id) {
|
|
277
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
278
|
+
},
|
|
279
|
+
register(context) {
|
|
280
|
+
context.registerNode(DefinedNodeRuntime);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
DefinedNodeRegistry.register(definition);
|
|
284
|
+
return definition;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/authoring/defineHumanApprovalNode.types.ts
|
|
289
|
+
/**
|
|
290
|
+
* Returns `true` when `node` was created by {@link defineHumanApprovalNode}.
|
|
291
|
+
* Uses the `humanApprovalToolBehavior` typed field as the discriminant.
|
|
292
|
+
*/
|
|
293
|
+
function isHumanApprovalNode(node$1) {
|
|
294
|
+
return typeof node$1 === "object" && node$1 !== null && "humanApprovalToolBehavior" in node$1 && typeof node$1.humanApprovalToolBehavior === "object";
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Authoring helper that compiles a HITL approval channel down to a regular
|
|
298
|
+
* {@link defineNode}-backed node with `SuspensionRequest` semantics.
|
|
299
|
+
*
|
|
300
|
+
* **Fast-forward decision semantics:**
|
|
301
|
+
* - On the first `execute` call (no `ctx.resumeContext`): throws a `SuspensionRequest`
|
|
302
|
+
* that calls the author's `deliver`. The engine persists the suspension and continues.
|
|
303
|
+
* - On resume (`ctx.resumeContext` set): calls `onDecision`/`onTimeout` as appropriate,
|
|
304
|
+
* merges a `decision` key into `item.json`, and returns an item with the original
|
|
305
|
+
* `binary` map passed by reference (no copy).
|
|
306
|
+
*
|
|
307
|
+
* **Output shape per item:**
|
|
308
|
+
* ```ts
|
|
309
|
+
* // Input: { json: { invoiceId: 42 }, binary?: {...} }
|
|
310
|
+
* // Output: { json: { invoiceId: 42, decision: { status: "approved", actor, decidedAt } }, binary: <unchanged> }
|
|
311
|
+
* ```
|
|
312
|
+
* If `item.json` already has a `decision` key it is **overwritten**. Namespace as
|
|
313
|
+
* needed if your schema reserves that key for another purpose.
|
|
314
|
+
*
|
|
315
|
+
* **Predicate persistence:**
|
|
316
|
+
* The `approvedPredicate` function is NOT serialized to the suspension record (except
|
|
317
|
+
* as an audit-only string via `toString()`). On resume, the workflow definition is
|
|
318
|
+
* reloaded from code at process start and the predicate closure is rebuilt naturally.
|
|
319
|
+
* If a deploy ships a changed predicate between suspend and resume, the *new* predicate
|
|
320
|
+
* runs — document this in your runbook when the predicate carries business logic that
|
|
321
|
+
* may change across deploys.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* export const slackApprovalNode = defineHumanApprovalNode({
|
|
326
|
+
* key: "my-plugin.slackApproval",
|
|
327
|
+
* title: "Slack Approval",
|
|
328
|
+
* channel: "slack",
|
|
329
|
+
* configSchema: z.object({ channel: z.string(), message: z.string() }),
|
|
330
|
+
* decisionSchema: z.object({ approved: z.boolean(), note: z.string().optional() }),
|
|
331
|
+
*
|
|
332
|
+
* async deliver({ task, config, item }, ctx) {
|
|
333
|
+
* const ts = await postSlackMessage(config.channel, `Approve? <${task.resumeUrl}>`);
|
|
334
|
+
* return { channel: config.channel, ts };
|
|
335
|
+
* },
|
|
336
|
+
*
|
|
337
|
+
* async onDecision({ decision, actor, delivery }, ctx) {
|
|
338
|
+
* await updateSlackMessage(delivery.channel, delivery.ts, decision.approved ? "✅" : "❌");
|
|
339
|
+
* },
|
|
340
|
+
* });
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
function defineHumanApprovalNode(opts) {
|
|
344
|
+
const resolvedPredicate = resolveApprovedPredicate(opts.decisionSchema, opts.approvedPredicate);
|
|
345
|
+
const timeout = opts.defaultTimeout ?? "24h";
|
|
346
|
+
const onTimeout = opts.defaultOnTimeout ?? "halt";
|
|
347
|
+
const inner = defineNode({
|
|
348
|
+
key: opts.key,
|
|
349
|
+
title: opts.title,
|
|
350
|
+
description: opts.description,
|
|
351
|
+
icon: opts.icon,
|
|
352
|
+
configSchema: opts.configSchema,
|
|
353
|
+
inputSchema: opts.inputSchema,
|
|
354
|
+
credentials: opts.credentials,
|
|
355
|
+
inspectorSummary: opts.inspectorSummary ? ({ config }) => opts.inspectorSummary(config) : void 0,
|
|
356
|
+
async execute(args, { config, execution: ctx }) {
|
|
357
|
+
if (!ctx.resumeContext) {
|
|
358
|
+
const subject = buildSubject(opts.title, args.item, ctx);
|
|
359
|
+
throw new SuspensionRequest({
|
|
360
|
+
decisionSchema: opts.decisionSchema,
|
|
361
|
+
timeout,
|
|
362
|
+
onTimeout,
|
|
363
|
+
subject,
|
|
364
|
+
metadata: {
|
|
365
|
+
channel: opts.channel,
|
|
366
|
+
nodeKey: opts.key,
|
|
367
|
+
approvedPredicateSource: opts.approvedPredicate?.toString() ?? null
|
|
368
|
+
},
|
|
369
|
+
deliver: (handle) => opts.deliver({
|
|
370
|
+
task: handle,
|
|
371
|
+
config,
|
|
372
|
+
input: args.input,
|
|
373
|
+
item: args.item
|
|
374
|
+
}, ctx)
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return await handleResume(args.item, ctx.resumeContext, opts.decisionSchema, resolvedPredicate, opts.onDecision, opts.onTimeout, ctx);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return Object.assign(inner, { humanApprovalToolBehavior: { onRejected: "return" } });
|
|
381
|
+
}
|
|
382
|
+
function resolveApprovedPredicate(schema, predicate) {
|
|
383
|
+
if (predicate) return predicate;
|
|
384
|
+
const shape = schema.shape;
|
|
385
|
+
if (shape && typeof shape === "object" && "approved" in shape) return (d) => d.approved === true;
|
|
386
|
+
throw new Error("defineHumanApprovalNode: decisionSchema has no \"approved\" field and no approvedPredicate was provided. Either add { approved: z.boolean() } to the decision schema or supply approvedPredicate explicitly.");
|
|
387
|
+
}
|
|
388
|
+
function buildSubject(title, item, ctx) {
|
|
389
|
+
return {
|
|
390
|
+
title,
|
|
391
|
+
summary: "",
|
|
392
|
+
attributes: {
|
|
393
|
+
workflowId: ctx.workflowId,
|
|
394
|
+
nodeId: ctx.nodeId,
|
|
395
|
+
item: item.json
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function mergeDecision(item, decision) {
|
|
400
|
+
return {
|
|
401
|
+
json: {
|
|
402
|
+
...item.json,
|
|
403
|
+
decision
|
|
404
|
+
},
|
|
405
|
+
binary: item.binary,
|
|
406
|
+
meta: item.meta
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function handleResume(item, resumeContext, decisionSchema, resolvedPredicate, onDecision, onTimeoutCb, ctx) {
|
|
410
|
+
const { decision: dec, delivery, task } = resumeContext;
|
|
411
|
+
if (dec.kind === "timed_out" || dec.kind === "auto_accepted") {
|
|
412
|
+
const policy = dec.kind === "auto_accepted" ? "auto-accept" : "halt";
|
|
413
|
+
await onTimeoutCb?.({
|
|
414
|
+
task,
|
|
415
|
+
delivery,
|
|
416
|
+
item,
|
|
417
|
+
policy
|
|
418
|
+
}, ctx);
|
|
419
|
+
return mergeDecision(item, {
|
|
420
|
+
status: dec.kind === "auto_accepted" ? "auto-accepted" : "timed-out",
|
|
421
|
+
decidedAt: dec.at
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
const parsed = decisionSchema.parse(dec.value);
|
|
425
|
+
await onDecision?.({
|
|
426
|
+
decision: parsed,
|
|
427
|
+
actor: dec.actor,
|
|
428
|
+
task,
|
|
429
|
+
delivery,
|
|
430
|
+
item
|
|
431
|
+
}, ctx);
|
|
432
|
+
return mergeDecision(item, {
|
|
433
|
+
status: resolvedPredicate(parsed) ? "approved" : "rejected",
|
|
434
|
+
actor: dec.actor,
|
|
435
|
+
decidedAt: dec.decidedAt,
|
|
436
|
+
note: parsed.note,
|
|
437
|
+
payload: parsed
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
114
441
|
//#endregion
|
|
115
442
|
//#region src/workflow/dsl/WhenBuilder.ts
|
|
116
443
|
var WhenBuilder = class WhenBuilder {
|
|
@@ -237,6 +564,25 @@ var ChainCursor = class ChainCursor {
|
|
|
237
564
|
}
|
|
238
565
|
return new ChainCursor(this.wf, nextEndpoints);
|
|
239
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Chainable shorthand for `.then(node.create(config, metadata?.name, metadata?.nodeId))`.
|
|
569
|
+
*
|
|
570
|
+
* Signals to readers that this step suspends the run and waits for a human decision.
|
|
571
|
+
* Throws at workflow-build time if `node` was not created via `defineHumanApprovalNode`.
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```ts
|
|
575
|
+
* workflow
|
|
576
|
+
* .trigger(...)
|
|
577
|
+
* .humanApproval(inboxApproval, { title: "Approve?", body: "...", priority: "normal" })
|
|
578
|
+
* .then(nextStep.create(...))
|
|
579
|
+
* .build();
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
humanApproval(node$1, config, metadata) {
|
|
583
|
+
if (!isHumanApprovalNode(node$1)) throw new Error(`.humanApproval() requires a node created via defineHumanApprovalNode (got '${node$1.key ?? String(node$1)}').`);
|
|
584
|
+
return this.then(node$1.create(config, metadata?.name, metadata?.nodeId));
|
|
585
|
+
}
|
|
240
586
|
build() {
|
|
241
587
|
return this.wf.build();
|
|
242
588
|
}
|
|
@@ -703,7 +1049,7 @@ var NodeExecutionSnapshotFactory = class {
|
|
|
703
1049
|
nodeId: args.nodeId,
|
|
704
1050
|
activationId: args.activationId,
|
|
705
1051
|
parent: args.parent,
|
|
706
|
-
status: "completed",
|
|
1052
|
+
status: args.hitlStatus ?? "completed",
|
|
707
1053
|
queuedAt: args.previous?.queuedAt,
|
|
708
1054
|
startedAt,
|
|
709
1055
|
finishedAt: args.finishedAt,
|
|
@@ -830,7 +1176,9 @@ var ActivationEnqueueService = class {
|
|
|
830
1176
|
nodeSnapshotsByNodeId: {
|
|
831
1177
|
...args.previousNodeSnapshotsByNodeId,
|
|
832
1178
|
[args.request.nodeId]: queuedSnapshot
|
|
833
|
-
}
|
|
1179
|
+
},
|
|
1180
|
+
...args.suspension !== void 0 ? { suspension: args.suspension } : {},
|
|
1181
|
+
...args.pendingResume !== void 0 ? { pendingResume: args.pendingResume } : {}
|
|
834
1182
|
});
|
|
835
1183
|
await this.dispatchPreparedActivation(preparedDispatch);
|
|
836
1184
|
return {
|
|
@@ -1297,6 +1645,16 @@ var CodemationTelemetryAttributeNames = class {
|
|
|
1297
1645
|
static mcpServerId = "mcp.server_id";
|
|
1298
1646
|
/** MCP tool name on spans created for callTool invocations. */
|
|
1299
1647
|
static mcpToolName = "mcp.tool_name";
|
|
1648
|
+
/** Terminal node-execution status (e.g. `"hitl-approved"`, `"hitl-rejected"`) on HITL outcome spans. */
|
|
1649
|
+
static nodeExecutionStatus = "codemation.node.execution_status";
|
|
1650
|
+
/** Populated on run-halted spans; discriminates the halt reason (e.g. `"hitl-rejected"`). */
|
|
1651
|
+
static runHaltReason = "codemation.run.halt_reason";
|
|
1652
|
+
/** Human task ID on `hitl.task.*` span events. */
|
|
1653
|
+
static hitlTaskId = "codemation.hitl.task_id";
|
|
1654
|
+
/** HITL channel name (e.g. `"inbox"`, `"control-plane-inbox"`) on `hitl.task.*` span events. */
|
|
1655
|
+
static hitlChannel = "codemation.hitl.channel";
|
|
1656
|
+
/** Decision outcome (e.g. `"approved"`, `"rejected"`) on `hitl.task.decided` span events. */
|
|
1657
|
+
static hitlDecisionStatus = "codemation.hitl.decision_status";
|
|
1300
1658
|
};
|
|
1301
1659
|
|
|
1302
1660
|
//#endregion
|
|
@@ -1395,12 +1753,13 @@ var ExecutionTelemetryCostTrackingDecoratorFactory = class {
|
|
|
1395
1753
|
//#region src/execution/DefaultExecutionContextFactory.ts
|
|
1396
1754
|
var DefaultExecutionContextFactory = class {
|
|
1397
1755
|
telemetryDecoratorFactory = new ExecutionTelemetryCostTrackingDecoratorFactory();
|
|
1398
|
-
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections) {
|
|
1756
|
+
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections, nodeResolver) {
|
|
1399
1757
|
this.binaryStorage = binaryStorage;
|
|
1400
1758
|
this.telemetryFactory = telemetryFactory;
|
|
1401
1759
|
this.costTrackingFactory = costTrackingFactory;
|
|
1402
1760
|
this.currentDate = currentDate;
|
|
1403
1761
|
this.collections = collections;
|
|
1762
|
+
this.nodeResolver = nodeResolver;
|
|
1404
1763
|
}
|
|
1405
1764
|
create(args) {
|
|
1406
1765
|
const baseTelemetry = args.telemetry ?? this.telemetryFactory.create({
|
|
@@ -1427,7 +1786,11 @@ var DefaultExecutionContextFactory = class {
|
|
|
1427
1786
|
binary: new DefaultExecutionBinaryService(this.binaryStorage, args.workflowId, args.runId, this.currentDate),
|
|
1428
1787
|
getCredential: args.getCredential,
|
|
1429
1788
|
testContext: args.testContext,
|
|
1430
|
-
collections: this.collections
|
|
1789
|
+
collections: this.collections,
|
|
1790
|
+
resolve: (token) => {
|
|
1791
|
+
if (!this.nodeResolver) throw new Error("ExecutionContext.resolve() is not available: no NodeResolver was provided to DefaultExecutionContextFactory.");
|
|
1792
|
+
return this.nodeResolver.resolve(token);
|
|
1793
|
+
}
|
|
1431
1794
|
};
|
|
1432
1795
|
}
|
|
1433
1796
|
};
|
|
@@ -1769,6 +2132,27 @@ var NodeActivationRequestComposer = class {
|
|
|
1769
2132
|
}
|
|
1770
2133
|
};
|
|
1771
2134
|
|
|
2135
|
+
//#endregion
|
|
2136
|
+
//#region src/execution/RunSuspendedError.ts
|
|
2137
|
+
/**
|
|
2138
|
+
* Internal sentinel thrown by {@link NodeSuspensionHandler} after persisting a suspension
|
|
2139
|
+
* entry. `NodeExecutionRequestHandlerService` catches this specifically and returns cleanly —
|
|
2140
|
+
* no continuation call, preventing `resumeFromNodeResult` / `resumeFromNodeError` from
|
|
2141
|
+
* overwriting the `"suspended"` run status.
|
|
2142
|
+
*
|
|
2143
|
+
* The `Error` suffix satisfies the ESLint `no-manual-di-new` allowlist. This is NOT a
|
|
2144
|
+
* user-facing error — it is an engine-internal control-flow primitive and should NOT be
|
|
2145
|
+
* exported from the public barrel.
|
|
2146
|
+
*/
|
|
2147
|
+
var RunSuspendedError = class extends Error {
|
|
2148
|
+
constructor(runId, taskId) {
|
|
2149
|
+
super(`RunSuspendedError: run ${runId} suspended on task ${taskId}`);
|
|
2150
|
+
this.runId = runId;
|
|
2151
|
+
this.taskId = taskId;
|
|
2152
|
+
this.name = "RunSuspendedError";
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
|
|
1772
2156
|
//#endregion
|
|
1773
2157
|
//#region src/execution/NodeExecutor.ts
|
|
1774
2158
|
var NodeExecutor = class {
|
|
@@ -1776,9 +2160,11 @@ var NodeExecutor = class {
|
|
|
1776
2160
|
outputNormalizer = new NodeOutputNormalizer();
|
|
1777
2161
|
itemExprResolver;
|
|
1778
2162
|
outputBehaviorResolver;
|
|
1779
|
-
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver) {
|
|
2163
|
+
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
1780
2164
|
this.nodeInstanceFactory = nodeInstanceFactory;
|
|
1781
2165
|
this.retryRunner = retryRunner;
|
|
2166
|
+
this.suspensionHandler = suspensionHandler;
|
|
2167
|
+
this.loadRunState = loadRunState;
|
|
1782
2168
|
this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
|
|
1783
2169
|
this.outputBehaviorResolver = outputBehaviorResolver ?? new RunnableOutputBehaviorResolver();
|
|
1784
2170
|
}
|
|
@@ -1878,6 +2264,7 @@ var NodeExecutor = class {
|
|
|
1878
2264
|
});
|
|
1879
2265
|
}
|
|
1880
2266
|
const byPort = {};
|
|
2267
|
+
let hasSuspension = false;
|
|
1881
2268
|
for (let i = 0; i < inputBatch.length; i++) {
|
|
1882
2269
|
const item = inputBatch[i];
|
|
1883
2270
|
this.assertItemJsonNotTopLevelArray(request.nodeId, item);
|
|
@@ -1896,7 +2283,28 @@ var NodeExecutor = class {
|
|
|
1896
2283
|
items: inputBatch,
|
|
1897
2284
|
ctx: iterationCtx
|
|
1898
2285
|
};
|
|
1899
|
-
|
|
2286
|
+
let raw;
|
|
2287
|
+
try {
|
|
2288
|
+
raw = await Promise.resolve(node$1.execute(args));
|
|
2289
|
+
} catch (e) {
|
|
2290
|
+
if (e instanceof SuspensionRequest || e instanceof Error && e.name === "SuspensionRequest" && typeof e.request === "object") {
|
|
2291
|
+
if (!this.suspensionHandler || !this.loadRunState) throw new Error(`Node ${request.nodeId} threw SuspensionRequest but this NodeExecutor has no suspensionHandler configured.`, { cause: e });
|
|
2292
|
+
const state = await this.loadRunState(request.runId);
|
|
2293
|
+
if (!state) throw new Error(`NodeExecutor: run state not found for runId ${request.runId} during suspension`, { cause: e });
|
|
2294
|
+
await this.suspensionHandler.handle({
|
|
2295
|
+
runId: request.runId,
|
|
2296
|
+
nodeId: request.nodeId,
|
|
2297
|
+
activationId: request.activationId,
|
|
2298
|
+
itemIndex: i,
|
|
2299
|
+
suspensionRequest: e,
|
|
2300
|
+
state,
|
|
2301
|
+
telemetry: iterationCtx.telemetry
|
|
2302
|
+
});
|
|
2303
|
+
hasSuspension = true;
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2306
|
+
throw e;
|
|
2307
|
+
}
|
|
1900
2308
|
const normalized = this.outputNormalizer.normalizeExecuteResult({
|
|
1901
2309
|
baseItem: item,
|
|
1902
2310
|
raw,
|
|
@@ -1909,6 +2317,7 @@ var NodeExecutor = class {
|
|
|
1909
2317
|
byPort[port] = list;
|
|
1910
2318
|
}
|
|
1911
2319
|
}
|
|
2320
|
+
if (hasSuspension) throw new RunSuspendedError(request.runId, "unknown");
|
|
1912
2321
|
return byPort;
|
|
1913
2322
|
}
|
|
1914
2323
|
/** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
|
|
@@ -1935,8 +2344,8 @@ var NodeExecutor = class {
|
|
|
1935
2344
|
//#endregion
|
|
1936
2345
|
//#region src/execution/NodeExecutorFactory.ts
|
|
1937
2346
|
var NodeExecutorFactory = class {
|
|
1938
|
-
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver) {
|
|
1939
|
-
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver);
|
|
2347
|
+
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
2348
|
+
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver, suspensionHandler, loadRunState);
|
|
1940
2349
|
}
|
|
1941
2350
|
};
|
|
1942
2351
|
|
|
@@ -2529,6 +2938,7 @@ var PersistedRunStateTerminalBuilder = class {
|
|
|
2529
2938
|
...args.state,
|
|
2530
2939
|
engineCounters: args.engineCounters,
|
|
2531
2940
|
status: args.status,
|
|
2941
|
+
reason: args.reason,
|
|
2532
2942
|
pending: void 0,
|
|
2533
2943
|
queue: args.queue,
|
|
2534
2944
|
outputsByNode: args.outputsByNode,
|
|
@@ -2811,6 +3221,7 @@ var RunContinuationService = class {
|
|
|
2811
3221
|
});
|
|
2812
3222
|
data.setOutputs(args.nodeId, args.outputs);
|
|
2813
3223
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3224
|
+
const hitlResolution = this.resolveHitlStatus(args.outputs);
|
|
2814
3225
|
const completedSnapshot = this.semantics.createFinishedSnapshot({
|
|
2815
3226
|
workflow: wf,
|
|
2816
3227
|
previous: state.nodeSnapshotsByNodeId?.[args.nodeId],
|
|
@@ -2821,8 +3232,41 @@ var RunContinuationService = class {
|
|
|
2821
3232
|
parent: state.parent,
|
|
2822
3233
|
finishedAt: completedAt,
|
|
2823
3234
|
inputsByPort: pendingExecution.inputsByPort,
|
|
2824
|
-
outputs: args.outputs
|
|
3235
|
+
outputs: args.outputs,
|
|
3236
|
+
hitlStatus: hitlResolution?.nodeStatus
|
|
2825
3237
|
});
|
|
3238
|
+
if (hitlResolution?.halt) {
|
|
3239
|
+
const haltedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
3240
|
+
state,
|
|
3241
|
+
engineCounters: state.engineCounters ?? { completedNodeActivations: 0 },
|
|
3242
|
+
status: "halted",
|
|
3243
|
+
reason: hitlResolution.reason,
|
|
3244
|
+
queue: [],
|
|
3245
|
+
outputsByNode: data.dump(),
|
|
3246
|
+
nodeSnapshotsByNodeId: {
|
|
3247
|
+
...state.nodeSnapshotsByNodeId ?? {},
|
|
3248
|
+
[args.nodeId]: completedSnapshot
|
|
3249
|
+
},
|
|
3250
|
+
finishedAtIso: completedAt
|
|
3251
|
+
});
|
|
3252
|
+
await this.workflowExecutionRepository.save(haltedState);
|
|
3253
|
+
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
3254
|
+
await this.terminalPersistence.maybeDeleteAfterTerminalState({
|
|
3255
|
+
workflow: wf,
|
|
3256
|
+
state: haltedState,
|
|
3257
|
+
finalStatus: "failed",
|
|
3258
|
+
finishedAt: completedAt
|
|
3259
|
+
});
|
|
3260
|
+
const result = {
|
|
3261
|
+
runId: state.runId,
|
|
3262
|
+
workflowId: state.workflowId,
|
|
3263
|
+
startedAt: state.startedAt,
|
|
3264
|
+
status: "halted",
|
|
3265
|
+
reason: hitlResolution.reason
|
|
3266
|
+
};
|
|
3267
|
+
this.waiters.resolveRunCompletion(result);
|
|
3268
|
+
return result;
|
|
3269
|
+
}
|
|
2826
3270
|
const completedActivations = (state.engineCounters?.completedNodeActivations ?? 0) + 1;
|
|
2827
3271
|
const engineCounters = { completedNodeActivations: completedActivations };
|
|
2828
3272
|
const maxNodeActivations = state.executionOptions?.maxNodeActivations ?? this.executionLimitsPolicy.createRootExecutionOptions().maxNodeActivations;
|
|
@@ -3123,13 +3567,114 @@ var RunContinuationService = class {
|
|
|
3123
3567
|
status: "failed",
|
|
3124
3568
|
error: { message: "Run failed" }
|
|
3125
3569
|
};
|
|
3570
|
+
if (existing?.status === "halted") return {
|
|
3571
|
+
runId: existing.runId,
|
|
3572
|
+
workflowId: existing.workflowId,
|
|
3573
|
+
startedAt: existing.startedAt,
|
|
3574
|
+
status: "halted",
|
|
3575
|
+
reason: existing.reason ?? "hitl-rejected"
|
|
3576
|
+
};
|
|
3126
3577
|
const result = await this.waiters.waitForCompletion(runId);
|
|
3127
|
-
if (result.status !== "completed" && result.status !== "failed") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3578
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3128
3579
|
return result;
|
|
3129
3580
|
}
|
|
3130
3581
|
async waitForWebhookResponse(runId) {
|
|
3131
3582
|
return await this.waiters.waitForWebhookResponse(runId);
|
|
3132
3583
|
}
|
|
3584
|
+
/**
|
|
3585
|
+
* Re-activate a previously suspended run item with a human decision.
|
|
3586
|
+
*
|
|
3587
|
+
* Called by the HITL resume endpoint. This method:
|
|
3588
|
+
* 1. Loads `PersistedRunState` and locates the suspension entry by `taskId`.
|
|
3589
|
+
* 2. Removes the entry from the `suspension` array; if empty, run stays `"suspended"` until
|
|
3590
|
+
* enqueue flips it to `"pending"`.
|
|
3591
|
+
* 3. Writes `pendingResume` onto the state so `NodeExecutionRequestHandlerService` can
|
|
3592
|
+
* splice `resumeContext` into the node's execution context.
|
|
3593
|
+
* 4. Reconstructs the original input from `outputsByNode` of the upstream node and
|
|
3594
|
+
* enqueues a new activation via `activationEnqueueService`.
|
|
3595
|
+
*
|
|
3596
|
+
* @throws if the run is not found, not suspended, or the `taskId` is unknown.
|
|
3597
|
+
*/
|
|
3598
|
+
async resumeRun(args) {
|
|
3599
|
+
const state = await this.workflowExecutionRepository.load(args.runId);
|
|
3600
|
+
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
3601
|
+
if (state.status !== "suspended") throw new Error(`Run ${args.runId} is not suspended (status: ${state.status})`);
|
|
3602
|
+
const suspensionEntry = (state.suspension ?? []).find((s) => s.taskId === args.taskId);
|
|
3603
|
+
if (!suspensionEntry) throw new Error(`No suspension entry with taskId "${args.taskId}" found on run ${args.runId}`);
|
|
3604
|
+
const wf = this.resolvePersistedWorkflow(state);
|
|
3605
|
+
if (!wf) throw new Error(`Unknown workflowId: ${state.workflowId}`);
|
|
3606
|
+
const { topology, planner } = this.planningFactory.create(wf);
|
|
3607
|
+
const def = topology.defsById.get(suspensionEntry.nodeId);
|
|
3608
|
+
if (!def || def.kind !== "node") throw new Error(`Node ${suspensionEntry.nodeId} is not a runnable node`);
|
|
3609
|
+
const data = this.runDataFactory.create(state.outputsByNode);
|
|
3610
|
+
const limits = this.resolveEngineLimitsFromState(state);
|
|
3611
|
+
const base = this.runExecutionContextFactory.create({
|
|
3612
|
+
runId: state.runId,
|
|
3613
|
+
workflowId: state.workflowId,
|
|
3614
|
+
nodeId: suspensionEntry.nodeId,
|
|
3615
|
+
parent: state.parent,
|
|
3616
|
+
policySnapshot: state.policySnapshot,
|
|
3617
|
+
subworkflowDepth: state.executionOptions?.subworkflowDepth ?? 0,
|
|
3618
|
+
engineMaxNodeActivations: limits.engineMaxNodeActivations,
|
|
3619
|
+
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
3620
|
+
data,
|
|
3621
|
+
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
3622
|
+
testContext: state.executionOptions?.testContext
|
|
3623
|
+
});
|
|
3624
|
+
const parentEdges = wf.edges.filter((e) => e.to.nodeId === suspensionEntry.nodeId);
|
|
3625
|
+
const parentNodeId = parentEdges[0]?.from.nodeId;
|
|
3626
|
+
const parentOutputPort = parentEdges[0]?.from.output ?? "main";
|
|
3627
|
+
const allParentItems = parentNodeId ? data.getOutputItems(parentNodeId, parentOutputPort) ?? [] : [];
|
|
3628
|
+
const resumeInput = allParentItems.length > suspensionEntry.itemIndex ? [allParentItems[suspensionEntry.itemIndex]] : allParentItems;
|
|
3629
|
+
const newActivationId = this.activationIdFactory.makeActivationId();
|
|
3630
|
+
const pendingResume = {
|
|
3631
|
+
activationId: newActivationId,
|
|
3632
|
+
nodeId: suspensionEntry.nodeId,
|
|
3633
|
+
resumeContext: args.resumeContext
|
|
3634
|
+
};
|
|
3635
|
+
const remainingSuspensions = (state.suspension ?? []).filter((s) => s.taskId !== args.taskId);
|
|
3636
|
+
const baseWithResume = {
|
|
3637
|
+
...base,
|
|
3638
|
+
resumeContext: args.resumeContext
|
|
3639
|
+
};
|
|
3640
|
+
const batchId = `resume_${newActivationId}`;
|
|
3641
|
+
const request = this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
|
|
3642
|
+
activationId: newActivationId,
|
|
3643
|
+
runId: state.runId,
|
|
3644
|
+
workflowId: state.workflowId,
|
|
3645
|
+
parent: state.parent,
|
|
3646
|
+
executionOptions: state.executionOptions,
|
|
3647
|
+
base: baseWithResume,
|
|
3648
|
+
data,
|
|
3649
|
+
definition: {
|
|
3650
|
+
id: suspensionEntry.nodeId,
|
|
3651
|
+
config: def.config
|
|
3652
|
+
},
|
|
3653
|
+
batchId,
|
|
3654
|
+
input: resumeInput
|
|
3655
|
+
});
|
|
3656
|
+
const { result, queuedSnapshot } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
3657
|
+
runId: state.runId,
|
|
3658
|
+
workflowId: state.workflowId,
|
|
3659
|
+
startedAt: state.startedAt,
|
|
3660
|
+
parent: state.parent,
|
|
3661
|
+
executionOptions: state.executionOptions,
|
|
3662
|
+
control: state.control,
|
|
3663
|
+
workflowSnapshot: state.workflowSnapshot,
|
|
3664
|
+
mutableState: state.mutableState,
|
|
3665
|
+
policySnapshot: state.policySnapshot,
|
|
3666
|
+
pendingQueue: [],
|
|
3667
|
+
request,
|
|
3668
|
+
previousNodeSnapshotsByNodeId: state.nodeSnapshotsByNodeId ?? {},
|
|
3669
|
+
planner,
|
|
3670
|
+
engineCounters: state.engineCounters,
|
|
3671
|
+
connectionInvocations: state.connectionInvocations ?? [],
|
|
3672
|
+
suspension: remainingSuspensions.length > 0 ? remainingSuspensions : void 0,
|
|
3673
|
+
pendingResume
|
|
3674
|
+
});
|
|
3675
|
+
await this.nodeEventPublisher.publish("nodeQueued", queuedSnapshot);
|
|
3676
|
+
return result;
|
|
3677
|
+
}
|
|
3133
3678
|
async resumeFromWebhookControl(args) {
|
|
3134
3679
|
const data = this.runDataFactory.create(args.state.outputsByNode);
|
|
3135
3680
|
const { topology, planner } = this.planningFactory.create(args.workflow);
|
|
@@ -3516,6 +4061,34 @@ var RunContinuationService = class {
|
|
|
3516
4061
|
this.waiters.resolveRunCompletion(result);
|
|
3517
4062
|
return result;
|
|
3518
4063
|
}
|
|
4064
|
+
/**
|
|
4065
|
+
* Inspects node outputs for a `decision.status` written by `defineHumanApprovalNode`.
|
|
4066
|
+
* Returns the first-class HITL node status and halt classification, or `undefined`
|
|
4067
|
+
* when the node is not a HITL approval node.
|
|
4068
|
+
*/
|
|
4069
|
+
resolveHitlStatus(outputs) {
|
|
4070
|
+
const firstItem = outputs?.main?.[0];
|
|
4071
|
+
const decisionStatus = firstItem && typeof firstItem === "object" && "json" in firstItem && firstItem.json && typeof firstItem.json === "object" && "decision" in firstItem.json && firstItem.json.decision && typeof firstItem.json.decision === "object" && "status" in firstItem.json.decision ? firstItem.json.decision.status : void 0;
|
|
4072
|
+
if (!decisionStatus) return void 0;
|
|
4073
|
+
if (decisionStatus === "approved") return {
|
|
4074
|
+
nodeStatus: "hitl-approved",
|
|
4075
|
+
halt: false
|
|
4076
|
+
};
|
|
4077
|
+
if (decisionStatus === "auto-accepted") return {
|
|
4078
|
+
nodeStatus: "hitl-auto-accepted",
|
|
4079
|
+
halt: false
|
|
4080
|
+
};
|
|
4081
|
+
if (decisionStatus === "rejected") return {
|
|
4082
|
+
nodeStatus: "hitl-rejected",
|
|
4083
|
+
halt: true,
|
|
4084
|
+
reason: "hitl-rejected"
|
|
4085
|
+
};
|
|
4086
|
+
if (decisionStatus === "timed-out") return {
|
|
4087
|
+
nodeStatus: "hitl-timeout",
|
|
4088
|
+
halt: true,
|
|
4089
|
+
reason: "hitl-timeout"
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
3519
4092
|
formatNodeLabel(args) {
|
|
3520
4093
|
const tokenName = typeof args.definition?.type === "function" ? args.definition.type.name : "Node";
|
|
3521
4094
|
return args.definition?.name ? `"${args.definition.name}" (${tokenName}:${args.nodeId})` : `${tokenName}:${args.nodeId}`;
|
|
@@ -4868,13 +5441,19 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4868
5441
|
const portKeys = Object.keys(inputsByPort);
|
|
4869
5442
|
const kind = portKeys.length === 1 && portKeys[0] === "in" ? "single" : "multi";
|
|
4870
5443
|
const batchId = pendingExecution.batchId ?? "batch_1";
|
|
5444
|
+
const pendingResume = state.pendingResume;
|
|
5445
|
+
const resumeContext = pendingResume?.activationId === request.activationId && pendingResume?.nodeId === request.nodeId ? pendingResume.resumeContext : void 0;
|
|
5446
|
+
const baseWithResume = resumeContext != null ? {
|
|
5447
|
+
...base,
|
|
5448
|
+
resumeContext
|
|
5449
|
+
} : base;
|
|
4871
5450
|
const activationRequest = kind === "multi" ? this.nodeActivationRequestComposer.createMultiFromDefinitionWithActivation({
|
|
4872
5451
|
activationId: request.activationId,
|
|
4873
5452
|
runId: request.runId,
|
|
4874
5453
|
workflowId: request.workflowId,
|
|
4875
5454
|
parent: resolvedParent,
|
|
4876
5455
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4877
|
-
base,
|
|
5456
|
+
base: baseWithResume,
|
|
4878
5457
|
data,
|
|
4879
5458
|
definition: {
|
|
4880
5459
|
id: definition.id,
|
|
@@ -4888,7 +5467,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4888
5467
|
workflowId: request.workflowId,
|
|
4889
5468
|
parent: resolvedParent,
|
|
4890
5469
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4891
|
-
base,
|
|
5470
|
+
base: baseWithResume,
|
|
4892
5471
|
data,
|
|
4893
5472
|
definition: {
|
|
4894
5473
|
id: definition.id,
|
|
@@ -4897,6 +5476,13 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4897
5476
|
batchId,
|
|
4898
5477
|
input: inputsByPort.in ?? request.input ?? []
|
|
4899
5478
|
});
|
|
5479
|
+
if (resumeContext != null) {
|
|
5480
|
+
const clearedState = await this.workflowExecutionRepository.load(request.runId);
|
|
5481
|
+
if (clearedState?.pendingResume?.activationId === request.activationId) await this.workflowExecutionRepository.save({
|
|
5482
|
+
...clearedState,
|
|
5483
|
+
pendingResume: void 0
|
|
5484
|
+
});
|
|
5485
|
+
}
|
|
4900
5486
|
await this.continuation.markNodeRunning({
|
|
4901
5487
|
runId: activationRequest.runId,
|
|
4902
5488
|
activationId: activationRequest.activationId,
|
|
@@ -4907,6 +5493,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4907
5493
|
try {
|
|
4908
5494
|
outputs = await this.nodeExecutor.execute(activationRequest);
|
|
4909
5495
|
} catch (error) {
|
|
5496
|
+
if (error instanceof RunSuspendedError) return;
|
|
4910
5497
|
await this.resumeAfterExecutionError(activationRequest, this.asError(error));
|
|
4911
5498
|
return;
|
|
4912
5499
|
}
|
|
@@ -5467,7 +6054,7 @@ var EngineWaiters = class {
|
|
|
5467
6054
|
});
|
|
5468
6055
|
}
|
|
5469
6056
|
resolveRunCompletion(result) {
|
|
5470
|
-
if (result.status !== "completed" && result.status !== "failed") return;
|
|
6057
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") return;
|
|
5471
6058
|
const list = this.completionWaiters.get(result.runId);
|
|
5472
6059
|
if (!list || list.length === 0) return;
|
|
5473
6060
|
this.completionWaiters.delete(result.runId);
|
|
@@ -5561,6 +6148,13 @@ var Engine = class {
|
|
|
5561
6148
|
async waitForWebhookResponse(runId) {
|
|
5562
6149
|
return await this.deps.runContinuationService.waitForWebhookResponse(runId);
|
|
5563
6150
|
}
|
|
6151
|
+
/**
|
|
6152
|
+
* Re-activate a suspended run item with a human decision (HITL).
|
|
6153
|
+
* The HTTP resume endpoint calls this; this method exposes the engine primitive.
|
|
6154
|
+
*/
|
|
6155
|
+
async resumeRun(args) {
|
|
6156
|
+
return await this.deps.runContinuationService.resumeRun(args);
|
|
6157
|
+
}
|
|
5564
6158
|
async handleNodeExecutionRequest(request) {
|
|
5565
6159
|
await this.deps.nodeExecutionRequestHandler.handleNodeExecutionRequest(request);
|
|
5566
6160
|
}
|
|
@@ -5768,6 +6362,7 @@ var RunIntentService = class {
|
|
|
5768
6362
|
};
|
|
5769
6363
|
return await Promise.race([this.engine.waitForWebhookResponse(scheduled.runId), this.engine.waitForCompletion(scheduled.runId).then((completed) => {
|
|
5770
6364
|
if (completed.status === "failed") throw new Error(completed.error.message);
|
|
6365
|
+
if (completed.status === "halted") throw new Error(`Run halted: ${completed.reason}`);
|
|
5771
6366
|
return {
|
|
5772
6367
|
runId: completed.runId,
|
|
5773
6368
|
workflowId: completed.workflowId,
|
|
@@ -5892,5 +6487,5 @@ var WorkflowRepositoryWebhookTriggerMatcherFactory = class {
|
|
|
5892
6487
|
};
|
|
5893
6488
|
|
|
5894
6489
|
//#endregion
|
|
5895
|
-
export {
|
|
5896
|
-
//# sourceMappingURL=runtime-
|
|
6490
|
+
export { CostTrackingTelemetryAttributeNames as $, PersistedWorkflowTokenRegistry as A, node as At, CatalogBackedCostTrackingTelemetryFactory as B, HumanTaskStoreToken as Bt, DefaultDrivingScheduler as C, WhenBuilder as Ct, NodeInstanceFactoryFactory as D, defineNode as Dt, StaticCostCatalog as E, defineBatchNode as Et, InProcessRetryRunnerFactory as F, PersistedRuntimeTypeNameResolver as Ft, AllWorkflowsActiveWorkflowActivationPolicy as G, CodemationTelemetryMetricNames as H, RunnableOutputBehaviorResolver as I, DefinedNodeRegistry as It, NoOpNodeExecutionTelemetry as J, NoOpExecutionTelemetryFactory as K, NodeOutputNormalizer as L, HitlResumeTokenSignerToken as Lt, NodeExecutorFactory as M, InjectableRuntimeDecoratorComposer as Mt, NodeExecutor as N, PersistedRuntimeTypeMetadataStore as Nt, NodeInstanceFactory as O, chatModel as Ot, RunSuspendedError as P, StackTraceCallSitePathResolver as Pt, NoOpCostTrackingTelemetry as Q, ItemExprResolver as R, HitlTimeoutJobSchedulerToken as Rt, HintOnlyOffloadPolicy as S, ChainCursor as St, RunPolicySnapshotFactory as T, isHumanApprovalNode as Tt, GenAiTelemetryAttributeNames as U, DefaultExecutionContextFactory as V, CodemationTelemetryAttributeNames as W, NoOpTelemetryArtifactReference as X, NoOpTelemetrySpanScope as Y, NoOpCostTrackingTelemetryFactory as Z, RunTerminalPersistenceCoordinator as _, WorkflowExecutableNodeClassifier as _t, InMemoryLiveWorkflowRepository as a, isPortsEmission as at, LocalOnlyScheduler as b, WorkflowDefinitionError as bt, EngineFactory as c, CredentialResolverFactory as ct, PollingTriggerRuntime as d, DefaultExecutionBinaryService as dt, CostTrackingTelemetryMetricNames as et, PollingTriggerDedupWindow as f, UnavailableBinaryStorage as ft, WorkflowPolicyErrorServices as g, WorkflowExecutableNodeClassifierFactory as gt, WorkflowStoragePolicyEvaluator as h, DefaultWorkflowGraphFactory as ht, RunIntentService as i, emitPorts as it, MissingRuntimeTriggerToken as j, tool as jt, WorkflowSnapshotCodec as k, getPersistedRuntimeTypeMetadata as kt, Engine as l, getOriginIndexFromItem as lt, InMemoryBinaryStorage as m, ConnectionInvocationEventPublisher as mt, WorkflowRepositoryWebhookTriggerMatcher as n, RetryPolicy as nt, EngineWorkflowRunnerServiceFactory as o, isUnbrandedPortsEmissionShape as ot, InMemoryRunDataFactory as p, NodeEventPublisher as pt, NoOpExecutionTelemetry as q, RunIntentServiceFactory as r, NoRetryPolicy as rt, EngineWorkflowRunnerService as s, DefaultAsyncSleeper as st, WorkflowRepositoryWebhookTriggerMatcherFactory as t, ExpRetryPolicy as tt, NoOpPollingTriggerLogger as u, ChildExecutionScopeFactory as ut, ENGINE_EXECUTION_LIMITS_DEFAULTS as v, ConnectionInvocationIdFactory as vt, ConfigDrivenOffloadPolicy as w, defineHumanApprovalNode as wt, InlineDrivingScheduler as x, NodeIdSlugifier as xt, EngineExecutionLimitsPolicy as y, WorkflowBuilder as yt, InProcessRetryRunner as z, HitlWorkspaceIdToken as zt };
|
|
6491
|
+
//# sourceMappingURL=runtime-BPZgnZ9G.js.map
|