@codemation/core 0.11.1 → 0.13.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 +28 -0
- package/dist/{CostCatalogContract-DZgcUBE4.d.cts → CostCatalogContract-Dxq1BTyi.d.cts} +2 -2
- package/dist/{EngineRuntimeRegistration.types-Cggm5GVY.d.cts → EngineRuntimeRegistration.types-CqcTWexS.d.cts} +3 -3
- package/dist/{EngineRuntimeRegistration.types-BQbS9_gs.d.ts → EngineRuntimeRegistration.types-Cr75cSfL.d.ts} +2 -2
- package/dist/InMemoryRunDataFactory-Csy2evr_.d.cts +205 -0
- package/dist/{ItemsInputNormalizer-CwdOhSAK.cjs → ItemsInputNormalizer-57EdA1ad.cjs} +2 -2
- package/dist/{ItemsInputNormalizer-CwdOhSAK.cjs.map → ItemsInputNormalizer-57EdA1ad.cjs.map} +1 -1
- package/dist/{ItemsInputNormalizer-_Mfcd3YU.d.ts → ItemsInputNormalizer-BWtlwdVI.d.ts} +2 -2
- package/dist/{ItemsInputNormalizer-D-MH8MBs.js → ItemsInputNormalizer-BkSvmfAW.js} +2 -2
- package/dist/{ItemsInputNormalizer-D-MH8MBs.js.map → ItemsInputNormalizer-BkSvmfAW.js.map} +1 -1
- package/dist/{ItemsInputNormalizer-C_dpn76M.d.cts → ItemsInputNormalizer-pLrWwUAP.d.cts} +3 -3
- package/dist/{RunIntentService-CEF-sFfI.d.cts → RunIntentService-BitgkKaT.d.cts} +18 -4
- package/dist/{RunIntentService-BVur7x9n.d.ts → RunIntentService-DYpqfu6D.d.ts} +18 -4
- package/dist/{agentMcpTypes-ZiNbNsEi.d.cts → agentMcpTypes-DGIwk6Ue.d.cts} +201 -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-BxuTFTLB.cjs → bootstrap-BEu1fJBM.cjs} +175 -4
- package/dist/bootstrap-BEu1fJBM.cjs.map +1 -0
- package/dist/{bootstrap-D_Yyi0wL.js → bootstrap-CSeInbj1.js} +173 -4
- package/dist/bootstrap-CSeInbj1.js.map +1 -0
- package/dist/browser.cjs +4 -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-BlEKdoZS.cjs → di-C-2ep8NZ.cjs} +44 -1
- package/dist/di-C-2ep8NZ.cjs.map +1 -0
- package/dist/{di-0Wop7z1y.js → di-D9Mv3kF3.js} +33 -2
- package/dist/di-D9Mv3kF3.js.map +1 -0
- package/dist/{executionPersistenceContracts-BgZMRsTa.d.cts → executionPersistenceContracts-CN9d7AnL.d.cts} +2 -2
- package/dist/{index-62Ba9f7D.d.ts → index-CqZeNGAp.d.ts} +343 -101
- package/dist/{index-zWGtEhrf.d.ts → index-rllWL4r-.d.ts} +459 -5
- package/dist/index.cjs +91 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +458 -97
- package/dist/index.d.ts +5 -5
- package/dist/index.js +74 -159
- package/dist/index.js.map +1 -1
- package/dist/{params-B5SENSzZ.d.cts → params-DRUr0F5v.d.cts} +2 -2
- package/dist/{runtime-cxmUkk0l.js → runtime-6-U2Cou5.js} +690 -18
- package/dist/runtime-6-U2Cou5.js.map +1 -0
- package/dist/{runtime-DBzq5YBi.cjs → runtime-DjYXgOo0.cjs} +749 -17
- package/dist/runtime-DjYXgOo0.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/binaries/DefaultExecutionBinaryServiceFactory.ts +27 -2
- package/src/binaries/DefaultNodeBinaryAttachmentServiceFactory.ts +14 -0
- package/src/binaries/boundedReadBinary.types.ts +90 -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 +131 -0
- package/src/contracts/workspaceFileTypes.ts +73 -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 +42 -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-C-2ep8NZ.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
|
}
|
|
@@ -549,6 +895,58 @@ var NodeEventPublisher = class {
|
|
|
549
895
|
}
|
|
550
896
|
};
|
|
551
897
|
|
|
898
|
+
//#endregion
|
|
899
|
+
//#region src/binaries/boundedReadBinary.types.ts
|
|
900
|
+
/**
|
|
901
|
+
* Reads all bytes from an already-opened binary stream into a contiguous `Uint8Array`.
|
|
902
|
+
*
|
|
903
|
+
* Safety contract:
|
|
904
|
+
* - `attachment.size` is checked against `maxBytes` *before* any allocation (no OOM).
|
|
905
|
+
* - A single buffer of exactly `attachment.size` is pre-allocated; the stream fills it
|
|
906
|
+
* directly — no chunks array, no doubling.
|
|
907
|
+
* - A byte-count mismatch between the declared size and actual stream content is an error.
|
|
908
|
+
*
|
|
909
|
+
* This is the single canonical implementation; `ExecutionBinaryService.getBytes`,
|
|
910
|
+
* `getText`, and `getJson` all delegate here. The per-package `readBinaryBody` helpers
|
|
911
|
+
* in `core-nodes` and `core-nodes-ocr` have been removed in favour of this function.
|
|
912
|
+
*/
|
|
913
|
+
async function boundedReadBinary(result, attachment, maxBytes = require_di.BINARY_DEFAULT_MAX_BYTES) {
|
|
914
|
+
if (attachment.size > maxBytes) throw new Error(`Binary attachment size ${attachment.size} bytes exceeds maxBytes ${maxBytes}. Raise the node's maxBytes setting if this document is expected to be larger.`);
|
|
915
|
+
const out = new Uint8Array(attachment.size);
|
|
916
|
+
const reader = result.body.getReader();
|
|
917
|
+
let offset = 0;
|
|
918
|
+
while (true) {
|
|
919
|
+
const { done, value } = await reader.read();
|
|
920
|
+
if (done) break;
|
|
921
|
+
if (!value) continue;
|
|
922
|
+
if (offset + value.byteLength > out.byteLength) throw new Error(`Binary stream produced more bytes than the attachment's declared size (${attachment.size}).`);
|
|
923
|
+
out.set(value, offset);
|
|
924
|
+
offset += value.byteLength;
|
|
925
|
+
}
|
|
926
|
+
if (offset !== out.byteLength) throw new Error(`Binary stream produced ${offset} bytes but attachment declared size ${attachment.size}.`);
|
|
927
|
+
return out;
|
|
928
|
+
}
|
|
929
|
+
/** Shared implementation of `getBytes` used by both binary-service classes. */
|
|
930
|
+
async function readBinaryAsBytes(storage, attachment, maxBytes) {
|
|
931
|
+
const result = await storage.openReadStream(attachment.storageKey);
|
|
932
|
+
if (!result) throw new Error("Binary attachment stream is unavailable.");
|
|
933
|
+
return boundedReadBinary(result, attachment, maxBytes);
|
|
934
|
+
}
|
|
935
|
+
/** Shared implementation of `getText` used by both binary-service classes. */
|
|
936
|
+
async function readBinaryAsText(storage, attachment, maxBytes) {
|
|
937
|
+
const bytes = await readBinaryAsBytes(storage, attachment, maxBytes);
|
|
938
|
+
return new TextDecoder().decode(bytes);
|
|
939
|
+
}
|
|
940
|
+
/** Shared implementation of `getJson` used by both binary-service classes. */
|
|
941
|
+
async function readBinaryAsJson(storage, attachment, maxBytes) {
|
|
942
|
+
const text = await readBinaryAsText(storage, attachment, maxBytes);
|
|
943
|
+
try {
|
|
944
|
+
return JSON.parse(text);
|
|
945
|
+
} catch (cause) {
|
|
946
|
+
throw new SyntaxError(`Binary attachment at storage key "${attachment.storageKey}" is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
552
950
|
//#endregion
|
|
553
951
|
//#region src/binaries/DefaultNodeBinaryAttachmentServiceFactory.ts
|
|
554
952
|
var DefaultNodeBinaryAttachmentService = class DefaultNodeBinaryAttachmentService {
|
|
@@ -599,6 +997,15 @@ var DefaultNodeBinaryAttachmentService = class DefaultNodeBinaryAttachmentServic
|
|
|
599
997
|
async openReadStream(attachment) {
|
|
600
998
|
return await this.storage.openReadStream(attachment.storageKey);
|
|
601
999
|
}
|
|
1000
|
+
async getBytes(attachment, maxBytes) {
|
|
1001
|
+
return readBinaryAsBytes(this.storage, attachment, maxBytes);
|
|
1002
|
+
}
|
|
1003
|
+
async getText(attachment, maxBytes) {
|
|
1004
|
+
return readBinaryAsText(this.storage, attachment, maxBytes);
|
|
1005
|
+
}
|
|
1006
|
+
async getJson(attachment, maxBytes) {
|
|
1007
|
+
return readBinaryAsJson(this.storage, attachment, maxBytes);
|
|
1008
|
+
}
|
|
602
1009
|
createAttachmentId() {
|
|
603
1010
|
return DefaultNodeBinaryAttachmentService.createAttachmentIdValue(`${this.activationId}-${this.now().getTime()}`);
|
|
604
1011
|
}
|
|
@@ -662,8 +1069,24 @@ var DefaultExecutionBinaryService = class {
|
|
|
662
1069
|
forNode(args) {
|
|
663
1070
|
return new DefaultNodeBinaryAttachmentService(this.storage, this.workflowId, this.runId, args.nodeId, args.activationId, this.now);
|
|
664
1071
|
}
|
|
665
|
-
|
|
666
|
-
return
|
|
1072
|
+
openReadStream(attachment) {
|
|
1073
|
+
return this.storage.openReadStream(attachment.storageKey);
|
|
1074
|
+
}
|
|
1075
|
+
async getBytes(attachment, maxBytes) {
|
|
1076
|
+
const stream = await this.openReadStream(attachment);
|
|
1077
|
+
if (!stream) throw new Error("Binary attachment stream is unavailable.");
|
|
1078
|
+
return boundedReadBinary(stream, attachment, maxBytes);
|
|
1079
|
+
}
|
|
1080
|
+
async getText(attachment, maxBytes) {
|
|
1081
|
+
return new TextDecoder().decode(await this.getBytes(attachment, maxBytes));
|
|
1082
|
+
}
|
|
1083
|
+
async getJson(attachment, maxBytes) {
|
|
1084
|
+
const text = await this.getText(attachment, maxBytes);
|
|
1085
|
+
try {
|
|
1086
|
+
return JSON.parse(text);
|
|
1087
|
+
} catch (cause) {
|
|
1088
|
+
throw new SyntaxError(`Binary attachment at storage key "${attachment.storageKey}" is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
1089
|
+
}
|
|
667
1090
|
}
|
|
668
1091
|
};
|
|
669
1092
|
|
|
@@ -708,7 +1131,7 @@ var NodeExecutionSnapshotFactory = class {
|
|
|
708
1131
|
nodeId: args.nodeId,
|
|
709
1132
|
activationId: args.activationId,
|
|
710
1133
|
parent: args.parent,
|
|
711
|
-
status: "completed",
|
|
1134
|
+
status: args.hitlStatus ?? "completed",
|
|
712
1135
|
queuedAt: args.previous?.queuedAt,
|
|
713
1136
|
startedAt,
|
|
714
1137
|
finishedAt: args.finishedAt,
|
|
@@ -835,7 +1258,9 @@ var ActivationEnqueueService = class {
|
|
|
835
1258
|
nodeSnapshotsByNodeId: {
|
|
836
1259
|
...args.previousNodeSnapshotsByNodeId,
|
|
837
1260
|
[args.request.nodeId]: queuedSnapshot
|
|
838
|
-
}
|
|
1261
|
+
},
|
|
1262
|
+
...args.suspension !== void 0 ? { suspension: args.suspension } : {},
|
|
1263
|
+
...args.pendingResume !== void 0 ? { pendingResume: args.pendingResume } : {}
|
|
839
1264
|
});
|
|
840
1265
|
await this.dispatchPreparedActivation(preparedDispatch);
|
|
841
1266
|
return {
|
|
@@ -1302,6 +1727,16 @@ var CodemationTelemetryAttributeNames = class {
|
|
|
1302
1727
|
static mcpServerId = "mcp.server_id";
|
|
1303
1728
|
/** MCP tool name on spans created for callTool invocations. */
|
|
1304
1729
|
static mcpToolName = "mcp.tool_name";
|
|
1730
|
+
/** Terminal node-execution status (e.g. `"hitl-approved"`, `"hitl-rejected"`) on HITL outcome spans. */
|
|
1731
|
+
static nodeExecutionStatus = "codemation.node.execution_status";
|
|
1732
|
+
/** Populated on run-halted spans; discriminates the halt reason (e.g. `"hitl-rejected"`). */
|
|
1733
|
+
static runHaltReason = "codemation.run.halt_reason";
|
|
1734
|
+
/** Human task ID on `hitl.task.*` span events. */
|
|
1735
|
+
static hitlTaskId = "codemation.hitl.task_id";
|
|
1736
|
+
/** HITL channel name (e.g. `"inbox"`, `"control-plane-inbox"`) on `hitl.task.*` span events. */
|
|
1737
|
+
static hitlChannel = "codemation.hitl.channel";
|
|
1738
|
+
/** Decision outcome (e.g. `"approved"`, `"rejected"`) on `hitl.task.decided` span events. */
|
|
1739
|
+
static hitlDecisionStatus = "codemation.hitl.decision_status";
|
|
1305
1740
|
};
|
|
1306
1741
|
|
|
1307
1742
|
//#endregion
|
|
@@ -1400,12 +1835,13 @@ var ExecutionTelemetryCostTrackingDecoratorFactory = class {
|
|
|
1400
1835
|
//#region src/execution/DefaultExecutionContextFactory.ts
|
|
1401
1836
|
var DefaultExecutionContextFactory = class {
|
|
1402
1837
|
telemetryDecoratorFactory = new ExecutionTelemetryCostTrackingDecoratorFactory();
|
|
1403
|
-
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections) {
|
|
1838
|
+
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections, nodeResolver) {
|
|
1404
1839
|
this.binaryStorage = binaryStorage;
|
|
1405
1840
|
this.telemetryFactory = telemetryFactory;
|
|
1406
1841
|
this.costTrackingFactory = costTrackingFactory;
|
|
1407
1842
|
this.currentDate = currentDate;
|
|
1408
1843
|
this.collections = collections;
|
|
1844
|
+
this.nodeResolver = nodeResolver;
|
|
1409
1845
|
}
|
|
1410
1846
|
create(args) {
|
|
1411
1847
|
const baseTelemetry = args.telemetry ?? this.telemetryFactory.create({
|
|
@@ -1432,7 +1868,11 @@ var DefaultExecutionContextFactory = class {
|
|
|
1432
1868
|
binary: new DefaultExecutionBinaryService(this.binaryStorage, args.workflowId, args.runId, this.currentDate),
|
|
1433
1869
|
getCredential: args.getCredential,
|
|
1434
1870
|
testContext: args.testContext,
|
|
1435
|
-
collections: this.collections
|
|
1871
|
+
collections: this.collections,
|
|
1872
|
+
resolve: (token) => {
|
|
1873
|
+
if (!this.nodeResolver) throw new Error("ExecutionContext.resolve() is not available: no NodeResolver was provided to DefaultExecutionContextFactory.");
|
|
1874
|
+
return this.nodeResolver.resolve(token);
|
|
1875
|
+
}
|
|
1436
1876
|
};
|
|
1437
1877
|
}
|
|
1438
1878
|
};
|
|
@@ -1774,6 +2214,27 @@ var NodeActivationRequestComposer = class {
|
|
|
1774
2214
|
}
|
|
1775
2215
|
};
|
|
1776
2216
|
|
|
2217
|
+
//#endregion
|
|
2218
|
+
//#region src/execution/RunSuspendedError.ts
|
|
2219
|
+
/**
|
|
2220
|
+
* Internal sentinel thrown by {@link NodeSuspensionHandler} after persisting a suspension
|
|
2221
|
+
* entry. `NodeExecutionRequestHandlerService` catches this specifically and returns cleanly —
|
|
2222
|
+
* no continuation call, preventing `resumeFromNodeResult` / `resumeFromNodeError` from
|
|
2223
|
+
* overwriting the `"suspended"` run status.
|
|
2224
|
+
*
|
|
2225
|
+
* The `Error` suffix satisfies the ESLint `no-manual-di-new` allowlist. This is NOT a
|
|
2226
|
+
* user-facing error — it is an engine-internal control-flow primitive and should NOT be
|
|
2227
|
+
* exported from the public barrel.
|
|
2228
|
+
*/
|
|
2229
|
+
var RunSuspendedError = class extends Error {
|
|
2230
|
+
constructor(runId, taskId) {
|
|
2231
|
+
super(`RunSuspendedError: run ${runId} suspended on task ${taskId}`);
|
|
2232
|
+
this.runId = runId;
|
|
2233
|
+
this.taskId = taskId;
|
|
2234
|
+
this.name = "RunSuspendedError";
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
|
|
1777
2238
|
//#endregion
|
|
1778
2239
|
//#region src/execution/NodeExecutor.ts
|
|
1779
2240
|
var NodeExecutor = class {
|
|
@@ -1781,9 +2242,11 @@ var NodeExecutor = class {
|
|
|
1781
2242
|
outputNormalizer = new NodeOutputNormalizer();
|
|
1782
2243
|
itemExprResolver;
|
|
1783
2244
|
outputBehaviorResolver;
|
|
1784
|
-
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver) {
|
|
2245
|
+
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
1785
2246
|
this.nodeInstanceFactory = nodeInstanceFactory;
|
|
1786
2247
|
this.retryRunner = retryRunner;
|
|
2248
|
+
this.suspensionHandler = suspensionHandler;
|
|
2249
|
+
this.loadRunState = loadRunState;
|
|
1787
2250
|
this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
|
|
1788
2251
|
this.outputBehaviorResolver = outputBehaviorResolver ?? new RunnableOutputBehaviorResolver();
|
|
1789
2252
|
}
|
|
@@ -1883,6 +2346,7 @@ var NodeExecutor = class {
|
|
|
1883
2346
|
});
|
|
1884
2347
|
}
|
|
1885
2348
|
const byPort = {};
|
|
2349
|
+
let hasSuspension = false;
|
|
1886
2350
|
for (let i = 0; i < inputBatch.length; i++) {
|
|
1887
2351
|
const item = inputBatch[i];
|
|
1888
2352
|
this.assertItemJsonNotTopLevelArray(request.nodeId, item);
|
|
@@ -1901,7 +2365,28 @@ var NodeExecutor = class {
|
|
|
1901
2365
|
items: inputBatch,
|
|
1902
2366
|
ctx: iterationCtx
|
|
1903
2367
|
};
|
|
1904
|
-
|
|
2368
|
+
let raw;
|
|
2369
|
+
try {
|
|
2370
|
+
raw = await Promise.resolve(node$1.execute(args));
|
|
2371
|
+
} catch (e) {
|
|
2372
|
+
if (e instanceof require_di.SuspensionRequest || e instanceof Error && e.name === "SuspensionRequest" && typeof e.request === "object") {
|
|
2373
|
+
if (!this.suspensionHandler || !this.loadRunState) throw new Error(`Node ${request.nodeId} threw SuspensionRequest but this NodeExecutor has no suspensionHandler configured.`, { cause: e });
|
|
2374
|
+
const state = await this.loadRunState(request.runId);
|
|
2375
|
+
if (!state) throw new Error(`NodeExecutor: run state not found for runId ${request.runId} during suspension`, { cause: e });
|
|
2376
|
+
await this.suspensionHandler.handle({
|
|
2377
|
+
runId: request.runId,
|
|
2378
|
+
nodeId: request.nodeId,
|
|
2379
|
+
activationId: request.activationId,
|
|
2380
|
+
itemIndex: i,
|
|
2381
|
+
suspensionRequest: e,
|
|
2382
|
+
state,
|
|
2383
|
+
telemetry: iterationCtx.telemetry
|
|
2384
|
+
});
|
|
2385
|
+
hasSuspension = true;
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
throw e;
|
|
2389
|
+
}
|
|
1905
2390
|
const normalized = this.outputNormalizer.normalizeExecuteResult({
|
|
1906
2391
|
baseItem: item,
|
|
1907
2392
|
raw,
|
|
@@ -1914,6 +2399,7 @@ var NodeExecutor = class {
|
|
|
1914
2399
|
byPort[port] = list;
|
|
1915
2400
|
}
|
|
1916
2401
|
}
|
|
2402
|
+
if (hasSuspension) throw new RunSuspendedError(request.runId, "unknown");
|
|
1917
2403
|
return byPort;
|
|
1918
2404
|
}
|
|
1919
2405
|
/** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
|
|
@@ -1940,8 +2426,8 @@ var NodeExecutor = class {
|
|
|
1940
2426
|
//#endregion
|
|
1941
2427
|
//#region src/execution/NodeExecutorFactory.ts
|
|
1942
2428
|
var NodeExecutorFactory = class {
|
|
1943
|
-
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver) {
|
|
1944
|
-
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver);
|
|
2429
|
+
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
2430
|
+
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver, suspensionHandler, loadRunState);
|
|
1945
2431
|
}
|
|
1946
2432
|
};
|
|
1947
2433
|
|
|
@@ -2534,6 +3020,7 @@ var PersistedRunStateTerminalBuilder = class {
|
|
|
2534
3020
|
...args.state,
|
|
2535
3021
|
engineCounters: args.engineCounters,
|
|
2536
3022
|
status: args.status,
|
|
3023
|
+
reason: args.reason,
|
|
2537
3024
|
pending: void 0,
|
|
2538
3025
|
queue: args.queue,
|
|
2539
3026
|
outputsByNode: args.outputsByNode,
|
|
@@ -2816,6 +3303,7 @@ var RunContinuationService = class {
|
|
|
2816
3303
|
});
|
|
2817
3304
|
data.setOutputs(args.nodeId, args.outputs);
|
|
2818
3305
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3306
|
+
const hitlResolution = this.resolveHitlStatus(args.outputs);
|
|
2819
3307
|
const completedSnapshot = this.semantics.createFinishedSnapshot({
|
|
2820
3308
|
workflow: wf,
|
|
2821
3309
|
previous: state.nodeSnapshotsByNodeId?.[args.nodeId],
|
|
@@ -2826,8 +3314,41 @@ var RunContinuationService = class {
|
|
|
2826
3314
|
parent: state.parent,
|
|
2827
3315
|
finishedAt: completedAt,
|
|
2828
3316
|
inputsByPort: pendingExecution.inputsByPort,
|
|
2829
|
-
outputs: args.outputs
|
|
3317
|
+
outputs: args.outputs,
|
|
3318
|
+
hitlStatus: hitlResolution?.nodeStatus
|
|
2830
3319
|
});
|
|
3320
|
+
if (hitlResolution?.halt) {
|
|
3321
|
+
const haltedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
3322
|
+
state,
|
|
3323
|
+
engineCounters: state.engineCounters ?? { completedNodeActivations: 0 },
|
|
3324
|
+
status: "halted",
|
|
3325
|
+
reason: hitlResolution.reason,
|
|
3326
|
+
queue: [],
|
|
3327
|
+
outputsByNode: data.dump(),
|
|
3328
|
+
nodeSnapshotsByNodeId: {
|
|
3329
|
+
...state.nodeSnapshotsByNodeId ?? {},
|
|
3330
|
+
[args.nodeId]: completedSnapshot
|
|
3331
|
+
},
|
|
3332
|
+
finishedAtIso: completedAt
|
|
3333
|
+
});
|
|
3334
|
+
await this.workflowExecutionRepository.save(haltedState);
|
|
3335
|
+
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
3336
|
+
await this.terminalPersistence.maybeDeleteAfterTerminalState({
|
|
3337
|
+
workflow: wf,
|
|
3338
|
+
state: haltedState,
|
|
3339
|
+
finalStatus: "failed",
|
|
3340
|
+
finishedAt: completedAt
|
|
3341
|
+
});
|
|
3342
|
+
const result = {
|
|
3343
|
+
runId: state.runId,
|
|
3344
|
+
workflowId: state.workflowId,
|
|
3345
|
+
startedAt: state.startedAt,
|
|
3346
|
+
status: "halted",
|
|
3347
|
+
reason: hitlResolution.reason
|
|
3348
|
+
};
|
|
3349
|
+
this.waiters.resolveRunCompletion(result);
|
|
3350
|
+
return result;
|
|
3351
|
+
}
|
|
2831
3352
|
const completedActivations = (state.engineCounters?.completedNodeActivations ?? 0) + 1;
|
|
2832
3353
|
const engineCounters = { completedNodeActivations: completedActivations };
|
|
2833
3354
|
const maxNodeActivations = state.executionOptions?.maxNodeActivations ?? this.executionLimitsPolicy.createRootExecutionOptions().maxNodeActivations;
|
|
@@ -3128,13 +3649,114 @@ var RunContinuationService = class {
|
|
|
3128
3649
|
status: "failed",
|
|
3129
3650
|
error: { message: "Run failed" }
|
|
3130
3651
|
};
|
|
3652
|
+
if (existing?.status === "halted") return {
|
|
3653
|
+
runId: existing.runId,
|
|
3654
|
+
workflowId: existing.workflowId,
|
|
3655
|
+
startedAt: existing.startedAt,
|
|
3656
|
+
status: "halted",
|
|
3657
|
+
reason: existing.reason ?? "hitl-rejected"
|
|
3658
|
+
};
|
|
3131
3659
|
const result = await this.waiters.waitForCompletion(runId);
|
|
3132
|
-
if (result.status !== "completed" && result.status !== "failed") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3660
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3133
3661
|
return result;
|
|
3134
3662
|
}
|
|
3135
3663
|
async waitForWebhookResponse(runId) {
|
|
3136
3664
|
return await this.waiters.waitForWebhookResponse(runId);
|
|
3137
3665
|
}
|
|
3666
|
+
/**
|
|
3667
|
+
* Re-activate a previously suspended run item with a human decision.
|
|
3668
|
+
*
|
|
3669
|
+
* Called by the HITL resume endpoint. This method:
|
|
3670
|
+
* 1. Loads `PersistedRunState` and locates the suspension entry by `taskId`.
|
|
3671
|
+
* 2. Removes the entry from the `suspension` array; if empty, run stays `"suspended"` until
|
|
3672
|
+
* enqueue flips it to `"pending"`.
|
|
3673
|
+
* 3. Writes `pendingResume` onto the state so `NodeExecutionRequestHandlerService` can
|
|
3674
|
+
* splice `resumeContext` into the node's execution context.
|
|
3675
|
+
* 4. Reconstructs the original input from `outputsByNode` of the upstream node and
|
|
3676
|
+
* enqueues a new activation via `activationEnqueueService`.
|
|
3677
|
+
*
|
|
3678
|
+
* @throws if the run is not found, not suspended, or the `taskId` is unknown.
|
|
3679
|
+
*/
|
|
3680
|
+
async resumeRun(args) {
|
|
3681
|
+
const state = await this.workflowExecutionRepository.load(args.runId);
|
|
3682
|
+
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
3683
|
+
if (state.status !== "suspended") throw new Error(`Run ${args.runId} is not suspended (status: ${state.status})`);
|
|
3684
|
+
const suspensionEntry = (state.suspension ?? []).find((s) => s.taskId === args.taskId);
|
|
3685
|
+
if (!suspensionEntry) throw new Error(`No suspension entry with taskId "${args.taskId}" found on run ${args.runId}`);
|
|
3686
|
+
const wf = this.resolvePersistedWorkflow(state);
|
|
3687
|
+
if (!wf) throw new Error(`Unknown workflowId: ${state.workflowId}`);
|
|
3688
|
+
const { topology, planner } = this.planningFactory.create(wf);
|
|
3689
|
+
const def = topology.defsById.get(suspensionEntry.nodeId);
|
|
3690
|
+
if (!def || def.kind !== "node") throw new Error(`Node ${suspensionEntry.nodeId} is not a runnable node`);
|
|
3691
|
+
const data = this.runDataFactory.create(state.outputsByNode);
|
|
3692
|
+
const limits = this.resolveEngineLimitsFromState(state);
|
|
3693
|
+
const base = this.runExecutionContextFactory.create({
|
|
3694
|
+
runId: state.runId,
|
|
3695
|
+
workflowId: state.workflowId,
|
|
3696
|
+
nodeId: suspensionEntry.nodeId,
|
|
3697
|
+
parent: state.parent,
|
|
3698
|
+
policySnapshot: state.policySnapshot,
|
|
3699
|
+
subworkflowDepth: state.executionOptions?.subworkflowDepth ?? 0,
|
|
3700
|
+
engineMaxNodeActivations: limits.engineMaxNodeActivations,
|
|
3701
|
+
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
3702
|
+
data,
|
|
3703
|
+
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
3704
|
+
testContext: state.executionOptions?.testContext
|
|
3705
|
+
});
|
|
3706
|
+
const parentEdges = wf.edges.filter((e) => e.to.nodeId === suspensionEntry.nodeId);
|
|
3707
|
+
const parentNodeId = parentEdges[0]?.from.nodeId;
|
|
3708
|
+
const parentOutputPort = parentEdges[0]?.from.output ?? "main";
|
|
3709
|
+
const allParentItems = parentNodeId ? data.getOutputItems(parentNodeId, parentOutputPort) ?? [] : [];
|
|
3710
|
+
const resumeInput = allParentItems.length > suspensionEntry.itemIndex ? [allParentItems[suspensionEntry.itemIndex]] : allParentItems;
|
|
3711
|
+
const newActivationId = this.activationIdFactory.makeActivationId();
|
|
3712
|
+
const pendingResume = {
|
|
3713
|
+
activationId: newActivationId,
|
|
3714
|
+
nodeId: suspensionEntry.nodeId,
|
|
3715
|
+
resumeContext: args.resumeContext
|
|
3716
|
+
};
|
|
3717
|
+
const remainingSuspensions = (state.suspension ?? []).filter((s) => s.taskId !== args.taskId);
|
|
3718
|
+
const baseWithResume = {
|
|
3719
|
+
...base,
|
|
3720
|
+
resumeContext: args.resumeContext
|
|
3721
|
+
};
|
|
3722
|
+
const batchId = `resume_${newActivationId}`;
|
|
3723
|
+
const request = this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
|
|
3724
|
+
activationId: newActivationId,
|
|
3725
|
+
runId: state.runId,
|
|
3726
|
+
workflowId: state.workflowId,
|
|
3727
|
+
parent: state.parent,
|
|
3728
|
+
executionOptions: state.executionOptions,
|
|
3729
|
+
base: baseWithResume,
|
|
3730
|
+
data,
|
|
3731
|
+
definition: {
|
|
3732
|
+
id: suspensionEntry.nodeId,
|
|
3733
|
+
config: def.config
|
|
3734
|
+
},
|
|
3735
|
+
batchId,
|
|
3736
|
+
input: resumeInput
|
|
3737
|
+
});
|
|
3738
|
+
const { result, queuedSnapshot } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
3739
|
+
runId: state.runId,
|
|
3740
|
+
workflowId: state.workflowId,
|
|
3741
|
+
startedAt: state.startedAt,
|
|
3742
|
+
parent: state.parent,
|
|
3743
|
+
executionOptions: state.executionOptions,
|
|
3744
|
+
control: state.control,
|
|
3745
|
+
workflowSnapshot: state.workflowSnapshot,
|
|
3746
|
+
mutableState: state.mutableState,
|
|
3747
|
+
policySnapshot: state.policySnapshot,
|
|
3748
|
+
pendingQueue: [],
|
|
3749
|
+
request,
|
|
3750
|
+
previousNodeSnapshotsByNodeId: state.nodeSnapshotsByNodeId ?? {},
|
|
3751
|
+
planner,
|
|
3752
|
+
engineCounters: state.engineCounters,
|
|
3753
|
+
connectionInvocations: state.connectionInvocations ?? [],
|
|
3754
|
+
suspension: remainingSuspensions.length > 0 ? remainingSuspensions : void 0,
|
|
3755
|
+
pendingResume
|
|
3756
|
+
});
|
|
3757
|
+
await this.nodeEventPublisher.publish("nodeQueued", queuedSnapshot);
|
|
3758
|
+
return result;
|
|
3759
|
+
}
|
|
3138
3760
|
async resumeFromWebhookControl(args) {
|
|
3139
3761
|
const data = this.runDataFactory.create(args.state.outputsByNode);
|
|
3140
3762
|
const { topology, planner } = this.planningFactory.create(args.workflow);
|
|
@@ -3521,6 +4143,34 @@ var RunContinuationService = class {
|
|
|
3521
4143
|
this.waiters.resolveRunCompletion(result);
|
|
3522
4144
|
return result;
|
|
3523
4145
|
}
|
|
4146
|
+
/**
|
|
4147
|
+
* Inspects node outputs for a `decision.status` written by `defineHumanApprovalNode`.
|
|
4148
|
+
* Returns the first-class HITL node status and halt classification, or `undefined`
|
|
4149
|
+
* when the node is not a HITL approval node.
|
|
4150
|
+
*/
|
|
4151
|
+
resolveHitlStatus(outputs) {
|
|
4152
|
+
const firstItem = outputs?.main?.[0];
|
|
4153
|
+
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;
|
|
4154
|
+
if (!decisionStatus) return void 0;
|
|
4155
|
+
if (decisionStatus === "approved") return {
|
|
4156
|
+
nodeStatus: "hitl-approved",
|
|
4157
|
+
halt: false
|
|
4158
|
+
};
|
|
4159
|
+
if (decisionStatus === "auto-accepted") return {
|
|
4160
|
+
nodeStatus: "hitl-auto-accepted",
|
|
4161
|
+
halt: false
|
|
4162
|
+
};
|
|
4163
|
+
if (decisionStatus === "rejected") return {
|
|
4164
|
+
nodeStatus: "hitl-rejected",
|
|
4165
|
+
halt: true,
|
|
4166
|
+
reason: "hitl-rejected"
|
|
4167
|
+
};
|
|
4168
|
+
if (decisionStatus === "timed-out") return {
|
|
4169
|
+
nodeStatus: "hitl-timeout",
|
|
4170
|
+
halt: true,
|
|
4171
|
+
reason: "hitl-timeout"
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
3524
4174
|
formatNodeLabel(args) {
|
|
3525
4175
|
const tokenName = typeof args.definition?.type === "function" ? args.definition.type.name : "Node";
|
|
3526
4176
|
return args.definition?.name ? `"${args.definition.name}" (${tokenName}:${args.nodeId})` : `${tokenName}:${args.nodeId}`;
|
|
@@ -4873,13 +5523,19 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4873
5523
|
const portKeys = Object.keys(inputsByPort);
|
|
4874
5524
|
const kind = portKeys.length === 1 && portKeys[0] === "in" ? "single" : "multi";
|
|
4875
5525
|
const batchId = pendingExecution.batchId ?? "batch_1";
|
|
5526
|
+
const pendingResume = state.pendingResume;
|
|
5527
|
+
const resumeContext = pendingResume?.activationId === request.activationId && pendingResume?.nodeId === request.nodeId ? pendingResume.resumeContext : void 0;
|
|
5528
|
+
const baseWithResume = resumeContext != null ? {
|
|
5529
|
+
...base,
|
|
5530
|
+
resumeContext
|
|
5531
|
+
} : base;
|
|
4876
5532
|
const activationRequest = kind === "multi" ? this.nodeActivationRequestComposer.createMultiFromDefinitionWithActivation({
|
|
4877
5533
|
activationId: request.activationId,
|
|
4878
5534
|
runId: request.runId,
|
|
4879
5535
|
workflowId: request.workflowId,
|
|
4880
5536
|
parent: resolvedParent,
|
|
4881
5537
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4882
|
-
base,
|
|
5538
|
+
base: baseWithResume,
|
|
4883
5539
|
data,
|
|
4884
5540
|
definition: {
|
|
4885
5541
|
id: definition.id,
|
|
@@ -4893,7 +5549,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4893
5549
|
workflowId: request.workflowId,
|
|
4894
5550
|
parent: resolvedParent,
|
|
4895
5551
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4896
|
-
base,
|
|
5552
|
+
base: baseWithResume,
|
|
4897
5553
|
data,
|
|
4898
5554
|
definition: {
|
|
4899
5555
|
id: definition.id,
|
|
@@ -4902,6 +5558,13 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4902
5558
|
batchId,
|
|
4903
5559
|
input: inputsByPort.in ?? request.input ?? []
|
|
4904
5560
|
});
|
|
5561
|
+
if (resumeContext != null) {
|
|
5562
|
+
const clearedState = await this.workflowExecutionRepository.load(request.runId);
|
|
5563
|
+
if (clearedState?.pendingResume?.activationId === request.activationId) await this.workflowExecutionRepository.save({
|
|
5564
|
+
...clearedState,
|
|
5565
|
+
pendingResume: void 0
|
|
5566
|
+
});
|
|
5567
|
+
}
|
|
4905
5568
|
await this.continuation.markNodeRunning({
|
|
4906
5569
|
runId: activationRequest.runId,
|
|
4907
5570
|
activationId: activationRequest.activationId,
|
|
@@ -4912,6 +5575,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4912
5575
|
try {
|
|
4913
5576
|
outputs = await this.nodeExecutor.execute(activationRequest);
|
|
4914
5577
|
} catch (error) {
|
|
5578
|
+
if (error instanceof RunSuspendedError) return;
|
|
4915
5579
|
await this.resumeAfterExecutionError(activationRequest, this.asError(error));
|
|
4916
5580
|
return;
|
|
4917
5581
|
}
|
|
@@ -5472,7 +6136,7 @@ var EngineWaiters = class {
|
|
|
5472
6136
|
});
|
|
5473
6137
|
}
|
|
5474
6138
|
resolveRunCompletion(result) {
|
|
5475
|
-
if (result.status !== "completed" && result.status !== "failed") return;
|
|
6139
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") return;
|
|
5476
6140
|
const list = this.completionWaiters.get(result.runId);
|
|
5477
6141
|
if (!list || list.length === 0) return;
|
|
5478
6142
|
this.completionWaiters.delete(result.runId);
|
|
@@ -5566,6 +6230,13 @@ var Engine = class {
|
|
|
5566
6230
|
async waitForWebhookResponse(runId) {
|
|
5567
6231
|
return await this.deps.runContinuationService.waitForWebhookResponse(runId);
|
|
5568
6232
|
}
|
|
6233
|
+
/**
|
|
6234
|
+
* Re-activate a suspended run item with a human decision (HITL).
|
|
6235
|
+
* The HTTP resume endpoint calls this; this method exposes the engine primitive.
|
|
6236
|
+
*/
|
|
6237
|
+
async resumeRun(args) {
|
|
6238
|
+
return await this.deps.runContinuationService.resumeRun(args);
|
|
6239
|
+
}
|
|
5569
6240
|
async handleNodeExecutionRequest(request) {
|
|
5570
6241
|
await this.deps.nodeExecutionRequestHandler.handleNodeExecutionRequest(request);
|
|
5571
6242
|
}
|
|
@@ -5773,6 +6444,7 @@ var RunIntentService = class {
|
|
|
5773
6444
|
};
|
|
5774
6445
|
return await Promise.race([this.engine.waitForWebhookResponse(scheduled.runId), this.engine.waitForCompletion(scheduled.runId).then((completed) => {
|
|
5775
6446
|
if (completed.status === "failed") throw new Error(completed.error.message);
|
|
6447
|
+
if (completed.status === "halted") throw new Error(`Run halted: ${completed.reason}`);
|
|
5776
6448
|
return {
|
|
5777
6449
|
runId: completed.runId,
|
|
5778
6450
|
workflowId: completed.workflowId,
|
|
@@ -5999,6 +6671,12 @@ Object.defineProperty(exports, 'DefaultWorkflowGraphFactory', {
|
|
|
5999
6671
|
return DefaultWorkflowGraphFactory;
|
|
6000
6672
|
}
|
|
6001
6673
|
});
|
|
6674
|
+
Object.defineProperty(exports, 'DefinedNodeRegistry', {
|
|
6675
|
+
enumerable: true,
|
|
6676
|
+
get: function () {
|
|
6677
|
+
return DefinedNodeRegistry;
|
|
6678
|
+
}
|
|
6679
|
+
});
|
|
6002
6680
|
Object.defineProperty(exports, 'ENGINE_EXECUTION_LIMITS_DEFAULTS', {
|
|
6003
6681
|
enumerable: true,
|
|
6004
6682
|
get: function () {
|
|
@@ -6053,6 +6731,30 @@ Object.defineProperty(exports, 'HintOnlyOffloadPolicy', {
|
|
|
6053
6731
|
return HintOnlyOffloadPolicy;
|
|
6054
6732
|
}
|
|
6055
6733
|
});
|
|
6734
|
+
Object.defineProperty(exports, 'HitlResumeTokenSignerToken', {
|
|
6735
|
+
enumerable: true,
|
|
6736
|
+
get: function () {
|
|
6737
|
+
return HitlResumeTokenSignerToken;
|
|
6738
|
+
}
|
|
6739
|
+
});
|
|
6740
|
+
Object.defineProperty(exports, 'HitlTimeoutJobSchedulerToken', {
|
|
6741
|
+
enumerable: true,
|
|
6742
|
+
get: function () {
|
|
6743
|
+
return HitlTimeoutJobSchedulerToken;
|
|
6744
|
+
}
|
|
6745
|
+
});
|
|
6746
|
+
Object.defineProperty(exports, 'HitlWorkspaceIdToken', {
|
|
6747
|
+
enumerable: true,
|
|
6748
|
+
get: function () {
|
|
6749
|
+
return HitlWorkspaceIdToken;
|
|
6750
|
+
}
|
|
6751
|
+
});
|
|
6752
|
+
Object.defineProperty(exports, 'HumanTaskStoreToken', {
|
|
6753
|
+
enumerable: true,
|
|
6754
|
+
get: function () {
|
|
6755
|
+
return HumanTaskStoreToken;
|
|
6756
|
+
}
|
|
6757
|
+
});
|
|
6056
6758
|
Object.defineProperty(exports, 'InMemoryBinaryStorage', {
|
|
6057
6759
|
enumerable: true,
|
|
6058
6760
|
get: function () {
|
|
@@ -6263,6 +6965,12 @@ Object.defineProperty(exports, 'RunPolicySnapshotFactory', {
|
|
|
6263
6965
|
return RunPolicySnapshotFactory;
|
|
6264
6966
|
}
|
|
6265
6967
|
});
|
|
6968
|
+
Object.defineProperty(exports, 'RunSuspendedError', {
|
|
6969
|
+
enumerable: true,
|
|
6970
|
+
get: function () {
|
|
6971
|
+
return RunSuspendedError;
|
|
6972
|
+
}
|
|
6973
|
+
});
|
|
6266
6974
|
Object.defineProperty(exports, 'RunTerminalPersistenceCoordinator', {
|
|
6267
6975
|
enumerable: true,
|
|
6268
6976
|
get: function () {
|
|
@@ -6359,6 +7067,24 @@ Object.defineProperty(exports, 'chatModel', {
|
|
|
6359
7067
|
return chatModel;
|
|
6360
7068
|
}
|
|
6361
7069
|
});
|
|
7070
|
+
Object.defineProperty(exports, 'defineBatchNode', {
|
|
7071
|
+
enumerable: true,
|
|
7072
|
+
get: function () {
|
|
7073
|
+
return defineBatchNode;
|
|
7074
|
+
}
|
|
7075
|
+
});
|
|
7076
|
+
Object.defineProperty(exports, 'defineHumanApprovalNode', {
|
|
7077
|
+
enumerable: true,
|
|
7078
|
+
get: function () {
|
|
7079
|
+
return defineHumanApprovalNode;
|
|
7080
|
+
}
|
|
7081
|
+
});
|
|
7082
|
+
Object.defineProperty(exports, 'defineNode', {
|
|
7083
|
+
enumerable: true,
|
|
7084
|
+
get: function () {
|
|
7085
|
+
return defineNode;
|
|
7086
|
+
}
|
|
7087
|
+
});
|
|
6362
7088
|
Object.defineProperty(exports, 'emitPorts', {
|
|
6363
7089
|
enumerable: true,
|
|
6364
7090
|
get: function () {
|
|
@@ -6377,6 +7103,12 @@ Object.defineProperty(exports, 'getPersistedRuntimeTypeMetadata', {
|
|
|
6377
7103
|
return getPersistedRuntimeTypeMetadata;
|
|
6378
7104
|
}
|
|
6379
7105
|
});
|
|
7106
|
+
Object.defineProperty(exports, 'isHumanApprovalNode', {
|
|
7107
|
+
enumerable: true,
|
|
7108
|
+
get: function () {
|
|
7109
|
+
return isHumanApprovalNode;
|
|
7110
|
+
}
|
|
7111
|
+
});
|
|
6380
7112
|
Object.defineProperty(exports, 'isPortsEmission', {
|
|
6381
7113
|
enumerable: true,
|
|
6382
7114
|
get: function () {
|
|
@@ -6401,4 +7133,4 @@ Object.defineProperty(exports, 'tool', {
|
|
|
6401
7133
|
return tool;
|
|
6402
7134
|
}
|
|
6403
7135
|
});
|
|
6404
|
-
//# sourceMappingURL=runtime-
|
|
7136
|
+
//# sourceMappingURL=runtime-DjYXgOo0.cjs.map
|