@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,4 +1,4 @@
|
|
|
1
|
-
const require_di = require('./di-
|
|
1
|
+
const require_di = require('./di-tO6R7VJV.cjs');
|
|
2
2
|
let zod = require("zod");
|
|
3
3
|
zod = require_di.__toESM(zod);
|
|
4
4
|
let node_stream_web = require("node:stream/web");
|
|
@@ -8,6 +8,34 @@ node_crypto = require_di.__toESM(node_crypto);
|
|
|
8
8
|
let tsyringe = require("tsyringe");
|
|
9
9
|
tsyringe = require_di.__toESM(tsyringe);
|
|
10
10
|
|
|
11
|
+
//#region src/contracts/humanTaskStoreTypes.ts
|
|
12
|
+
const HumanTaskStoreToken = Symbol.for("codemation.core.HumanTaskStore");
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/contracts/hitlSeamTypes.ts
|
|
16
|
+
const HitlResumeTokenSignerToken = Symbol.for("codemation.core.HitlResumeTokenSigner");
|
|
17
|
+
const HitlTimeoutJobSchedulerToken = Symbol.for("codemation.core.HitlTimeoutJobScheduler");
|
|
18
|
+
/**
|
|
19
|
+
* Optional workspace ID injected into NodeSuspensionHandler in managed mode (T7 security fix).
|
|
20
|
+
* Allows the handler to stamp the workspaceId on each HumanTaskRecord so HitlCallbackHandler
|
|
21
|
+
* can assert workspace identity independently of the HMAC middleware.
|
|
22
|
+
* Not registered in non-managed mode; NodeSuspensionHandler defaults to null.
|
|
23
|
+
*/
|
|
24
|
+
const HitlWorkspaceIdToken = Symbol.for("codemation.core.HitlWorkspaceId");
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/authoring/DefinedNodeRegistry.ts
|
|
28
|
+
var DefinedNodeRegistry = class {
|
|
29
|
+
static definitions = /* @__PURE__ */ new Map();
|
|
30
|
+
static register(definition) {
|
|
31
|
+
this.definitions.set(definition.key, definition);
|
|
32
|
+
}
|
|
33
|
+
static resolve(key) {
|
|
34
|
+
return this.definitions.get(key);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
11
39
|
//#region src/runtime-types/persistedRuntimeTypeModelRegistry.ts
|
|
12
40
|
/** Shared metadata key used to attach persisted runtime-type information to decorated classes. */
|
|
13
41
|
const persistedRuntimeTypeMetadataKey = Symbol.for("codemation.core.persistedRuntimeTypeMetadata");
|
|
@@ -116,6 +144,305 @@ function chatModel(options = {}) {
|
|
|
116
144
|
return InjectableRuntimeDecoratorComposer.compose("chatModel", options, require("url").pathToFileURL(__filename).href);
|
|
117
145
|
}
|
|
118
146
|
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/authoring/defineNode.types.ts
|
|
149
|
+
const definedNodeCredentialRequirementFactory = {
|
|
150
|
+
create(bindings) {
|
|
151
|
+
if (!bindings) return [];
|
|
152
|
+
return Object.entries(bindings).map(([slotKey, binding]) => {
|
|
153
|
+
if (typeof binding === "string" || this.isCredentialType(binding)) return {
|
|
154
|
+
slotKey,
|
|
155
|
+
label: this.humanize(slotKey),
|
|
156
|
+
acceptedTypes: [this.resolveTypeId(binding)]
|
|
157
|
+
};
|
|
158
|
+
const types = Array.isArray(binding.type) ? binding.type : [binding.type];
|
|
159
|
+
return {
|
|
160
|
+
slotKey,
|
|
161
|
+
label: binding.label ?? this.humanize(slotKey),
|
|
162
|
+
acceptedTypes: types.map((entry) => this.resolveTypeId(entry)),
|
|
163
|
+
optional: binding.optional,
|
|
164
|
+
helpText: binding.helpText,
|
|
165
|
+
helpUrl: binding.helpUrl
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
isCredentialType(value) {
|
|
170
|
+
return Boolean(value) && typeof value === "object" && "definition" in value && typeof value.definition?.typeId === "string";
|
|
171
|
+
},
|
|
172
|
+
resolveTypeId(type) {
|
|
173
|
+
return typeof type === "string" ? type : type.definition.typeId;
|
|
174
|
+
},
|
|
175
|
+
humanize(key) {
|
|
176
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_.]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (character) => character.toUpperCase());
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const definedNodeCredentialAccessorFactory = { create(bindings, ctx) {
|
|
180
|
+
if (!bindings) return {};
|
|
181
|
+
const entries = Object.keys(bindings).map((slotKey) => [slotKey, () => ctx.getCredential(slotKey)]);
|
|
182
|
+
return Object.fromEntries(entries);
|
|
183
|
+
} };
|
|
184
|
+
function defineNode(options) {
|
|
185
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
186
|
+
const DefinedNodeRuntime = class {
|
|
187
|
+
kind = "node";
|
|
188
|
+
outputPorts = ["main"];
|
|
189
|
+
inputSchema = options.inputSchema;
|
|
190
|
+
async execute(args) {
|
|
191
|
+
const ctx = args.ctx;
|
|
192
|
+
const context = {
|
|
193
|
+
config: ctx.config.config,
|
|
194
|
+
credentials: definedNodeCredentialAccessorFactory.create(options.credentials, ctx),
|
|
195
|
+
execution: ctx
|
|
196
|
+
};
|
|
197
|
+
const payload = {
|
|
198
|
+
input: args.input,
|
|
199
|
+
item: args.item,
|
|
200
|
+
itemIndex: args.itemIndex,
|
|
201
|
+
items: args.items,
|
|
202
|
+
ctx
|
|
203
|
+
};
|
|
204
|
+
return await options.execute(payload, context);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
node({ name: options.key })(DefinedNodeRuntime);
|
|
208
|
+
const DefinedRunnableNodeConfig = class {
|
|
209
|
+
kind = "node";
|
|
210
|
+
type = DefinedNodeRuntime;
|
|
211
|
+
icon = options.icon;
|
|
212
|
+
inputSchema = options.inputSchema;
|
|
213
|
+
keepBinaries = options.keepBinaries ?? false;
|
|
214
|
+
constructor(name, config, id) {
|
|
215
|
+
this.name = name;
|
|
216
|
+
this.id = id;
|
|
217
|
+
this.config = config;
|
|
218
|
+
}
|
|
219
|
+
config;
|
|
220
|
+
getCredentialRequirements() {
|
|
221
|
+
return credentialRequirements;
|
|
222
|
+
}
|
|
223
|
+
inspectorSummary() {
|
|
224
|
+
return options.inspectorSummary?.({ config: this.config });
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const definition = {
|
|
228
|
+
kind: "defined-node",
|
|
229
|
+
key: options.key,
|
|
230
|
+
title: options.title,
|
|
231
|
+
description: options.description,
|
|
232
|
+
create(config, name = options.title, id) {
|
|
233
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
234
|
+
},
|
|
235
|
+
register(context) {
|
|
236
|
+
context.registerNode(DefinedNodeRuntime);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
DefinedNodeRegistry.register(definition);
|
|
240
|
+
return definition;
|
|
241
|
+
}
|
|
242
|
+
function defineBatchNode(options) {
|
|
243
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
244
|
+
const DefinedNodeRuntime = class {
|
|
245
|
+
kind = "node";
|
|
246
|
+
outputPorts = ["main"];
|
|
247
|
+
async execute(args) {
|
|
248
|
+
if (args.itemIndex !== args.items.length - 1) return [];
|
|
249
|
+
const ctx = args.ctx;
|
|
250
|
+
const context = {
|
|
251
|
+
config: ctx.config.config,
|
|
252
|
+
credentials: definedNodeCredentialAccessorFactory.create(options.credentials, ctx),
|
|
253
|
+
execution: ctx
|
|
254
|
+
};
|
|
255
|
+
return [...await options.run(args.items.map((item) => item.json), context)];
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
node({ name: options.key })(DefinedNodeRuntime);
|
|
259
|
+
const DefinedRunnableNodeConfig = class {
|
|
260
|
+
kind = "node";
|
|
261
|
+
type = DefinedNodeRuntime;
|
|
262
|
+
icon = options.icon;
|
|
263
|
+
constructor(name, config, id) {
|
|
264
|
+
this.name = name;
|
|
265
|
+
this.id = id;
|
|
266
|
+
this.config = config;
|
|
267
|
+
}
|
|
268
|
+
config;
|
|
269
|
+
getCredentialRequirements() {
|
|
270
|
+
return credentialRequirements;
|
|
271
|
+
}
|
|
272
|
+
inspectorSummary() {
|
|
273
|
+
return options.inspectorSummary?.({ config: this.config });
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const definition = {
|
|
277
|
+
kind: "defined-node",
|
|
278
|
+
key: options.key,
|
|
279
|
+
title: options.title,
|
|
280
|
+
description: options.description,
|
|
281
|
+
create(config, name = options.title, id) {
|
|
282
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
283
|
+
},
|
|
284
|
+
register(context) {
|
|
285
|
+
context.registerNode(DefinedNodeRuntime);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
DefinedNodeRegistry.register(definition);
|
|
289
|
+
return definition;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
//#endregion
|
|
293
|
+
//#region src/authoring/defineHumanApprovalNode.types.ts
|
|
294
|
+
/**
|
|
295
|
+
* Returns `true` when `node` was created by {@link defineHumanApprovalNode}.
|
|
296
|
+
* Uses the `humanApprovalToolBehavior` typed field as the discriminant.
|
|
297
|
+
*/
|
|
298
|
+
function isHumanApprovalNode(node$1) {
|
|
299
|
+
return typeof node$1 === "object" && node$1 !== null && "humanApprovalToolBehavior" in node$1 && typeof node$1.humanApprovalToolBehavior === "object";
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Authoring helper that compiles a HITL approval channel down to a regular
|
|
303
|
+
* {@link defineNode}-backed node with `SuspensionRequest` semantics.
|
|
304
|
+
*
|
|
305
|
+
* **Fast-forward decision semantics:**
|
|
306
|
+
* - On the first `execute` call (no `ctx.resumeContext`): throws a `SuspensionRequest`
|
|
307
|
+
* that calls the author's `deliver`. The engine persists the suspension and continues.
|
|
308
|
+
* - On resume (`ctx.resumeContext` set): calls `onDecision`/`onTimeout` as appropriate,
|
|
309
|
+
* merges a `decision` key into `item.json`, and returns an item with the original
|
|
310
|
+
* `binary` map passed by reference (no copy).
|
|
311
|
+
*
|
|
312
|
+
* **Output shape per item:**
|
|
313
|
+
* ```ts
|
|
314
|
+
* // Input: { json: { invoiceId: 42 }, binary?: {...} }
|
|
315
|
+
* // Output: { json: { invoiceId: 42, decision: { status: "approved", actor, decidedAt } }, binary: <unchanged> }
|
|
316
|
+
* ```
|
|
317
|
+
* If `item.json` already has a `decision` key it is **overwritten**. Namespace as
|
|
318
|
+
* needed if your schema reserves that key for another purpose.
|
|
319
|
+
*
|
|
320
|
+
* **Predicate persistence:**
|
|
321
|
+
* The `approvedPredicate` function is NOT serialized to the suspension record (except
|
|
322
|
+
* as an audit-only string via `toString()`). On resume, the workflow definition is
|
|
323
|
+
* reloaded from code at process start and the predicate closure is rebuilt naturally.
|
|
324
|
+
* If a deploy ships a changed predicate between suspend and resume, the *new* predicate
|
|
325
|
+
* runs — document this in your runbook when the predicate carries business logic that
|
|
326
|
+
* may change across deploys.
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```ts
|
|
330
|
+
* export const slackApprovalNode = defineHumanApprovalNode({
|
|
331
|
+
* key: "my-plugin.slackApproval",
|
|
332
|
+
* title: "Slack Approval",
|
|
333
|
+
* channel: "slack",
|
|
334
|
+
* configSchema: z.object({ channel: z.string(), message: z.string() }),
|
|
335
|
+
* decisionSchema: z.object({ approved: z.boolean(), note: z.string().optional() }),
|
|
336
|
+
*
|
|
337
|
+
* async deliver({ task, config, item }, ctx) {
|
|
338
|
+
* const ts = await postSlackMessage(config.channel, `Approve? <${task.resumeUrl}>`);
|
|
339
|
+
* return { channel: config.channel, ts };
|
|
340
|
+
* },
|
|
341
|
+
*
|
|
342
|
+
* async onDecision({ decision, actor, delivery }, ctx) {
|
|
343
|
+
* await updateSlackMessage(delivery.channel, delivery.ts, decision.approved ? "✅" : "❌");
|
|
344
|
+
* },
|
|
345
|
+
* });
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
function defineHumanApprovalNode(opts) {
|
|
349
|
+
const resolvedPredicate = resolveApprovedPredicate(opts.decisionSchema, opts.approvedPredicate);
|
|
350
|
+
const timeout = opts.defaultTimeout ?? "24h";
|
|
351
|
+
const onTimeout = opts.defaultOnTimeout ?? "halt";
|
|
352
|
+
const inner = defineNode({
|
|
353
|
+
key: opts.key,
|
|
354
|
+
title: opts.title,
|
|
355
|
+
description: opts.description,
|
|
356
|
+
icon: opts.icon,
|
|
357
|
+
configSchema: opts.configSchema,
|
|
358
|
+
inputSchema: opts.inputSchema,
|
|
359
|
+
credentials: opts.credentials,
|
|
360
|
+
inspectorSummary: opts.inspectorSummary ? ({ config }) => opts.inspectorSummary(config) : void 0,
|
|
361
|
+
async execute(args, { config, execution: ctx }) {
|
|
362
|
+
if (!ctx.resumeContext) {
|
|
363
|
+
const subject = buildSubject(opts.title, args.item, ctx);
|
|
364
|
+
throw new require_di.SuspensionRequest({
|
|
365
|
+
decisionSchema: opts.decisionSchema,
|
|
366
|
+
timeout,
|
|
367
|
+
onTimeout,
|
|
368
|
+
subject,
|
|
369
|
+
metadata: {
|
|
370
|
+
channel: opts.channel,
|
|
371
|
+
nodeKey: opts.key,
|
|
372
|
+
approvedPredicateSource: opts.approvedPredicate?.toString() ?? null
|
|
373
|
+
},
|
|
374
|
+
deliver: (handle) => opts.deliver({
|
|
375
|
+
task: handle,
|
|
376
|
+
config,
|
|
377
|
+
input: args.input,
|
|
378
|
+
item: args.item
|
|
379
|
+
}, ctx)
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return await handleResume(args.item, ctx.resumeContext, opts.decisionSchema, resolvedPredicate, opts.onDecision, opts.onTimeout, ctx);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
return Object.assign(inner, { humanApprovalToolBehavior: { onRejected: "return" } });
|
|
386
|
+
}
|
|
387
|
+
function resolveApprovedPredicate(schema, predicate) {
|
|
388
|
+
if (predicate) return predicate;
|
|
389
|
+
const shape = schema.shape;
|
|
390
|
+
if (shape && typeof shape === "object" && "approved" in shape) return (d) => d.approved === true;
|
|
391
|
+
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.");
|
|
392
|
+
}
|
|
393
|
+
function buildSubject(title, item, ctx) {
|
|
394
|
+
return {
|
|
395
|
+
title,
|
|
396
|
+
summary: "",
|
|
397
|
+
attributes: {
|
|
398
|
+
workflowId: ctx.workflowId,
|
|
399
|
+
nodeId: ctx.nodeId,
|
|
400
|
+
item: item.json
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function mergeDecision(item, decision) {
|
|
405
|
+
return {
|
|
406
|
+
json: {
|
|
407
|
+
...item.json,
|
|
408
|
+
decision
|
|
409
|
+
},
|
|
410
|
+
binary: item.binary,
|
|
411
|
+
meta: item.meta
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
async function handleResume(item, resumeContext, decisionSchema, resolvedPredicate, onDecision, onTimeoutCb, ctx) {
|
|
415
|
+
const { decision: dec, delivery, task } = resumeContext;
|
|
416
|
+
if (dec.kind === "timed_out" || dec.kind === "auto_accepted") {
|
|
417
|
+
const policy = dec.kind === "auto_accepted" ? "auto-accept" : "halt";
|
|
418
|
+
await onTimeoutCb?.({
|
|
419
|
+
task,
|
|
420
|
+
delivery,
|
|
421
|
+
item,
|
|
422
|
+
policy
|
|
423
|
+
}, ctx);
|
|
424
|
+
return mergeDecision(item, {
|
|
425
|
+
status: dec.kind === "auto_accepted" ? "auto-accepted" : "timed-out",
|
|
426
|
+
decidedAt: dec.at
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const parsed = decisionSchema.parse(dec.value);
|
|
430
|
+
await onDecision?.({
|
|
431
|
+
decision: parsed,
|
|
432
|
+
actor: dec.actor,
|
|
433
|
+
task,
|
|
434
|
+
delivery,
|
|
435
|
+
item
|
|
436
|
+
}, ctx);
|
|
437
|
+
return mergeDecision(item, {
|
|
438
|
+
status: resolvedPredicate(parsed) ? "approved" : "rejected",
|
|
439
|
+
actor: dec.actor,
|
|
440
|
+
decidedAt: dec.decidedAt,
|
|
441
|
+
note: parsed.note,
|
|
442
|
+
payload: parsed
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
119
446
|
//#endregion
|
|
120
447
|
//#region src/workflow/dsl/WhenBuilder.ts
|
|
121
448
|
var WhenBuilder = class WhenBuilder {
|
|
@@ -242,6 +569,25 @@ var ChainCursor = class ChainCursor {
|
|
|
242
569
|
}
|
|
243
570
|
return new ChainCursor(this.wf, nextEndpoints);
|
|
244
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Chainable shorthand for `.then(node.create(config, metadata?.name, metadata?.nodeId))`.
|
|
574
|
+
*
|
|
575
|
+
* Signals to readers that this step suspends the run and waits for a human decision.
|
|
576
|
+
* Throws at workflow-build time if `node` was not created via `defineHumanApprovalNode`.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```ts
|
|
580
|
+
* workflow
|
|
581
|
+
* .trigger(...)
|
|
582
|
+
* .humanApproval(inboxApproval, { title: "Approve?", body: "...", priority: "normal" })
|
|
583
|
+
* .then(nextStep.create(...))
|
|
584
|
+
* .build();
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
587
|
+
humanApproval(node$1, config, metadata) {
|
|
588
|
+
if (!isHumanApprovalNode(node$1)) throw new Error(`.humanApproval() requires a node created via defineHumanApprovalNode (got '${node$1.key ?? String(node$1)}').`);
|
|
589
|
+
return this.then(node$1.create(config, metadata?.name, metadata?.nodeId));
|
|
590
|
+
}
|
|
245
591
|
build() {
|
|
246
592
|
return this.wf.build();
|
|
247
593
|
}
|
|
@@ -708,7 +1054,7 @@ var NodeExecutionSnapshotFactory = class {
|
|
|
708
1054
|
nodeId: args.nodeId,
|
|
709
1055
|
activationId: args.activationId,
|
|
710
1056
|
parent: args.parent,
|
|
711
|
-
status: "completed",
|
|
1057
|
+
status: args.hitlStatus ?? "completed",
|
|
712
1058
|
queuedAt: args.previous?.queuedAt,
|
|
713
1059
|
startedAt,
|
|
714
1060
|
finishedAt: args.finishedAt,
|
|
@@ -835,7 +1181,9 @@ var ActivationEnqueueService = class {
|
|
|
835
1181
|
nodeSnapshotsByNodeId: {
|
|
836
1182
|
...args.previousNodeSnapshotsByNodeId,
|
|
837
1183
|
[args.request.nodeId]: queuedSnapshot
|
|
838
|
-
}
|
|
1184
|
+
},
|
|
1185
|
+
...args.suspension !== void 0 ? { suspension: args.suspension } : {},
|
|
1186
|
+
...args.pendingResume !== void 0 ? { pendingResume: args.pendingResume } : {}
|
|
839
1187
|
});
|
|
840
1188
|
await this.dispatchPreparedActivation(preparedDispatch);
|
|
841
1189
|
return {
|
|
@@ -1302,6 +1650,16 @@ var CodemationTelemetryAttributeNames = class {
|
|
|
1302
1650
|
static mcpServerId = "mcp.server_id";
|
|
1303
1651
|
/** MCP tool name on spans created for callTool invocations. */
|
|
1304
1652
|
static mcpToolName = "mcp.tool_name";
|
|
1653
|
+
/** Terminal node-execution status (e.g. `"hitl-approved"`, `"hitl-rejected"`) on HITL outcome spans. */
|
|
1654
|
+
static nodeExecutionStatus = "codemation.node.execution_status";
|
|
1655
|
+
/** Populated on run-halted spans; discriminates the halt reason (e.g. `"hitl-rejected"`). */
|
|
1656
|
+
static runHaltReason = "codemation.run.halt_reason";
|
|
1657
|
+
/** Human task ID on `hitl.task.*` span events. */
|
|
1658
|
+
static hitlTaskId = "codemation.hitl.task_id";
|
|
1659
|
+
/** HITL channel name (e.g. `"inbox"`, `"control-plane-inbox"`) on `hitl.task.*` span events. */
|
|
1660
|
+
static hitlChannel = "codemation.hitl.channel";
|
|
1661
|
+
/** Decision outcome (e.g. `"approved"`, `"rejected"`) on `hitl.task.decided` span events. */
|
|
1662
|
+
static hitlDecisionStatus = "codemation.hitl.decision_status";
|
|
1305
1663
|
};
|
|
1306
1664
|
|
|
1307
1665
|
//#endregion
|
|
@@ -1400,12 +1758,13 @@ var ExecutionTelemetryCostTrackingDecoratorFactory = class {
|
|
|
1400
1758
|
//#region src/execution/DefaultExecutionContextFactory.ts
|
|
1401
1759
|
var DefaultExecutionContextFactory = class {
|
|
1402
1760
|
telemetryDecoratorFactory = new ExecutionTelemetryCostTrackingDecoratorFactory();
|
|
1403
|
-
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections) {
|
|
1761
|
+
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections, nodeResolver) {
|
|
1404
1762
|
this.binaryStorage = binaryStorage;
|
|
1405
1763
|
this.telemetryFactory = telemetryFactory;
|
|
1406
1764
|
this.costTrackingFactory = costTrackingFactory;
|
|
1407
1765
|
this.currentDate = currentDate;
|
|
1408
1766
|
this.collections = collections;
|
|
1767
|
+
this.nodeResolver = nodeResolver;
|
|
1409
1768
|
}
|
|
1410
1769
|
create(args) {
|
|
1411
1770
|
const baseTelemetry = args.telemetry ?? this.telemetryFactory.create({
|
|
@@ -1432,7 +1791,11 @@ var DefaultExecutionContextFactory = class {
|
|
|
1432
1791
|
binary: new DefaultExecutionBinaryService(this.binaryStorage, args.workflowId, args.runId, this.currentDate),
|
|
1433
1792
|
getCredential: args.getCredential,
|
|
1434
1793
|
testContext: args.testContext,
|
|
1435
|
-
collections: this.collections
|
|
1794
|
+
collections: this.collections,
|
|
1795
|
+
resolve: (token) => {
|
|
1796
|
+
if (!this.nodeResolver) throw new Error("ExecutionContext.resolve() is not available: no NodeResolver was provided to DefaultExecutionContextFactory.");
|
|
1797
|
+
return this.nodeResolver.resolve(token);
|
|
1798
|
+
}
|
|
1436
1799
|
};
|
|
1437
1800
|
}
|
|
1438
1801
|
};
|
|
@@ -1774,6 +2137,27 @@ var NodeActivationRequestComposer = class {
|
|
|
1774
2137
|
}
|
|
1775
2138
|
};
|
|
1776
2139
|
|
|
2140
|
+
//#endregion
|
|
2141
|
+
//#region src/execution/RunSuspendedError.ts
|
|
2142
|
+
/**
|
|
2143
|
+
* Internal sentinel thrown by {@link NodeSuspensionHandler} after persisting a suspension
|
|
2144
|
+
* entry. `NodeExecutionRequestHandlerService` catches this specifically and returns cleanly —
|
|
2145
|
+
* no continuation call, preventing `resumeFromNodeResult` / `resumeFromNodeError` from
|
|
2146
|
+
* overwriting the `"suspended"` run status.
|
|
2147
|
+
*
|
|
2148
|
+
* The `Error` suffix satisfies the ESLint `no-manual-di-new` allowlist. This is NOT a
|
|
2149
|
+
* user-facing error — it is an engine-internal control-flow primitive and should NOT be
|
|
2150
|
+
* exported from the public barrel.
|
|
2151
|
+
*/
|
|
2152
|
+
var RunSuspendedError = class extends Error {
|
|
2153
|
+
constructor(runId, taskId) {
|
|
2154
|
+
super(`RunSuspendedError: run ${runId} suspended on task ${taskId}`);
|
|
2155
|
+
this.runId = runId;
|
|
2156
|
+
this.taskId = taskId;
|
|
2157
|
+
this.name = "RunSuspendedError";
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
|
|
1777
2161
|
//#endregion
|
|
1778
2162
|
//#region src/execution/NodeExecutor.ts
|
|
1779
2163
|
var NodeExecutor = class {
|
|
@@ -1781,9 +2165,11 @@ var NodeExecutor = class {
|
|
|
1781
2165
|
outputNormalizer = new NodeOutputNormalizer();
|
|
1782
2166
|
itemExprResolver;
|
|
1783
2167
|
outputBehaviorResolver;
|
|
1784
|
-
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver) {
|
|
2168
|
+
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
1785
2169
|
this.nodeInstanceFactory = nodeInstanceFactory;
|
|
1786
2170
|
this.retryRunner = retryRunner;
|
|
2171
|
+
this.suspensionHandler = suspensionHandler;
|
|
2172
|
+
this.loadRunState = loadRunState;
|
|
1787
2173
|
this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
|
|
1788
2174
|
this.outputBehaviorResolver = outputBehaviorResolver ?? new RunnableOutputBehaviorResolver();
|
|
1789
2175
|
}
|
|
@@ -1883,6 +2269,7 @@ var NodeExecutor = class {
|
|
|
1883
2269
|
});
|
|
1884
2270
|
}
|
|
1885
2271
|
const byPort = {};
|
|
2272
|
+
let hasSuspension = false;
|
|
1886
2273
|
for (let i = 0; i < inputBatch.length; i++) {
|
|
1887
2274
|
const item = inputBatch[i];
|
|
1888
2275
|
this.assertItemJsonNotTopLevelArray(request.nodeId, item);
|
|
@@ -1901,7 +2288,28 @@ var NodeExecutor = class {
|
|
|
1901
2288
|
items: inputBatch,
|
|
1902
2289
|
ctx: iterationCtx
|
|
1903
2290
|
};
|
|
1904
|
-
|
|
2291
|
+
let raw;
|
|
2292
|
+
try {
|
|
2293
|
+
raw = await Promise.resolve(node$1.execute(args));
|
|
2294
|
+
} catch (e) {
|
|
2295
|
+
if (e instanceof require_di.SuspensionRequest || e instanceof Error && e.name === "SuspensionRequest" && typeof e.request === "object") {
|
|
2296
|
+
if (!this.suspensionHandler || !this.loadRunState) throw new Error(`Node ${request.nodeId} threw SuspensionRequest but this NodeExecutor has no suspensionHandler configured.`, { cause: e });
|
|
2297
|
+
const state = await this.loadRunState(request.runId);
|
|
2298
|
+
if (!state) throw new Error(`NodeExecutor: run state not found for runId ${request.runId} during suspension`, { cause: e });
|
|
2299
|
+
await this.suspensionHandler.handle({
|
|
2300
|
+
runId: request.runId,
|
|
2301
|
+
nodeId: request.nodeId,
|
|
2302
|
+
activationId: request.activationId,
|
|
2303
|
+
itemIndex: i,
|
|
2304
|
+
suspensionRequest: e,
|
|
2305
|
+
state,
|
|
2306
|
+
telemetry: iterationCtx.telemetry
|
|
2307
|
+
});
|
|
2308
|
+
hasSuspension = true;
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
throw e;
|
|
2312
|
+
}
|
|
1905
2313
|
const normalized = this.outputNormalizer.normalizeExecuteResult({
|
|
1906
2314
|
baseItem: item,
|
|
1907
2315
|
raw,
|
|
@@ -1914,6 +2322,7 @@ var NodeExecutor = class {
|
|
|
1914
2322
|
byPort[port] = list;
|
|
1915
2323
|
}
|
|
1916
2324
|
}
|
|
2325
|
+
if (hasSuspension) throw new RunSuspendedError(request.runId, "unknown");
|
|
1917
2326
|
return byPort;
|
|
1918
2327
|
}
|
|
1919
2328
|
/** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
|
|
@@ -1940,8 +2349,8 @@ var NodeExecutor = class {
|
|
|
1940
2349
|
//#endregion
|
|
1941
2350
|
//#region src/execution/NodeExecutorFactory.ts
|
|
1942
2351
|
var NodeExecutorFactory = class {
|
|
1943
|
-
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver) {
|
|
1944
|
-
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver);
|
|
2352
|
+
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
2353
|
+
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver, suspensionHandler, loadRunState);
|
|
1945
2354
|
}
|
|
1946
2355
|
};
|
|
1947
2356
|
|
|
@@ -2534,6 +2943,7 @@ var PersistedRunStateTerminalBuilder = class {
|
|
|
2534
2943
|
...args.state,
|
|
2535
2944
|
engineCounters: args.engineCounters,
|
|
2536
2945
|
status: args.status,
|
|
2946
|
+
reason: args.reason,
|
|
2537
2947
|
pending: void 0,
|
|
2538
2948
|
queue: args.queue,
|
|
2539
2949
|
outputsByNode: args.outputsByNode,
|
|
@@ -2816,6 +3226,7 @@ var RunContinuationService = class {
|
|
|
2816
3226
|
});
|
|
2817
3227
|
data.setOutputs(args.nodeId, args.outputs);
|
|
2818
3228
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3229
|
+
const hitlResolution = this.resolveHitlStatus(args.outputs);
|
|
2819
3230
|
const completedSnapshot = this.semantics.createFinishedSnapshot({
|
|
2820
3231
|
workflow: wf,
|
|
2821
3232
|
previous: state.nodeSnapshotsByNodeId?.[args.nodeId],
|
|
@@ -2826,8 +3237,41 @@ var RunContinuationService = class {
|
|
|
2826
3237
|
parent: state.parent,
|
|
2827
3238
|
finishedAt: completedAt,
|
|
2828
3239
|
inputsByPort: pendingExecution.inputsByPort,
|
|
2829
|
-
outputs: args.outputs
|
|
3240
|
+
outputs: args.outputs,
|
|
3241
|
+
hitlStatus: hitlResolution?.nodeStatus
|
|
2830
3242
|
});
|
|
3243
|
+
if (hitlResolution?.halt) {
|
|
3244
|
+
const haltedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
3245
|
+
state,
|
|
3246
|
+
engineCounters: state.engineCounters ?? { completedNodeActivations: 0 },
|
|
3247
|
+
status: "halted",
|
|
3248
|
+
reason: hitlResolution.reason,
|
|
3249
|
+
queue: [],
|
|
3250
|
+
outputsByNode: data.dump(),
|
|
3251
|
+
nodeSnapshotsByNodeId: {
|
|
3252
|
+
...state.nodeSnapshotsByNodeId ?? {},
|
|
3253
|
+
[args.nodeId]: completedSnapshot
|
|
3254
|
+
},
|
|
3255
|
+
finishedAtIso: completedAt
|
|
3256
|
+
});
|
|
3257
|
+
await this.workflowExecutionRepository.save(haltedState);
|
|
3258
|
+
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
3259
|
+
await this.terminalPersistence.maybeDeleteAfterTerminalState({
|
|
3260
|
+
workflow: wf,
|
|
3261
|
+
state: haltedState,
|
|
3262
|
+
finalStatus: "failed",
|
|
3263
|
+
finishedAt: completedAt
|
|
3264
|
+
});
|
|
3265
|
+
const result = {
|
|
3266
|
+
runId: state.runId,
|
|
3267
|
+
workflowId: state.workflowId,
|
|
3268
|
+
startedAt: state.startedAt,
|
|
3269
|
+
status: "halted",
|
|
3270
|
+
reason: hitlResolution.reason
|
|
3271
|
+
};
|
|
3272
|
+
this.waiters.resolveRunCompletion(result);
|
|
3273
|
+
return result;
|
|
3274
|
+
}
|
|
2831
3275
|
const completedActivations = (state.engineCounters?.completedNodeActivations ?? 0) + 1;
|
|
2832
3276
|
const engineCounters = { completedNodeActivations: completedActivations };
|
|
2833
3277
|
const maxNodeActivations = state.executionOptions?.maxNodeActivations ?? this.executionLimitsPolicy.createRootExecutionOptions().maxNodeActivations;
|
|
@@ -3128,13 +3572,114 @@ var RunContinuationService = class {
|
|
|
3128
3572
|
status: "failed",
|
|
3129
3573
|
error: { message: "Run failed" }
|
|
3130
3574
|
};
|
|
3575
|
+
if (existing?.status === "halted") return {
|
|
3576
|
+
runId: existing.runId,
|
|
3577
|
+
workflowId: existing.workflowId,
|
|
3578
|
+
startedAt: existing.startedAt,
|
|
3579
|
+
status: "halted",
|
|
3580
|
+
reason: existing.reason ?? "hitl-rejected"
|
|
3581
|
+
};
|
|
3131
3582
|
const result = await this.waiters.waitForCompletion(runId);
|
|
3132
|
-
if (result.status !== "completed" && result.status !== "failed") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3583
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3133
3584
|
return result;
|
|
3134
3585
|
}
|
|
3135
3586
|
async waitForWebhookResponse(runId) {
|
|
3136
3587
|
return await this.waiters.waitForWebhookResponse(runId);
|
|
3137
3588
|
}
|
|
3589
|
+
/**
|
|
3590
|
+
* Re-activate a previously suspended run item with a human decision.
|
|
3591
|
+
*
|
|
3592
|
+
* Called by the HITL resume endpoint. This method:
|
|
3593
|
+
* 1. Loads `PersistedRunState` and locates the suspension entry by `taskId`.
|
|
3594
|
+
* 2. Removes the entry from the `suspension` array; if empty, run stays `"suspended"` until
|
|
3595
|
+
* enqueue flips it to `"pending"`.
|
|
3596
|
+
* 3. Writes `pendingResume` onto the state so `NodeExecutionRequestHandlerService` can
|
|
3597
|
+
* splice `resumeContext` into the node's execution context.
|
|
3598
|
+
* 4. Reconstructs the original input from `outputsByNode` of the upstream node and
|
|
3599
|
+
* enqueues a new activation via `activationEnqueueService`.
|
|
3600
|
+
*
|
|
3601
|
+
* @throws if the run is not found, not suspended, or the `taskId` is unknown.
|
|
3602
|
+
*/
|
|
3603
|
+
async resumeRun(args) {
|
|
3604
|
+
const state = await this.workflowExecutionRepository.load(args.runId);
|
|
3605
|
+
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
3606
|
+
if (state.status !== "suspended") throw new Error(`Run ${args.runId} is not suspended (status: ${state.status})`);
|
|
3607
|
+
const suspensionEntry = (state.suspension ?? []).find((s) => s.taskId === args.taskId);
|
|
3608
|
+
if (!suspensionEntry) throw new Error(`No suspension entry with taskId "${args.taskId}" found on run ${args.runId}`);
|
|
3609
|
+
const wf = this.resolvePersistedWorkflow(state);
|
|
3610
|
+
if (!wf) throw new Error(`Unknown workflowId: ${state.workflowId}`);
|
|
3611
|
+
const { topology, planner } = this.planningFactory.create(wf);
|
|
3612
|
+
const def = topology.defsById.get(suspensionEntry.nodeId);
|
|
3613
|
+
if (!def || def.kind !== "node") throw new Error(`Node ${suspensionEntry.nodeId} is not a runnable node`);
|
|
3614
|
+
const data = this.runDataFactory.create(state.outputsByNode);
|
|
3615
|
+
const limits = this.resolveEngineLimitsFromState(state);
|
|
3616
|
+
const base = this.runExecutionContextFactory.create({
|
|
3617
|
+
runId: state.runId,
|
|
3618
|
+
workflowId: state.workflowId,
|
|
3619
|
+
nodeId: suspensionEntry.nodeId,
|
|
3620
|
+
parent: state.parent,
|
|
3621
|
+
policySnapshot: state.policySnapshot,
|
|
3622
|
+
subworkflowDepth: state.executionOptions?.subworkflowDepth ?? 0,
|
|
3623
|
+
engineMaxNodeActivations: limits.engineMaxNodeActivations,
|
|
3624
|
+
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
3625
|
+
data,
|
|
3626
|
+
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
3627
|
+
testContext: state.executionOptions?.testContext
|
|
3628
|
+
});
|
|
3629
|
+
const parentEdges = wf.edges.filter((e) => e.to.nodeId === suspensionEntry.nodeId);
|
|
3630
|
+
const parentNodeId = parentEdges[0]?.from.nodeId;
|
|
3631
|
+
const parentOutputPort = parentEdges[0]?.from.output ?? "main";
|
|
3632
|
+
const allParentItems = parentNodeId ? data.getOutputItems(parentNodeId, parentOutputPort) ?? [] : [];
|
|
3633
|
+
const resumeInput = allParentItems.length > suspensionEntry.itemIndex ? [allParentItems[suspensionEntry.itemIndex]] : allParentItems;
|
|
3634
|
+
const newActivationId = this.activationIdFactory.makeActivationId();
|
|
3635
|
+
const pendingResume = {
|
|
3636
|
+
activationId: newActivationId,
|
|
3637
|
+
nodeId: suspensionEntry.nodeId,
|
|
3638
|
+
resumeContext: args.resumeContext
|
|
3639
|
+
};
|
|
3640
|
+
const remainingSuspensions = (state.suspension ?? []).filter((s) => s.taskId !== args.taskId);
|
|
3641
|
+
const baseWithResume = {
|
|
3642
|
+
...base,
|
|
3643
|
+
resumeContext: args.resumeContext
|
|
3644
|
+
};
|
|
3645
|
+
const batchId = `resume_${newActivationId}`;
|
|
3646
|
+
const request = this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
|
|
3647
|
+
activationId: newActivationId,
|
|
3648
|
+
runId: state.runId,
|
|
3649
|
+
workflowId: state.workflowId,
|
|
3650
|
+
parent: state.parent,
|
|
3651
|
+
executionOptions: state.executionOptions,
|
|
3652
|
+
base: baseWithResume,
|
|
3653
|
+
data,
|
|
3654
|
+
definition: {
|
|
3655
|
+
id: suspensionEntry.nodeId,
|
|
3656
|
+
config: def.config
|
|
3657
|
+
},
|
|
3658
|
+
batchId,
|
|
3659
|
+
input: resumeInput
|
|
3660
|
+
});
|
|
3661
|
+
const { result, queuedSnapshot } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
3662
|
+
runId: state.runId,
|
|
3663
|
+
workflowId: state.workflowId,
|
|
3664
|
+
startedAt: state.startedAt,
|
|
3665
|
+
parent: state.parent,
|
|
3666
|
+
executionOptions: state.executionOptions,
|
|
3667
|
+
control: state.control,
|
|
3668
|
+
workflowSnapshot: state.workflowSnapshot,
|
|
3669
|
+
mutableState: state.mutableState,
|
|
3670
|
+
policySnapshot: state.policySnapshot,
|
|
3671
|
+
pendingQueue: [],
|
|
3672
|
+
request,
|
|
3673
|
+
previousNodeSnapshotsByNodeId: state.nodeSnapshotsByNodeId ?? {},
|
|
3674
|
+
planner,
|
|
3675
|
+
engineCounters: state.engineCounters,
|
|
3676
|
+
connectionInvocations: state.connectionInvocations ?? [],
|
|
3677
|
+
suspension: remainingSuspensions.length > 0 ? remainingSuspensions : void 0,
|
|
3678
|
+
pendingResume
|
|
3679
|
+
});
|
|
3680
|
+
await this.nodeEventPublisher.publish("nodeQueued", queuedSnapshot);
|
|
3681
|
+
return result;
|
|
3682
|
+
}
|
|
3138
3683
|
async resumeFromWebhookControl(args) {
|
|
3139
3684
|
const data = this.runDataFactory.create(args.state.outputsByNode);
|
|
3140
3685
|
const { topology, planner } = this.planningFactory.create(args.workflow);
|
|
@@ -3521,6 +4066,34 @@ var RunContinuationService = class {
|
|
|
3521
4066
|
this.waiters.resolveRunCompletion(result);
|
|
3522
4067
|
return result;
|
|
3523
4068
|
}
|
|
4069
|
+
/**
|
|
4070
|
+
* Inspects node outputs for a `decision.status` written by `defineHumanApprovalNode`.
|
|
4071
|
+
* Returns the first-class HITL node status and halt classification, or `undefined`
|
|
4072
|
+
* when the node is not a HITL approval node.
|
|
4073
|
+
*/
|
|
4074
|
+
resolveHitlStatus(outputs) {
|
|
4075
|
+
const firstItem = outputs?.main?.[0];
|
|
4076
|
+
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;
|
|
4077
|
+
if (!decisionStatus) return void 0;
|
|
4078
|
+
if (decisionStatus === "approved") return {
|
|
4079
|
+
nodeStatus: "hitl-approved",
|
|
4080
|
+
halt: false
|
|
4081
|
+
};
|
|
4082
|
+
if (decisionStatus === "auto-accepted") return {
|
|
4083
|
+
nodeStatus: "hitl-auto-accepted",
|
|
4084
|
+
halt: false
|
|
4085
|
+
};
|
|
4086
|
+
if (decisionStatus === "rejected") return {
|
|
4087
|
+
nodeStatus: "hitl-rejected",
|
|
4088
|
+
halt: true,
|
|
4089
|
+
reason: "hitl-rejected"
|
|
4090
|
+
};
|
|
4091
|
+
if (decisionStatus === "timed-out") return {
|
|
4092
|
+
nodeStatus: "hitl-timeout",
|
|
4093
|
+
halt: true,
|
|
4094
|
+
reason: "hitl-timeout"
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
3524
4097
|
formatNodeLabel(args) {
|
|
3525
4098
|
const tokenName = typeof args.definition?.type === "function" ? args.definition.type.name : "Node";
|
|
3526
4099
|
return args.definition?.name ? `"${args.definition.name}" (${tokenName}:${args.nodeId})` : `${tokenName}:${args.nodeId}`;
|
|
@@ -4873,13 +5446,19 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4873
5446
|
const portKeys = Object.keys(inputsByPort);
|
|
4874
5447
|
const kind = portKeys.length === 1 && portKeys[0] === "in" ? "single" : "multi";
|
|
4875
5448
|
const batchId = pendingExecution.batchId ?? "batch_1";
|
|
5449
|
+
const pendingResume = state.pendingResume;
|
|
5450
|
+
const resumeContext = pendingResume?.activationId === request.activationId && pendingResume?.nodeId === request.nodeId ? pendingResume.resumeContext : void 0;
|
|
5451
|
+
const baseWithResume = resumeContext != null ? {
|
|
5452
|
+
...base,
|
|
5453
|
+
resumeContext
|
|
5454
|
+
} : base;
|
|
4876
5455
|
const activationRequest = kind === "multi" ? this.nodeActivationRequestComposer.createMultiFromDefinitionWithActivation({
|
|
4877
5456
|
activationId: request.activationId,
|
|
4878
5457
|
runId: request.runId,
|
|
4879
5458
|
workflowId: request.workflowId,
|
|
4880
5459
|
parent: resolvedParent,
|
|
4881
5460
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4882
|
-
base,
|
|
5461
|
+
base: baseWithResume,
|
|
4883
5462
|
data,
|
|
4884
5463
|
definition: {
|
|
4885
5464
|
id: definition.id,
|
|
@@ -4893,7 +5472,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4893
5472
|
workflowId: request.workflowId,
|
|
4894
5473
|
parent: resolvedParent,
|
|
4895
5474
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4896
|
-
base,
|
|
5475
|
+
base: baseWithResume,
|
|
4897
5476
|
data,
|
|
4898
5477
|
definition: {
|
|
4899
5478
|
id: definition.id,
|
|
@@ -4902,6 +5481,13 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4902
5481
|
batchId,
|
|
4903
5482
|
input: inputsByPort.in ?? request.input ?? []
|
|
4904
5483
|
});
|
|
5484
|
+
if (resumeContext != null) {
|
|
5485
|
+
const clearedState = await this.workflowExecutionRepository.load(request.runId);
|
|
5486
|
+
if (clearedState?.pendingResume?.activationId === request.activationId) await this.workflowExecutionRepository.save({
|
|
5487
|
+
...clearedState,
|
|
5488
|
+
pendingResume: void 0
|
|
5489
|
+
});
|
|
5490
|
+
}
|
|
4905
5491
|
await this.continuation.markNodeRunning({
|
|
4906
5492
|
runId: activationRequest.runId,
|
|
4907
5493
|
activationId: activationRequest.activationId,
|
|
@@ -4912,6 +5498,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4912
5498
|
try {
|
|
4913
5499
|
outputs = await this.nodeExecutor.execute(activationRequest);
|
|
4914
5500
|
} catch (error) {
|
|
5501
|
+
if (error instanceof RunSuspendedError) return;
|
|
4915
5502
|
await this.resumeAfterExecutionError(activationRequest, this.asError(error));
|
|
4916
5503
|
return;
|
|
4917
5504
|
}
|
|
@@ -5472,7 +6059,7 @@ var EngineWaiters = class {
|
|
|
5472
6059
|
});
|
|
5473
6060
|
}
|
|
5474
6061
|
resolveRunCompletion(result) {
|
|
5475
|
-
if (result.status !== "completed" && result.status !== "failed") return;
|
|
6062
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") return;
|
|
5476
6063
|
const list = this.completionWaiters.get(result.runId);
|
|
5477
6064
|
if (!list || list.length === 0) return;
|
|
5478
6065
|
this.completionWaiters.delete(result.runId);
|
|
@@ -5566,6 +6153,13 @@ var Engine = class {
|
|
|
5566
6153
|
async waitForWebhookResponse(runId) {
|
|
5567
6154
|
return await this.deps.runContinuationService.waitForWebhookResponse(runId);
|
|
5568
6155
|
}
|
|
6156
|
+
/**
|
|
6157
|
+
* Re-activate a suspended run item with a human decision (HITL).
|
|
6158
|
+
* The HTTP resume endpoint calls this; this method exposes the engine primitive.
|
|
6159
|
+
*/
|
|
6160
|
+
async resumeRun(args) {
|
|
6161
|
+
return await this.deps.runContinuationService.resumeRun(args);
|
|
6162
|
+
}
|
|
5569
6163
|
async handleNodeExecutionRequest(request) {
|
|
5570
6164
|
await this.deps.nodeExecutionRequestHandler.handleNodeExecutionRequest(request);
|
|
5571
6165
|
}
|
|
@@ -5773,6 +6367,7 @@ var RunIntentService = class {
|
|
|
5773
6367
|
};
|
|
5774
6368
|
return await Promise.race([this.engine.waitForWebhookResponse(scheduled.runId), this.engine.waitForCompletion(scheduled.runId).then((completed) => {
|
|
5775
6369
|
if (completed.status === "failed") throw new Error(completed.error.message);
|
|
6370
|
+
if (completed.status === "halted") throw new Error(`Run halted: ${completed.reason}`);
|
|
5776
6371
|
return {
|
|
5777
6372
|
runId: completed.runId,
|
|
5778
6373
|
workflowId: completed.workflowId,
|
|
@@ -5999,6 +6594,12 @@ Object.defineProperty(exports, 'DefaultWorkflowGraphFactory', {
|
|
|
5999
6594
|
return DefaultWorkflowGraphFactory;
|
|
6000
6595
|
}
|
|
6001
6596
|
});
|
|
6597
|
+
Object.defineProperty(exports, 'DefinedNodeRegistry', {
|
|
6598
|
+
enumerable: true,
|
|
6599
|
+
get: function () {
|
|
6600
|
+
return DefinedNodeRegistry;
|
|
6601
|
+
}
|
|
6602
|
+
});
|
|
6002
6603
|
Object.defineProperty(exports, 'ENGINE_EXECUTION_LIMITS_DEFAULTS', {
|
|
6003
6604
|
enumerable: true,
|
|
6004
6605
|
get: function () {
|
|
@@ -6053,6 +6654,30 @@ Object.defineProperty(exports, 'HintOnlyOffloadPolicy', {
|
|
|
6053
6654
|
return HintOnlyOffloadPolicy;
|
|
6054
6655
|
}
|
|
6055
6656
|
});
|
|
6657
|
+
Object.defineProperty(exports, 'HitlResumeTokenSignerToken', {
|
|
6658
|
+
enumerable: true,
|
|
6659
|
+
get: function () {
|
|
6660
|
+
return HitlResumeTokenSignerToken;
|
|
6661
|
+
}
|
|
6662
|
+
});
|
|
6663
|
+
Object.defineProperty(exports, 'HitlTimeoutJobSchedulerToken', {
|
|
6664
|
+
enumerable: true,
|
|
6665
|
+
get: function () {
|
|
6666
|
+
return HitlTimeoutJobSchedulerToken;
|
|
6667
|
+
}
|
|
6668
|
+
});
|
|
6669
|
+
Object.defineProperty(exports, 'HitlWorkspaceIdToken', {
|
|
6670
|
+
enumerable: true,
|
|
6671
|
+
get: function () {
|
|
6672
|
+
return HitlWorkspaceIdToken;
|
|
6673
|
+
}
|
|
6674
|
+
});
|
|
6675
|
+
Object.defineProperty(exports, 'HumanTaskStoreToken', {
|
|
6676
|
+
enumerable: true,
|
|
6677
|
+
get: function () {
|
|
6678
|
+
return HumanTaskStoreToken;
|
|
6679
|
+
}
|
|
6680
|
+
});
|
|
6056
6681
|
Object.defineProperty(exports, 'InMemoryBinaryStorage', {
|
|
6057
6682
|
enumerable: true,
|
|
6058
6683
|
get: function () {
|
|
@@ -6263,6 +6888,12 @@ Object.defineProperty(exports, 'RunPolicySnapshotFactory', {
|
|
|
6263
6888
|
return RunPolicySnapshotFactory;
|
|
6264
6889
|
}
|
|
6265
6890
|
});
|
|
6891
|
+
Object.defineProperty(exports, 'RunSuspendedError', {
|
|
6892
|
+
enumerable: true,
|
|
6893
|
+
get: function () {
|
|
6894
|
+
return RunSuspendedError;
|
|
6895
|
+
}
|
|
6896
|
+
});
|
|
6266
6897
|
Object.defineProperty(exports, 'RunTerminalPersistenceCoordinator', {
|
|
6267
6898
|
enumerable: true,
|
|
6268
6899
|
get: function () {
|
|
@@ -6359,6 +6990,24 @@ Object.defineProperty(exports, 'chatModel', {
|
|
|
6359
6990
|
return chatModel;
|
|
6360
6991
|
}
|
|
6361
6992
|
});
|
|
6993
|
+
Object.defineProperty(exports, 'defineBatchNode', {
|
|
6994
|
+
enumerable: true,
|
|
6995
|
+
get: function () {
|
|
6996
|
+
return defineBatchNode;
|
|
6997
|
+
}
|
|
6998
|
+
});
|
|
6999
|
+
Object.defineProperty(exports, 'defineHumanApprovalNode', {
|
|
7000
|
+
enumerable: true,
|
|
7001
|
+
get: function () {
|
|
7002
|
+
return defineHumanApprovalNode;
|
|
7003
|
+
}
|
|
7004
|
+
});
|
|
7005
|
+
Object.defineProperty(exports, 'defineNode', {
|
|
7006
|
+
enumerable: true,
|
|
7007
|
+
get: function () {
|
|
7008
|
+
return defineNode;
|
|
7009
|
+
}
|
|
7010
|
+
});
|
|
6362
7011
|
Object.defineProperty(exports, 'emitPorts', {
|
|
6363
7012
|
enumerable: true,
|
|
6364
7013
|
get: function () {
|
|
@@ -6377,6 +7026,12 @@ Object.defineProperty(exports, 'getPersistedRuntimeTypeMetadata', {
|
|
|
6377
7026
|
return getPersistedRuntimeTypeMetadata;
|
|
6378
7027
|
}
|
|
6379
7028
|
});
|
|
7029
|
+
Object.defineProperty(exports, 'isHumanApprovalNode', {
|
|
7030
|
+
enumerable: true,
|
|
7031
|
+
get: function () {
|
|
7032
|
+
return isHumanApprovalNode;
|
|
7033
|
+
}
|
|
7034
|
+
});
|
|
6380
7035
|
Object.defineProperty(exports, 'isPortsEmission', {
|
|
6381
7036
|
enumerable: true,
|
|
6382
7037
|
get: function () {
|
|
@@ -6401,4 +7056,4 @@ Object.defineProperty(exports, 'tool', {
|
|
|
6401
7056
|
return tool;
|
|
6402
7057
|
}
|
|
6403
7058
|
});
|
|
6404
|
-
//# sourceMappingURL=runtime-
|
|
7059
|
+
//# sourceMappingURL=runtime-CyW9c9XM.cjs.map
|