@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,8 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as ConnectionNodeIdFactory, _ as CredentialUnboundError, a as injectable, g as SuspensionRequest, h as BINARY_DEFAULT_MAX_BYTES, v as NodeIterationIdFactory, w as resolveItemExprsForExecution, x as AgentConfigInspector, y as AgentConnectionNodeCollector } from "./di-D9Mv3kF3.js";
|
|
2
2
|
import { ZodError, z } from "zod";
|
|
3
3
|
import { ReadableStream } from "node:stream/web";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
|
|
6
|
+
//#region src/contracts/humanTaskStoreTypes.ts
|
|
7
|
+
const HumanTaskStoreToken = Symbol.for("codemation.core.HumanTaskStore");
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/contracts/hitlSeamTypes.ts
|
|
11
|
+
const HitlResumeTokenSignerToken = Symbol.for("codemation.core.HitlResumeTokenSigner");
|
|
12
|
+
const HitlTimeoutJobSchedulerToken = Symbol.for("codemation.core.HitlTimeoutJobScheduler");
|
|
13
|
+
/**
|
|
14
|
+
* Optional workspace ID injected into NodeSuspensionHandler in managed mode (T7 security fix).
|
|
15
|
+
* Allows the handler to stamp the workspaceId on each HumanTaskRecord so HitlCallbackHandler
|
|
16
|
+
* can assert workspace identity independently of the HMAC middleware.
|
|
17
|
+
* Not registered in non-managed mode; NodeSuspensionHandler defaults to null.
|
|
18
|
+
*/
|
|
19
|
+
const HitlWorkspaceIdToken = Symbol.for("codemation.core.HitlWorkspaceId");
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/authoring/DefinedNodeRegistry.ts
|
|
23
|
+
var DefinedNodeRegistry = class {
|
|
24
|
+
static definitions = /* @__PURE__ */ new Map();
|
|
25
|
+
static register(definition) {
|
|
26
|
+
this.definitions.set(definition.key, definition);
|
|
27
|
+
}
|
|
28
|
+
static resolve(key) {
|
|
29
|
+
return this.definitions.get(key);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
6
34
|
//#region src/runtime-types/persistedRuntimeTypeModelRegistry.ts
|
|
7
35
|
/** Shared metadata key used to attach persisted runtime-type information to decorated classes. */
|
|
8
36
|
const persistedRuntimeTypeMetadataKey = Symbol.for("codemation.core.persistedRuntimeTypeMetadata");
|
|
@@ -111,6 +139,305 @@ function chatModel(options = {}) {
|
|
|
111
139
|
return InjectableRuntimeDecoratorComposer.compose("chatModel", options, import.meta.url);
|
|
112
140
|
}
|
|
113
141
|
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/authoring/defineNode.types.ts
|
|
144
|
+
const definedNodeCredentialRequirementFactory = {
|
|
145
|
+
create(bindings) {
|
|
146
|
+
if (!bindings) return [];
|
|
147
|
+
return Object.entries(bindings).map(([slotKey, binding]) => {
|
|
148
|
+
if (typeof binding === "string" || this.isCredentialType(binding)) return {
|
|
149
|
+
slotKey,
|
|
150
|
+
label: this.humanize(slotKey),
|
|
151
|
+
acceptedTypes: [this.resolveTypeId(binding)]
|
|
152
|
+
};
|
|
153
|
+
const types = Array.isArray(binding.type) ? binding.type : [binding.type];
|
|
154
|
+
return {
|
|
155
|
+
slotKey,
|
|
156
|
+
label: binding.label ?? this.humanize(slotKey),
|
|
157
|
+
acceptedTypes: types.map((entry) => this.resolveTypeId(entry)),
|
|
158
|
+
optional: binding.optional,
|
|
159
|
+
helpText: binding.helpText,
|
|
160
|
+
helpUrl: binding.helpUrl
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
isCredentialType(value) {
|
|
165
|
+
return Boolean(value) && typeof value === "object" && "definition" in value && typeof value.definition?.typeId === "string";
|
|
166
|
+
},
|
|
167
|
+
resolveTypeId(type) {
|
|
168
|
+
return typeof type === "string" ? type : type.definition.typeId;
|
|
169
|
+
},
|
|
170
|
+
humanize(key) {
|
|
171
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_.]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (character) => character.toUpperCase());
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const definedNodeCredentialAccessorFactory = { create(bindings, ctx) {
|
|
175
|
+
if (!bindings) return {};
|
|
176
|
+
const entries = Object.keys(bindings).map((slotKey) => [slotKey, () => ctx.getCredential(slotKey)]);
|
|
177
|
+
return Object.fromEntries(entries);
|
|
178
|
+
} };
|
|
179
|
+
function defineNode(options) {
|
|
180
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
181
|
+
const DefinedNodeRuntime = class {
|
|
182
|
+
kind = "node";
|
|
183
|
+
outputPorts = ["main"];
|
|
184
|
+
inputSchema = options.inputSchema;
|
|
185
|
+
async execute(args) {
|
|
186
|
+
const ctx = args.ctx;
|
|
187
|
+
const context = {
|
|
188
|
+
config: ctx.config.config,
|
|
189
|
+
credentials: definedNodeCredentialAccessorFactory.create(options.credentials, ctx),
|
|
190
|
+
execution: ctx
|
|
191
|
+
};
|
|
192
|
+
const payload = {
|
|
193
|
+
input: args.input,
|
|
194
|
+
item: args.item,
|
|
195
|
+
itemIndex: args.itemIndex,
|
|
196
|
+
items: args.items,
|
|
197
|
+
ctx
|
|
198
|
+
};
|
|
199
|
+
return await options.execute(payload, context);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
node({ name: options.key })(DefinedNodeRuntime);
|
|
203
|
+
const DefinedRunnableNodeConfig = class {
|
|
204
|
+
kind = "node";
|
|
205
|
+
type = DefinedNodeRuntime;
|
|
206
|
+
icon = options.icon;
|
|
207
|
+
inputSchema = options.inputSchema;
|
|
208
|
+
keepBinaries = options.keepBinaries ?? false;
|
|
209
|
+
constructor(name, config, id) {
|
|
210
|
+
this.name = name;
|
|
211
|
+
this.id = id;
|
|
212
|
+
this.config = config;
|
|
213
|
+
}
|
|
214
|
+
config;
|
|
215
|
+
getCredentialRequirements() {
|
|
216
|
+
return credentialRequirements;
|
|
217
|
+
}
|
|
218
|
+
inspectorSummary() {
|
|
219
|
+
return options.inspectorSummary?.({ config: this.config });
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const definition = {
|
|
223
|
+
kind: "defined-node",
|
|
224
|
+
key: options.key,
|
|
225
|
+
title: options.title,
|
|
226
|
+
description: options.description,
|
|
227
|
+
create(config, name = options.title, id) {
|
|
228
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
229
|
+
},
|
|
230
|
+
register(context) {
|
|
231
|
+
context.registerNode(DefinedNodeRuntime);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
DefinedNodeRegistry.register(definition);
|
|
235
|
+
return definition;
|
|
236
|
+
}
|
|
237
|
+
function defineBatchNode(options) {
|
|
238
|
+
const credentialRequirements = definedNodeCredentialRequirementFactory.create(options.credentials);
|
|
239
|
+
const DefinedNodeRuntime = class {
|
|
240
|
+
kind = "node";
|
|
241
|
+
outputPorts = ["main"];
|
|
242
|
+
async execute(args) {
|
|
243
|
+
if (args.itemIndex !== args.items.length - 1) return [];
|
|
244
|
+
const ctx = args.ctx;
|
|
245
|
+
const context = {
|
|
246
|
+
config: ctx.config.config,
|
|
247
|
+
credentials: definedNodeCredentialAccessorFactory.create(options.credentials, ctx),
|
|
248
|
+
execution: ctx
|
|
249
|
+
};
|
|
250
|
+
return [...await options.run(args.items.map((item) => item.json), context)];
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
node({ name: options.key })(DefinedNodeRuntime);
|
|
254
|
+
const DefinedRunnableNodeConfig = class {
|
|
255
|
+
kind = "node";
|
|
256
|
+
type = DefinedNodeRuntime;
|
|
257
|
+
icon = options.icon;
|
|
258
|
+
constructor(name, config, id) {
|
|
259
|
+
this.name = name;
|
|
260
|
+
this.id = id;
|
|
261
|
+
this.config = config;
|
|
262
|
+
}
|
|
263
|
+
config;
|
|
264
|
+
getCredentialRequirements() {
|
|
265
|
+
return credentialRequirements;
|
|
266
|
+
}
|
|
267
|
+
inspectorSummary() {
|
|
268
|
+
return options.inspectorSummary?.({ config: this.config });
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const definition = {
|
|
272
|
+
kind: "defined-node",
|
|
273
|
+
key: options.key,
|
|
274
|
+
title: options.title,
|
|
275
|
+
description: options.description,
|
|
276
|
+
create(config, name = options.title, id) {
|
|
277
|
+
return new DefinedRunnableNodeConfig(name, config, id);
|
|
278
|
+
},
|
|
279
|
+
register(context) {
|
|
280
|
+
context.registerNode(DefinedNodeRuntime);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
DefinedNodeRegistry.register(definition);
|
|
284
|
+
return definition;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/authoring/defineHumanApprovalNode.types.ts
|
|
289
|
+
/**
|
|
290
|
+
* Returns `true` when `node` was created by {@link defineHumanApprovalNode}.
|
|
291
|
+
* Uses the `humanApprovalToolBehavior` typed field as the discriminant.
|
|
292
|
+
*/
|
|
293
|
+
function isHumanApprovalNode(node$1) {
|
|
294
|
+
return typeof node$1 === "object" && node$1 !== null && "humanApprovalToolBehavior" in node$1 && typeof node$1.humanApprovalToolBehavior === "object";
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Authoring helper that compiles a HITL approval channel down to a regular
|
|
298
|
+
* {@link defineNode}-backed node with `SuspensionRequest` semantics.
|
|
299
|
+
*
|
|
300
|
+
* **Fast-forward decision semantics:**
|
|
301
|
+
* - On the first `execute` call (no `ctx.resumeContext`): throws a `SuspensionRequest`
|
|
302
|
+
* that calls the author's `deliver`. The engine persists the suspension and continues.
|
|
303
|
+
* - On resume (`ctx.resumeContext` set): calls `onDecision`/`onTimeout` as appropriate,
|
|
304
|
+
* merges a `decision` key into `item.json`, and returns an item with the original
|
|
305
|
+
* `binary` map passed by reference (no copy).
|
|
306
|
+
*
|
|
307
|
+
* **Output shape per item:**
|
|
308
|
+
* ```ts
|
|
309
|
+
* // Input: { json: { invoiceId: 42 }, binary?: {...} }
|
|
310
|
+
* // Output: { json: { invoiceId: 42, decision: { status: "approved", actor, decidedAt } }, binary: <unchanged> }
|
|
311
|
+
* ```
|
|
312
|
+
* If `item.json` already has a `decision` key it is **overwritten**. Namespace as
|
|
313
|
+
* needed if your schema reserves that key for another purpose.
|
|
314
|
+
*
|
|
315
|
+
* **Predicate persistence:**
|
|
316
|
+
* The `approvedPredicate` function is NOT serialized to the suspension record (except
|
|
317
|
+
* as an audit-only string via `toString()`). On resume, the workflow definition is
|
|
318
|
+
* reloaded from code at process start and the predicate closure is rebuilt naturally.
|
|
319
|
+
* If a deploy ships a changed predicate between suspend and resume, the *new* predicate
|
|
320
|
+
* runs — document this in your runbook when the predicate carries business logic that
|
|
321
|
+
* may change across deploys.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* export const slackApprovalNode = defineHumanApprovalNode({
|
|
326
|
+
* key: "my-plugin.slackApproval",
|
|
327
|
+
* title: "Slack Approval",
|
|
328
|
+
* channel: "slack",
|
|
329
|
+
* configSchema: z.object({ channel: z.string(), message: z.string() }),
|
|
330
|
+
* decisionSchema: z.object({ approved: z.boolean(), note: z.string().optional() }),
|
|
331
|
+
*
|
|
332
|
+
* async deliver({ task, config, item }, ctx) {
|
|
333
|
+
* const ts = await postSlackMessage(config.channel, `Approve? <${task.resumeUrl}>`);
|
|
334
|
+
* return { channel: config.channel, ts };
|
|
335
|
+
* },
|
|
336
|
+
*
|
|
337
|
+
* async onDecision({ decision, actor, delivery }, ctx) {
|
|
338
|
+
* await updateSlackMessage(delivery.channel, delivery.ts, decision.approved ? "✅" : "❌");
|
|
339
|
+
* },
|
|
340
|
+
* });
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
function defineHumanApprovalNode(opts) {
|
|
344
|
+
const resolvedPredicate = resolveApprovedPredicate(opts.decisionSchema, opts.approvedPredicate);
|
|
345
|
+
const timeout = opts.defaultTimeout ?? "24h";
|
|
346
|
+
const onTimeout = opts.defaultOnTimeout ?? "halt";
|
|
347
|
+
const inner = defineNode({
|
|
348
|
+
key: opts.key,
|
|
349
|
+
title: opts.title,
|
|
350
|
+
description: opts.description,
|
|
351
|
+
icon: opts.icon,
|
|
352
|
+
configSchema: opts.configSchema,
|
|
353
|
+
inputSchema: opts.inputSchema,
|
|
354
|
+
credentials: opts.credentials,
|
|
355
|
+
inspectorSummary: opts.inspectorSummary ? ({ config }) => opts.inspectorSummary(config) : void 0,
|
|
356
|
+
async execute(args, { config, execution: ctx }) {
|
|
357
|
+
if (!ctx.resumeContext) {
|
|
358
|
+
const subject = buildSubject(opts.title, args.item, ctx);
|
|
359
|
+
throw new SuspensionRequest({
|
|
360
|
+
decisionSchema: opts.decisionSchema,
|
|
361
|
+
timeout,
|
|
362
|
+
onTimeout,
|
|
363
|
+
subject,
|
|
364
|
+
metadata: {
|
|
365
|
+
channel: opts.channel,
|
|
366
|
+
nodeKey: opts.key,
|
|
367
|
+
approvedPredicateSource: opts.approvedPredicate?.toString() ?? null
|
|
368
|
+
},
|
|
369
|
+
deliver: (handle) => opts.deliver({
|
|
370
|
+
task: handle,
|
|
371
|
+
config,
|
|
372
|
+
input: args.input,
|
|
373
|
+
item: args.item
|
|
374
|
+
}, ctx)
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return await handleResume(args.item, ctx.resumeContext, opts.decisionSchema, resolvedPredicate, opts.onDecision, opts.onTimeout, ctx);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return Object.assign(inner, { humanApprovalToolBehavior: { onRejected: "return" } });
|
|
381
|
+
}
|
|
382
|
+
function resolveApprovedPredicate(schema, predicate) {
|
|
383
|
+
if (predicate) return predicate;
|
|
384
|
+
const shape = schema.shape;
|
|
385
|
+
if (shape && typeof shape === "object" && "approved" in shape) return (d) => d.approved === true;
|
|
386
|
+
throw new Error("defineHumanApprovalNode: decisionSchema has no \"approved\" field and no approvedPredicate was provided. Either add { approved: z.boolean() } to the decision schema or supply approvedPredicate explicitly.");
|
|
387
|
+
}
|
|
388
|
+
function buildSubject(title, item, ctx) {
|
|
389
|
+
return {
|
|
390
|
+
title,
|
|
391
|
+
summary: "",
|
|
392
|
+
attributes: {
|
|
393
|
+
workflowId: ctx.workflowId,
|
|
394
|
+
nodeId: ctx.nodeId,
|
|
395
|
+
item: item.json
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function mergeDecision(item, decision) {
|
|
400
|
+
return {
|
|
401
|
+
json: {
|
|
402
|
+
...item.json,
|
|
403
|
+
decision
|
|
404
|
+
},
|
|
405
|
+
binary: item.binary,
|
|
406
|
+
meta: item.meta
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function handleResume(item, resumeContext, decisionSchema, resolvedPredicate, onDecision, onTimeoutCb, ctx) {
|
|
410
|
+
const { decision: dec, delivery, task } = resumeContext;
|
|
411
|
+
if (dec.kind === "timed_out" || dec.kind === "auto_accepted") {
|
|
412
|
+
const policy = dec.kind === "auto_accepted" ? "auto-accept" : "halt";
|
|
413
|
+
await onTimeoutCb?.({
|
|
414
|
+
task,
|
|
415
|
+
delivery,
|
|
416
|
+
item,
|
|
417
|
+
policy
|
|
418
|
+
}, ctx);
|
|
419
|
+
return mergeDecision(item, {
|
|
420
|
+
status: dec.kind === "auto_accepted" ? "auto-accepted" : "timed-out",
|
|
421
|
+
decidedAt: dec.at
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
const parsed = decisionSchema.parse(dec.value);
|
|
425
|
+
await onDecision?.({
|
|
426
|
+
decision: parsed,
|
|
427
|
+
actor: dec.actor,
|
|
428
|
+
task,
|
|
429
|
+
delivery,
|
|
430
|
+
item
|
|
431
|
+
}, ctx);
|
|
432
|
+
return mergeDecision(item, {
|
|
433
|
+
status: resolvedPredicate(parsed) ? "approved" : "rejected",
|
|
434
|
+
actor: dec.actor,
|
|
435
|
+
decidedAt: dec.decidedAt,
|
|
436
|
+
note: parsed.note,
|
|
437
|
+
payload: parsed
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
114
441
|
//#endregion
|
|
115
442
|
//#region src/workflow/dsl/WhenBuilder.ts
|
|
116
443
|
var WhenBuilder = class WhenBuilder {
|
|
@@ -237,6 +564,25 @@ var ChainCursor = class ChainCursor {
|
|
|
237
564
|
}
|
|
238
565
|
return new ChainCursor(this.wf, nextEndpoints);
|
|
239
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Chainable shorthand for `.then(node.create(config, metadata?.name, metadata?.nodeId))`.
|
|
569
|
+
*
|
|
570
|
+
* Signals to readers that this step suspends the run and waits for a human decision.
|
|
571
|
+
* Throws at workflow-build time if `node` was not created via `defineHumanApprovalNode`.
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```ts
|
|
575
|
+
* workflow
|
|
576
|
+
* .trigger(...)
|
|
577
|
+
* .humanApproval(inboxApproval, { title: "Approve?", body: "...", priority: "normal" })
|
|
578
|
+
* .then(nextStep.create(...))
|
|
579
|
+
* .build();
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
humanApproval(node$1, config, metadata) {
|
|
583
|
+
if (!isHumanApprovalNode(node$1)) throw new Error(`.humanApproval() requires a node created via defineHumanApprovalNode (got '${node$1.key ?? String(node$1)}').`);
|
|
584
|
+
return this.then(node$1.create(config, metadata?.name, metadata?.nodeId));
|
|
585
|
+
}
|
|
240
586
|
build() {
|
|
241
587
|
return this.wf.build();
|
|
242
588
|
}
|
|
@@ -544,6 +890,58 @@ var NodeEventPublisher = class {
|
|
|
544
890
|
}
|
|
545
891
|
};
|
|
546
892
|
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region src/binaries/boundedReadBinary.types.ts
|
|
895
|
+
/**
|
|
896
|
+
* Reads all bytes from an already-opened binary stream into a contiguous `Uint8Array`.
|
|
897
|
+
*
|
|
898
|
+
* Safety contract:
|
|
899
|
+
* - `attachment.size` is checked against `maxBytes` *before* any allocation (no OOM).
|
|
900
|
+
* - A single buffer of exactly `attachment.size` is pre-allocated; the stream fills it
|
|
901
|
+
* directly — no chunks array, no doubling.
|
|
902
|
+
* - A byte-count mismatch between the declared size and actual stream content is an error.
|
|
903
|
+
*
|
|
904
|
+
* This is the single canonical implementation; `ExecutionBinaryService.getBytes`,
|
|
905
|
+
* `getText`, and `getJson` all delegate here. The per-package `readBinaryBody` helpers
|
|
906
|
+
* in `core-nodes` and `core-nodes-ocr` have been removed in favour of this function.
|
|
907
|
+
*/
|
|
908
|
+
async function boundedReadBinary(result, attachment, maxBytes = BINARY_DEFAULT_MAX_BYTES) {
|
|
909
|
+
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.`);
|
|
910
|
+
const out = new Uint8Array(attachment.size);
|
|
911
|
+
const reader = result.body.getReader();
|
|
912
|
+
let offset = 0;
|
|
913
|
+
while (true) {
|
|
914
|
+
const { done, value } = await reader.read();
|
|
915
|
+
if (done) break;
|
|
916
|
+
if (!value) continue;
|
|
917
|
+
if (offset + value.byteLength > out.byteLength) throw new Error(`Binary stream produced more bytes than the attachment's declared size (${attachment.size}).`);
|
|
918
|
+
out.set(value, offset);
|
|
919
|
+
offset += value.byteLength;
|
|
920
|
+
}
|
|
921
|
+
if (offset !== out.byteLength) throw new Error(`Binary stream produced ${offset} bytes but attachment declared size ${attachment.size}.`);
|
|
922
|
+
return out;
|
|
923
|
+
}
|
|
924
|
+
/** Shared implementation of `getBytes` used by both binary-service classes. */
|
|
925
|
+
async function readBinaryAsBytes(storage, attachment, maxBytes) {
|
|
926
|
+
const result = await storage.openReadStream(attachment.storageKey);
|
|
927
|
+
if (!result) throw new Error("Binary attachment stream is unavailable.");
|
|
928
|
+
return boundedReadBinary(result, attachment, maxBytes);
|
|
929
|
+
}
|
|
930
|
+
/** Shared implementation of `getText` used by both binary-service classes. */
|
|
931
|
+
async function readBinaryAsText(storage, attachment, maxBytes) {
|
|
932
|
+
const bytes = await readBinaryAsBytes(storage, attachment, maxBytes);
|
|
933
|
+
return new TextDecoder().decode(bytes);
|
|
934
|
+
}
|
|
935
|
+
/** Shared implementation of `getJson` used by both binary-service classes. */
|
|
936
|
+
async function readBinaryAsJson(storage, attachment, maxBytes) {
|
|
937
|
+
const text = await readBinaryAsText(storage, attachment, maxBytes);
|
|
938
|
+
try {
|
|
939
|
+
return JSON.parse(text);
|
|
940
|
+
} catch (cause) {
|
|
941
|
+
throw new SyntaxError(`Binary attachment at storage key "${attachment.storageKey}" is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
547
945
|
//#endregion
|
|
548
946
|
//#region src/binaries/DefaultNodeBinaryAttachmentServiceFactory.ts
|
|
549
947
|
var DefaultNodeBinaryAttachmentService = class DefaultNodeBinaryAttachmentService {
|
|
@@ -594,6 +992,15 @@ var DefaultNodeBinaryAttachmentService = class DefaultNodeBinaryAttachmentServic
|
|
|
594
992
|
async openReadStream(attachment) {
|
|
595
993
|
return await this.storage.openReadStream(attachment.storageKey);
|
|
596
994
|
}
|
|
995
|
+
async getBytes(attachment, maxBytes) {
|
|
996
|
+
return readBinaryAsBytes(this.storage, attachment, maxBytes);
|
|
997
|
+
}
|
|
998
|
+
async getText(attachment, maxBytes) {
|
|
999
|
+
return readBinaryAsText(this.storage, attachment, maxBytes);
|
|
1000
|
+
}
|
|
1001
|
+
async getJson(attachment, maxBytes) {
|
|
1002
|
+
return readBinaryAsJson(this.storage, attachment, maxBytes);
|
|
1003
|
+
}
|
|
597
1004
|
createAttachmentId() {
|
|
598
1005
|
return DefaultNodeBinaryAttachmentService.createAttachmentIdValue(`${this.activationId}-${this.now().getTime()}`);
|
|
599
1006
|
}
|
|
@@ -657,8 +1064,24 @@ var DefaultExecutionBinaryService = class {
|
|
|
657
1064
|
forNode(args) {
|
|
658
1065
|
return new DefaultNodeBinaryAttachmentService(this.storage, this.workflowId, this.runId, args.nodeId, args.activationId, this.now);
|
|
659
1066
|
}
|
|
660
|
-
|
|
661
|
-
return
|
|
1067
|
+
openReadStream(attachment) {
|
|
1068
|
+
return this.storage.openReadStream(attachment.storageKey);
|
|
1069
|
+
}
|
|
1070
|
+
async getBytes(attachment, maxBytes) {
|
|
1071
|
+
const stream = await this.openReadStream(attachment);
|
|
1072
|
+
if (!stream) throw new Error("Binary attachment stream is unavailable.");
|
|
1073
|
+
return boundedReadBinary(stream, attachment, maxBytes);
|
|
1074
|
+
}
|
|
1075
|
+
async getText(attachment, maxBytes) {
|
|
1076
|
+
return new TextDecoder().decode(await this.getBytes(attachment, maxBytes));
|
|
1077
|
+
}
|
|
1078
|
+
async getJson(attachment, maxBytes) {
|
|
1079
|
+
const text = await this.getText(attachment, maxBytes);
|
|
1080
|
+
try {
|
|
1081
|
+
return JSON.parse(text);
|
|
1082
|
+
} catch (cause) {
|
|
1083
|
+
throw new SyntaxError(`Binary attachment at storage key "${attachment.storageKey}" is not valid JSON: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
1084
|
+
}
|
|
662
1085
|
}
|
|
663
1086
|
};
|
|
664
1087
|
|
|
@@ -703,7 +1126,7 @@ var NodeExecutionSnapshotFactory = class {
|
|
|
703
1126
|
nodeId: args.nodeId,
|
|
704
1127
|
activationId: args.activationId,
|
|
705
1128
|
parent: args.parent,
|
|
706
|
-
status: "completed",
|
|
1129
|
+
status: args.hitlStatus ?? "completed",
|
|
707
1130
|
queuedAt: args.previous?.queuedAt,
|
|
708
1131
|
startedAt,
|
|
709
1132
|
finishedAt: args.finishedAt,
|
|
@@ -830,7 +1253,9 @@ var ActivationEnqueueService = class {
|
|
|
830
1253
|
nodeSnapshotsByNodeId: {
|
|
831
1254
|
...args.previousNodeSnapshotsByNodeId,
|
|
832
1255
|
[args.request.nodeId]: queuedSnapshot
|
|
833
|
-
}
|
|
1256
|
+
},
|
|
1257
|
+
...args.suspension !== void 0 ? { suspension: args.suspension } : {},
|
|
1258
|
+
...args.pendingResume !== void 0 ? { pendingResume: args.pendingResume } : {}
|
|
834
1259
|
});
|
|
835
1260
|
await this.dispatchPreparedActivation(preparedDispatch);
|
|
836
1261
|
return {
|
|
@@ -1297,6 +1722,16 @@ var CodemationTelemetryAttributeNames = class {
|
|
|
1297
1722
|
static mcpServerId = "mcp.server_id";
|
|
1298
1723
|
/** MCP tool name on spans created for callTool invocations. */
|
|
1299
1724
|
static mcpToolName = "mcp.tool_name";
|
|
1725
|
+
/** Terminal node-execution status (e.g. `"hitl-approved"`, `"hitl-rejected"`) on HITL outcome spans. */
|
|
1726
|
+
static nodeExecutionStatus = "codemation.node.execution_status";
|
|
1727
|
+
/** Populated on run-halted spans; discriminates the halt reason (e.g. `"hitl-rejected"`). */
|
|
1728
|
+
static runHaltReason = "codemation.run.halt_reason";
|
|
1729
|
+
/** Human task ID on `hitl.task.*` span events. */
|
|
1730
|
+
static hitlTaskId = "codemation.hitl.task_id";
|
|
1731
|
+
/** HITL channel name (e.g. `"inbox"`, `"control-plane-inbox"`) on `hitl.task.*` span events. */
|
|
1732
|
+
static hitlChannel = "codemation.hitl.channel";
|
|
1733
|
+
/** Decision outcome (e.g. `"approved"`, `"rejected"`) on `hitl.task.decided` span events. */
|
|
1734
|
+
static hitlDecisionStatus = "codemation.hitl.decision_status";
|
|
1300
1735
|
};
|
|
1301
1736
|
|
|
1302
1737
|
//#endregion
|
|
@@ -1395,12 +1830,13 @@ var ExecutionTelemetryCostTrackingDecoratorFactory = class {
|
|
|
1395
1830
|
//#region src/execution/DefaultExecutionContextFactory.ts
|
|
1396
1831
|
var DefaultExecutionContextFactory = class {
|
|
1397
1832
|
telemetryDecoratorFactory = new ExecutionTelemetryCostTrackingDecoratorFactory();
|
|
1398
|
-
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections) {
|
|
1833
|
+
constructor(binaryStorage = new UnavailableBinaryStorage(), telemetryFactory = new NoOpExecutionTelemetryFactory(), costTrackingFactory = new NoOpCostTrackingTelemetryFactory(), currentDate = () => /* @__PURE__ */ new Date(), collections, nodeResolver) {
|
|
1399
1834
|
this.binaryStorage = binaryStorage;
|
|
1400
1835
|
this.telemetryFactory = telemetryFactory;
|
|
1401
1836
|
this.costTrackingFactory = costTrackingFactory;
|
|
1402
1837
|
this.currentDate = currentDate;
|
|
1403
1838
|
this.collections = collections;
|
|
1839
|
+
this.nodeResolver = nodeResolver;
|
|
1404
1840
|
}
|
|
1405
1841
|
create(args) {
|
|
1406
1842
|
const baseTelemetry = args.telemetry ?? this.telemetryFactory.create({
|
|
@@ -1427,7 +1863,11 @@ var DefaultExecutionContextFactory = class {
|
|
|
1427
1863
|
binary: new DefaultExecutionBinaryService(this.binaryStorage, args.workflowId, args.runId, this.currentDate),
|
|
1428
1864
|
getCredential: args.getCredential,
|
|
1429
1865
|
testContext: args.testContext,
|
|
1430
|
-
collections: this.collections
|
|
1866
|
+
collections: this.collections,
|
|
1867
|
+
resolve: (token) => {
|
|
1868
|
+
if (!this.nodeResolver) throw new Error("ExecutionContext.resolve() is not available: no NodeResolver was provided to DefaultExecutionContextFactory.");
|
|
1869
|
+
return this.nodeResolver.resolve(token);
|
|
1870
|
+
}
|
|
1431
1871
|
};
|
|
1432
1872
|
}
|
|
1433
1873
|
};
|
|
@@ -1769,6 +2209,27 @@ var NodeActivationRequestComposer = class {
|
|
|
1769
2209
|
}
|
|
1770
2210
|
};
|
|
1771
2211
|
|
|
2212
|
+
//#endregion
|
|
2213
|
+
//#region src/execution/RunSuspendedError.ts
|
|
2214
|
+
/**
|
|
2215
|
+
* Internal sentinel thrown by {@link NodeSuspensionHandler} after persisting a suspension
|
|
2216
|
+
* entry. `NodeExecutionRequestHandlerService` catches this specifically and returns cleanly —
|
|
2217
|
+
* no continuation call, preventing `resumeFromNodeResult` / `resumeFromNodeError` from
|
|
2218
|
+
* overwriting the `"suspended"` run status.
|
|
2219
|
+
*
|
|
2220
|
+
* The `Error` suffix satisfies the ESLint `no-manual-di-new` allowlist. This is NOT a
|
|
2221
|
+
* user-facing error — it is an engine-internal control-flow primitive and should NOT be
|
|
2222
|
+
* exported from the public barrel.
|
|
2223
|
+
*/
|
|
2224
|
+
var RunSuspendedError = class extends Error {
|
|
2225
|
+
constructor(runId, taskId) {
|
|
2226
|
+
super(`RunSuspendedError: run ${runId} suspended on task ${taskId}`);
|
|
2227
|
+
this.runId = runId;
|
|
2228
|
+
this.taskId = taskId;
|
|
2229
|
+
this.name = "RunSuspendedError";
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
|
|
1772
2233
|
//#endregion
|
|
1773
2234
|
//#region src/execution/NodeExecutor.ts
|
|
1774
2235
|
var NodeExecutor = class {
|
|
@@ -1776,9 +2237,11 @@ var NodeExecutor = class {
|
|
|
1776
2237
|
outputNormalizer = new NodeOutputNormalizer();
|
|
1777
2238
|
itemExprResolver;
|
|
1778
2239
|
outputBehaviorResolver;
|
|
1779
|
-
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver) {
|
|
2240
|
+
constructor(nodeInstanceFactory, retryRunner, itemExprResolver, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
1780
2241
|
this.nodeInstanceFactory = nodeInstanceFactory;
|
|
1781
2242
|
this.retryRunner = retryRunner;
|
|
2243
|
+
this.suspensionHandler = suspensionHandler;
|
|
2244
|
+
this.loadRunState = loadRunState;
|
|
1782
2245
|
this.itemExprResolver = itemExprResolver ?? new ItemExprResolver();
|
|
1783
2246
|
this.outputBehaviorResolver = outputBehaviorResolver ?? new RunnableOutputBehaviorResolver();
|
|
1784
2247
|
}
|
|
@@ -1878,6 +2341,7 @@ var NodeExecutor = class {
|
|
|
1878
2341
|
});
|
|
1879
2342
|
}
|
|
1880
2343
|
const byPort = {};
|
|
2344
|
+
let hasSuspension = false;
|
|
1881
2345
|
for (let i = 0; i < inputBatch.length; i++) {
|
|
1882
2346
|
const item = inputBatch[i];
|
|
1883
2347
|
this.assertItemJsonNotTopLevelArray(request.nodeId, item);
|
|
@@ -1896,7 +2360,28 @@ var NodeExecutor = class {
|
|
|
1896
2360
|
items: inputBatch,
|
|
1897
2361
|
ctx: iterationCtx
|
|
1898
2362
|
};
|
|
1899
|
-
|
|
2363
|
+
let raw;
|
|
2364
|
+
try {
|
|
2365
|
+
raw = await Promise.resolve(node$1.execute(args));
|
|
2366
|
+
} catch (e) {
|
|
2367
|
+
if (e instanceof SuspensionRequest || e instanceof Error && e.name === "SuspensionRequest" && typeof e.request === "object") {
|
|
2368
|
+
if (!this.suspensionHandler || !this.loadRunState) throw new Error(`Node ${request.nodeId} threw SuspensionRequest but this NodeExecutor has no suspensionHandler configured.`, { cause: e });
|
|
2369
|
+
const state = await this.loadRunState(request.runId);
|
|
2370
|
+
if (!state) throw new Error(`NodeExecutor: run state not found for runId ${request.runId} during suspension`, { cause: e });
|
|
2371
|
+
await this.suspensionHandler.handle({
|
|
2372
|
+
runId: request.runId,
|
|
2373
|
+
nodeId: request.nodeId,
|
|
2374
|
+
activationId: request.activationId,
|
|
2375
|
+
itemIndex: i,
|
|
2376
|
+
suspensionRequest: e,
|
|
2377
|
+
state,
|
|
2378
|
+
telemetry: iterationCtx.telemetry
|
|
2379
|
+
});
|
|
2380
|
+
hasSuspension = true;
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
throw e;
|
|
2384
|
+
}
|
|
1900
2385
|
const normalized = this.outputNormalizer.normalizeExecuteResult({
|
|
1901
2386
|
baseItem: item,
|
|
1902
2387
|
raw,
|
|
@@ -1909,6 +2394,7 @@ var NodeExecutor = class {
|
|
|
1909
2394
|
byPort[port] = list;
|
|
1910
2395
|
}
|
|
1911
2396
|
}
|
|
2397
|
+
if (hasSuspension) throw new RunSuspendedError(request.runId, "unknown");
|
|
1912
2398
|
return byPort;
|
|
1913
2399
|
}
|
|
1914
2400
|
/** Use resolver ctx only when {@link NodeExecutionContext.config} is non-nullish. */
|
|
@@ -1935,8 +2421,8 @@ var NodeExecutor = class {
|
|
|
1935
2421
|
//#endregion
|
|
1936
2422
|
//#region src/execution/NodeExecutorFactory.ts
|
|
1937
2423
|
var NodeExecutorFactory = class {
|
|
1938
|
-
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver) {
|
|
1939
|
-
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver);
|
|
2424
|
+
create(workflowNodeInstanceFactory, retryRunner, outputBehaviorResolver, suspensionHandler, loadRunState) {
|
|
2425
|
+
return new NodeExecutor(workflowNodeInstanceFactory, retryRunner, void 0, outputBehaviorResolver, suspensionHandler, loadRunState);
|
|
1940
2426
|
}
|
|
1941
2427
|
};
|
|
1942
2428
|
|
|
@@ -2529,6 +3015,7 @@ var PersistedRunStateTerminalBuilder = class {
|
|
|
2529
3015
|
...args.state,
|
|
2530
3016
|
engineCounters: args.engineCounters,
|
|
2531
3017
|
status: args.status,
|
|
3018
|
+
reason: args.reason,
|
|
2532
3019
|
pending: void 0,
|
|
2533
3020
|
queue: args.queue,
|
|
2534
3021
|
outputsByNode: args.outputsByNode,
|
|
@@ -2811,6 +3298,7 @@ var RunContinuationService = class {
|
|
|
2811
3298
|
});
|
|
2812
3299
|
data.setOutputs(args.nodeId, args.outputs);
|
|
2813
3300
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3301
|
+
const hitlResolution = this.resolveHitlStatus(args.outputs);
|
|
2814
3302
|
const completedSnapshot = this.semantics.createFinishedSnapshot({
|
|
2815
3303
|
workflow: wf,
|
|
2816
3304
|
previous: state.nodeSnapshotsByNodeId?.[args.nodeId],
|
|
@@ -2821,8 +3309,41 @@ var RunContinuationService = class {
|
|
|
2821
3309
|
parent: state.parent,
|
|
2822
3310
|
finishedAt: completedAt,
|
|
2823
3311
|
inputsByPort: pendingExecution.inputsByPort,
|
|
2824
|
-
outputs: args.outputs
|
|
3312
|
+
outputs: args.outputs,
|
|
3313
|
+
hitlStatus: hitlResolution?.nodeStatus
|
|
2825
3314
|
});
|
|
3315
|
+
if (hitlResolution?.halt) {
|
|
3316
|
+
const haltedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
|
|
3317
|
+
state,
|
|
3318
|
+
engineCounters: state.engineCounters ?? { completedNodeActivations: 0 },
|
|
3319
|
+
status: "halted",
|
|
3320
|
+
reason: hitlResolution.reason,
|
|
3321
|
+
queue: [],
|
|
3322
|
+
outputsByNode: data.dump(),
|
|
3323
|
+
nodeSnapshotsByNodeId: {
|
|
3324
|
+
...state.nodeSnapshotsByNodeId ?? {},
|
|
3325
|
+
[args.nodeId]: completedSnapshot
|
|
3326
|
+
},
|
|
3327
|
+
finishedAtIso: completedAt
|
|
3328
|
+
});
|
|
3329
|
+
await this.workflowExecutionRepository.save(haltedState);
|
|
3330
|
+
await this.nodeEventPublisher.publish("nodeCompleted", completedSnapshot);
|
|
3331
|
+
await this.terminalPersistence.maybeDeleteAfterTerminalState({
|
|
3332
|
+
workflow: wf,
|
|
3333
|
+
state: haltedState,
|
|
3334
|
+
finalStatus: "failed",
|
|
3335
|
+
finishedAt: completedAt
|
|
3336
|
+
});
|
|
3337
|
+
const result = {
|
|
3338
|
+
runId: state.runId,
|
|
3339
|
+
workflowId: state.workflowId,
|
|
3340
|
+
startedAt: state.startedAt,
|
|
3341
|
+
status: "halted",
|
|
3342
|
+
reason: hitlResolution.reason
|
|
3343
|
+
};
|
|
3344
|
+
this.waiters.resolveRunCompletion(result);
|
|
3345
|
+
return result;
|
|
3346
|
+
}
|
|
2826
3347
|
const completedActivations = (state.engineCounters?.completedNodeActivations ?? 0) + 1;
|
|
2827
3348
|
const engineCounters = { completedNodeActivations: completedActivations };
|
|
2828
3349
|
const maxNodeActivations = state.executionOptions?.maxNodeActivations ?? this.executionLimitsPolicy.createRootExecutionOptions().maxNodeActivations;
|
|
@@ -3123,13 +3644,114 @@ var RunContinuationService = class {
|
|
|
3123
3644
|
status: "failed",
|
|
3124
3645
|
error: { message: "Run failed" }
|
|
3125
3646
|
};
|
|
3647
|
+
if (existing?.status === "halted") return {
|
|
3648
|
+
runId: existing.runId,
|
|
3649
|
+
workflowId: existing.workflowId,
|
|
3650
|
+
startedAt: existing.startedAt,
|
|
3651
|
+
status: "halted",
|
|
3652
|
+
reason: existing.reason ?? "hitl-rejected"
|
|
3653
|
+
};
|
|
3126
3654
|
const result = await this.waiters.waitForCompletion(runId);
|
|
3127
|
-
if (result.status !== "completed" && result.status !== "failed") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3655
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") throw new Error(`Unexpected run completion status: ${result.status}`);
|
|
3128
3656
|
return result;
|
|
3129
3657
|
}
|
|
3130
3658
|
async waitForWebhookResponse(runId) {
|
|
3131
3659
|
return await this.waiters.waitForWebhookResponse(runId);
|
|
3132
3660
|
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Re-activate a previously suspended run item with a human decision.
|
|
3663
|
+
*
|
|
3664
|
+
* Called by the HITL resume endpoint. This method:
|
|
3665
|
+
* 1. Loads `PersistedRunState` and locates the suspension entry by `taskId`.
|
|
3666
|
+
* 2. Removes the entry from the `suspension` array; if empty, run stays `"suspended"` until
|
|
3667
|
+
* enqueue flips it to `"pending"`.
|
|
3668
|
+
* 3. Writes `pendingResume` onto the state so `NodeExecutionRequestHandlerService` can
|
|
3669
|
+
* splice `resumeContext` into the node's execution context.
|
|
3670
|
+
* 4. Reconstructs the original input from `outputsByNode` of the upstream node and
|
|
3671
|
+
* enqueues a new activation via `activationEnqueueService`.
|
|
3672
|
+
*
|
|
3673
|
+
* @throws if the run is not found, not suspended, or the `taskId` is unknown.
|
|
3674
|
+
*/
|
|
3675
|
+
async resumeRun(args) {
|
|
3676
|
+
const state = await this.workflowExecutionRepository.load(args.runId);
|
|
3677
|
+
if (!state) throw new Error(`Unknown runId: ${args.runId}`);
|
|
3678
|
+
if (state.status !== "suspended") throw new Error(`Run ${args.runId} is not suspended (status: ${state.status})`);
|
|
3679
|
+
const suspensionEntry = (state.suspension ?? []).find((s) => s.taskId === args.taskId);
|
|
3680
|
+
if (!suspensionEntry) throw new Error(`No suspension entry with taskId "${args.taskId}" found on run ${args.runId}`);
|
|
3681
|
+
const wf = this.resolvePersistedWorkflow(state);
|
|
3682
|
+
if (!wf) throw new Error(`Unknown workflowId: ${state.workflowId}`);
|
|
3683
|
+
const { topology, planner } = this.planningFactory.create(wf);
|
|
3684
|
+
const def = topology.defsById.get(suspensionEntry.nodeId);
|
|
3685
|
+
if (!def || def.kind !== "node") throw new Error(`Node ${suspensionEntry.nodeId} is not a runnable node`);
|
|
3686
|
+
const data = this.runDataFactory.create(state.outputsByNode);
|
|
3687
|
+
const limits = this.resolveEngineLimitsFromState(state);
|
|
3688
|
+
const base = this.runExecutionContextFactory.create({
|
|
3689
|
+
runId: state.runId,
|
|
3690
|
+
workflowId: state.workflowId,
|
|
3691
|
+
nodeId: suspensionEntry.nodeId,
|
|
3692
|
+
parent: state.parent,
|
|
3693
|
+
policySnapshot: state.policySnapshot,
|
|
3694
|
+
subworkflowDepth: state.executionOptions?.subworkflowDepth ?? 0,
|
|
3695
|
+
engineMaxNodeActivations: limits.engineMaxNodeActivations,
|
|
3696
|
+
engineMaxSubworkflowDepth: limits.engineMaxSubworkflowDepth,
|
|
3697
|
+
data,
|
|
3698
|
+
nodeState: this.nodeStatePublisherFactory.create(state.runId, state.workflowId, state.parent),
|
|
3699
|
+
testContext: state.executionOptions?.testContext
|
|
3700
|
+
});
|
|
3701
|
+
const parentEdges = wf.edges.filter((e) => e.to.nodeId === suspensionEntry.nodeId);
|
|
3702
|
+
const parentNodeId = parentEdges[0]?.from.nodeId;
|
|
3703
|
+
const parentOutputPort = parentEdges[0]?.from.output ?? "main";
|
|
3704
|
+
const allParentItems = parentNodeId ? data.getOutputItems(parentNodeId, parentOutputPort) ?? [] : [];
|
|
3705
|
+
const resumeInput = allParentItems.length > suspensionEntry.itemIndex ? [allParentItems[suspensionEntry.itemIndex]] : allParentItems;
|
|
3706
|
+
const newActivationId = this.activationIdFactory.makeActivationId();
|
|
3707
|
+
const pendingResume = {
|
|
3708
|
+
activationId: newActivationId,
|
|
3709
|
+
nodeId: suspensionEntry.nodeId,
|
|
3710
|
+
resumeContext: args.resumeContext
|
|
3711
|
+
};
|
|
3712
|
+
const remainingSuspensions = (state.suspension ?? []).filter((s) => s.taskId !== args.taskId);
|
|
3713
|
+
const baseWithResume = {
|
|
3714
|
+
...base,
|
|
3715
|
+
resumeContext: args.resumeContext
|
|
3716
|
+
};
|
|
3717
|
+
const batchId = `resume_${newActivationId}`;
|
|
3718
|
+
const request = this.nodeActivationRequestComposer.createSingleFromDefinitionWithActivation({
|
|
3719
|
+
activationId: newActivationId,
|
|
3720
|
+
runId: state.runId,
|
|
3721
|
+
workflowId: state.workflowId,
|
|
3722
|
+
parent: state.parent,
|
|
3723
|
+
executionOptions: state.executionOptions,
|
|
3724
|
+
base: baseWithResume,
|
|
3725
|
+
data,
|
|
3726
|
+
definition: {
|
|
3727
|
+
id: suspensionEntry.nodeId,
|
|
3728
|
+
config: def.config
|
|
3729
|
+
},
|
|
3730
|
+
batchId,
|
|
3731
|
+
input: resumeInput
|
|
3732
|
+
});
|
|
3733
|
+
const { result, queuedSnapshot } = await this.activationEnqueueService.enqueueActivationWithSnapshot({
|
|
3734
|
+
runId: state.runId,
|
|
3735
|
+
workflowId: state.workflowId,
|
|
3736
|
+
startedAt: state.startedAt,
|
|
3737
|
+
parent: state.parent,
|
|
3738
|
+
executionOptions: state.executionOptions,
|
|
3739
|
+
control: state.control,
|
|
3740
|
+
workflowSnapshot: state.workflowSnapshot,
|
|
3741
|
+
mutableState: state.mutableState,
|
|
3742
|
+
policySnapshot: state.policySnapshot,
|
|
3743
|
+
pendingQueue: [],
|
|
3744
|
+
request,
|
|
3745
|
+
previousNodeSnapshotsByNodeId: state.nodeSnapshotsByNodeId ?? {},
|
|
3746
|
+
planner,
|
|
3747
|
+
engineCounters: state.engineCounters,
|
|
3748
|
+
connectionInvocations: state.connectionInvocations ?? [],
|
|
3749
|
+
suspension: remainingSuspensions.length > 0 ? remainingSuspensions : void 0,
|
|
3750
|
+
pendingResume
|
|
3751
|
+
});
|
|
3752
|
+
await this.nodeEventPublisher.publish("nodeQueued", queuedSnapshot);
|
|
3753
|
+
return result;
|
|
3754
|
+
}
|
|
3133
3755
|
async resumeFromWebhookControl(args) {
|
|
3134
3756
|
const data = this.runDataFactory.create(args.state.outputsByNode);
|
|
3135
3757
|
const { topology, planner } = this.planningFactory.create(args.workflow);
|
|
@@ -3516,6 +4138,34 @@ var RunContinuationService = class {
|
|
|
3516
4138
|
this.waiters.resolveRunCompletion(result);
|
|
3517
4139
|
return result;
|
|
3518
4140
|
}
|
|
4141
|
+
/**
|
|
4142
|
+
* Inspects node outputs for a `decision.status` written by `defineHumanApprovalNode`.
|
|
4143
|
+
* Returns the first-class HITL node status and halt classification, or `undefined`
|
|
4144
|
+
* when the node is not a HITL approval node.
|
|
4145
|
+
*/
|
|
4146
|
+
resolveHitlStatus(outputs) {
|
|
4147
|
+
const firstItem = outputs?.main?.[0];
|
|
4148
|
+
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;
|
|
4149
|
+
if (!decisionStatus) return void 0;
|
|
4150
|
+
if (decisionStatus === "approved") return {
|
|
4151
|
+
nodeStatus: "hitl-approved",
|
|
4152
|
+
halt: false
|
|
4153
|
+
};
|
|
4154
|
+
if (decisionStatus === "auto-accepted") return {
|
|
4155
|
+
nodeStatus: "hitl-auto-accepted",
|
|
4156
|
+
halt: false
|
|
4157
|
+
};
|
|
4158
|
+
if (decisionStatus === "rejected") return {
|
|
4159
|
+
nodeStatus: "hitl-rejected",
|
|
4160
|
+
halt: true,
|
|
4161
|
+
reason: "hitl-rejected"
|
|
4162
|
+
};
|
|
4163
|
+
if (decisionStatus === "timed-out") return {
|
|
4164
|
+
nodeStatus: "hitl-timeout",
|
|
4165
|
+
halt: true,
|
|
4166
|
+
reason: "hitl-timeout"
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
3519
4169
|
formatNodeLabel(args) {
|
|
3520
4170
|
const tokenName = typeof args.definition?.type === "function" ? args.definition.type.name : "Node";
|
|
3521
4171
|
return args.definition?.name ? `"${args.definition.name}" (${tokenName}:${args.nodeId})` : `${tokenName}:${args.nodeId}`;
|
|
@@ -4868,13 +5518,19 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4868
5518
|
const portKeys = Object.keys(inputsByPort);
|
|
4869
5519
|
const kind = portKeys.length === 1 && portKeys[0] === "in" ? "single" : "multi";
|
|
4870
5520
|
const batchId = pendingExecution.batchId ?? "batch_1";
|
|
5521
|
+
const pendingResume = state.pendingResume;
|
|
5522
|
+
const resumeContext = pendingResume?.activationId === request.activationId && pendingResume?.nodeId === request.nodeId ? pendingResume.resumeContext : void 0;
|
|
5523
|
+
const baseWithResume = resumeContext != null ? {
|
|
5524
|
+
...base,
|
|
5525
|
+
resumeContext
|
|
5526
|
+
} : base;
|
|
4871
5527
|
const activationRequest = kind === "multi" ? this.nodeActivationRequestComposer.createMultiFromDefinitionWithActivation({
|
|
4872
5528
|
activationId: request.activationId,
|
|
4873
5529
|
runId: request.runId,
|
|
4874
5530
|
workflowId: request.workflowId,
|
|
4875
5531
|
parent: resolvedParent,
|
|
4876
5532
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4877
|
-
base,
|
|
5533
|
+
base: baseWithResume,
|
|
4878
5534
|
data,
|
|
4879
5535
|
definition: {
|
|
4880
5536
|
id: definition.id,
|
|
@@ -4888,7 +5544,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4888
5544
|
workflowId: request.workflowId,
|
|
4889
5545
|
parent: resolvedParent,
|
|
4890
5546
|
executionOptions: request.executionOptions ?? state.executionOptions,
|
|
4891
|
-
base,
|
|
5547
|
+
base: baseWithResume,
|
|
4892
5548
|
data,
|
|
4893
5549
|
definition: {
|
|
4894
5550
|
id: definition.id,
|
|
@@ -4897,6 +5553,13 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4897
5553
|
batchId,
|
|
4898
5554
|
input: inputsByPort.in ?? request.input ?? []
|
|
4899
5555
|
});
|
|
5556
|
+
if (resumeContext != null) {
|
|
5557
|
+
const clearedState = await this.workflowExecutionRepository.load(request.runId);
|
|
5558
|
+
if (clearedState?.pendingResume?.activationId === request.activationId) await this.workflowExecutionRepository.save({
|
|
5559
|
+
...clearedState,
|
|
5560
|
+
pendingResume: void 0
|
|
5561
|
+
});
|
|
5562
|
+
}
|
|
4900
5563
|
await this.continuation.markNodeRunning({
|
|
4901
5564
|
runId: activationRequest.runId,
|
|
4902
5565
|
activationId: activationRequest.activationId,
|
|
@@ -4907,6 +5570,7 @@ var NodeExecutionRequestHandlerService = class {
|
|
|
4907
5570
|
try {
|
|
4908
5571
|
outputs = await this.nodeExecutor.execute(activationRequest);
|
|
4909
5572
|
} catch (error) {
|
|
5573
|
+
if (error instanceof RunSuspendedError) return;
|
|
4910
5574
|
await this.resumeAfterExecutionError(activationRequest, this.asError(error));
|
|
4911
5575
|
return;
|
|
4912
5576
|
}
|
|
@@ -5467,7 +6131,7 @@ var EngineWaiters = class {
|
|
|
5467
6131
|
});
|
|
5468
6132
|
}
|
|
5469
6133
|
resolveRunCompletion(result) {
|
|
5470
|
-
if (result.status !== "completed" && result.status !== "failed") return;
|
|
6134
|
+
if (result.status !== "completed" && result.status !== "failed" && result.status !== "halted") return;
|
|
5471
6135
|
const list = this.completionWaiters.get(result.runId);
|
|
5472
6136
|
if (!list || list.length === 0) return;
|
|
5473
6137
|
this.completionWaiters.delete(result.runId);
|
|
@@ -5561,6 +6225,13 @@ var Engine = class {
|
|
|
5561
6225
|
async waitForWebhookResponse(runId) {
|
|
5562
6226
|
return await this.deps.runContinuationService.waitForWebhookResponse(runId);
|
|
5563
6227
|
}
|
|
6228
|
+
/**
|
|
6229
|
+
* Re-activate a suspended run item with a human decision (HITL).
|
|
6230
|
+
* The HTTP resume endpoint calls this; this method exposes the engine primitive.
|
|
6231
|
+
*/
|
|
6232
|
+
async resumeRun(args) {
|
|
6233
|
+
return await this.deps.runContinuationService.resumeRun(args);
|
|
6234
|
+
}
|
|
5564
6235
|
async handleNodeExecutionRequest(request) {
|
|
5565
6236
|
await this.deps.nodeExecutionRequestHandler.handleNodeExecutionRequest(request);
|
|
5566
6237
|
}
|
|
@@ -5768,6 +6439,7 @@ var RunIntentService = class {
|
|
|
5768
6439
|
};
|
|
5769
6440
|
return await Promise.race([this.engine.waitForWebhookResponse(scheduled.runId), this.engine.waitForCompletion(scheduled.runId).then((completed) => {
|
|
5770
6441
|
if (completed.status === "failed") throw new Error(completed.error.message);
|
|
6442
|
+
if (completed.status === "halted") throw new Error(`Run halted: ${completed.reason}`);
|
|
5771
6443
|
return {
|
|
5772
6444
|
runId: completed.runId,
|
|
5773
6445
|
workflowId: completed.workflowId,
|
|
@@ -5892,5 +6564,5 @@ var WorkflowRepositoryWebhookTriggerMatcherFactory = class {
|
|
|
5892
6564
|
};
|
|
5893
6565
|
|
|
5894
6566
|
//#endregion
|
|
5895
|
-
export {
|
|
5896
|
-
//# sourceMappingURL=runtime-
|
|
6567
|
+
export { CostTrackingTelemetryAttributeNames as $, PersistedWorkflowTokenRegistry as A, node as At, CatalogBackedCostTrackingTelemetryFactory as B, HumanTaskStoreToken as Bt, DefaultDrivingScheduler as C, WhenBuilder as Ct, NodeInstanceFactoryFactory as D, defineNode as Dt, StaticCostCatalog as E, defineBatchNode as Et, InProcessRetryRunnerFactory as F, PersistedRuntimeTypeNameResolver as Ft, AllWorkflowsActiveWorkflowActivationPolicy as G, CodemationTelemetryMetricNames as H, RunnableOutputBehaviorResolver as I, DefinedNodeRegistry as It, NoOpNodeExecutionTelemetry as J, NoOpExecutionTelemetryFactory as K, NodeOutputNormalizer as L, HitlResumeTokenSignerToken as Lt, NodeExecutorFactory as M, InjectableRuntimeDecoratorComposer as Mt, NodeExecutor as N, PersistedRuntimeTypeMetadataStore as Nt, NodeInstanceFactory as O, chatModel as Ot, RunSuspendedError as P, StackTraceCallSitePathResolver as Pt, NoOpCostTrackingTelemetry as Q, ItemExprResolver as R, HitlTimeoutJobSchedulerToken as Rt, HintOnlyOffloadPolicy as S, ChainCursor as St, RunPolicySnapshotFactory as T, isHumanApprovalNode as Tt, GenAiTelemetryAttributeNames as U, DefaultExecutionContextFactory as V, CodemationTelemetryAttributeNames as W, NoOpTelemetryArtifactReference as X, NoOpTelemetrySpanScope as Y, NoOpCostTrackingTelemetryFactory as Z, RunTerminalPersistenceCoordinator as _, WorkflowExecutableNodeClassifier as _t, InMemoryLiveWorkflowRepository as a, isPortsEmission as at, LocalOnlyScheduler as b, WorkflowDefinitionError as bt, EngineFactory as c, CredentialResolverFactory as ct, PollingTriggerRuntime as d, DefaultExecutionBinaryService as dt, CostTrackingTelemetryMetricNames as et, PollingTriggerDedupWindow as f, UnavailableBinaryStorage as ft, WorkflowPolicyErrorServices as g, WorkflowExecutableNodeClassifierFactory as gt, WorkflowStoragePolicyEvaluator as h, DefaultWorkflowGraphFactory as ht, RunIntentService as i, emitPorts as it, MissingRuntimeTriggerToken as j, tool as jt, WorkflowSnapshotCodec as k, getPersistedRuntimeTypeMetadata as kt, Engine as l, getOriginIndexFromItem as lt, InMemoryBinaryStorage as m, ConnectionInvocationEventPublisher as mt, WorkflowRepositoryWebhookTriggerMatcher as n, RetryPolicy as nt, EngineWorkflowRunnerServiceFactory as o, isUnbrandedPortsEmissionShape as ot, InMemoryRunDataFactory as p, NodeEventPublisher as pt, NoOpExecutionTelemetry as q, RunIntentServiceFactory as r, NoRetryPolicy as rt, EngineWorkflowRunnerService as s, DefaultAsyncSleeper as st, WorkflowRepositoryWebhookTriggerMatcherFactory as t, ExpRetryPolicy as tt, NoOpPollingTriggerLogger as u, ChildExecutionScopeFactory as ut, ENGINE_EXECUTION_LIMITS_DEFAULTS as v, ConnectionInvocationIdFactory as vt, ConfigDrivenOffloadPolicy as w, defineHumanApprovalNode as wt, InlineDrivingScheduler as x, NodeIdSlugifier as xt, EngineExecutionLimitsPolicy as y, WorkflowBuilder as yt, InProcessRetryRunner as z, HitlWorkspaceIdToken as zt };
|
|
6568
|
+
//# sourceMappingURL=runtime-6-U2Cou5.js.map
|