@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.
Files changed (93) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/{CostCatalogContract-DZgcUBE4.d.cts → CostCatalogContract-Dxq1BTyi.d.cts} +2 -2
  3. package/dist/{EngineRuntimeRegistration.types-Cggm5GVY.d.cts → EngineRuntimeRegistration.types-CqcTWexS.d.cts} +3 -3
  4. package/dist/{EngineRuntimeRegistration.types-BQbS9_gs.d.ts → EngineRuntimeRegistration.types-Cr75cSfL.d.ts} +2 -2
  5. package/dist/InMemoryRunDataFactory-Csy2evr_.d.cts +205 -0
  6. package/dist/{ItemsInputNormalizer-CwdOhSAK.cjs → ItemsInputNormalizer-57EdA1ad.cjs} +2 -2
  7. package/dist/{ItemsInputNormalizer-CwdOhSAK.cjs.map → ItemsInputNormalizer-57EdA1ad.cjs.map} +1 -1
  8. package/dist/{ItemsInputNormalizer-_Mfcd3YU.d.ts → ItemsInputNormalizer-BWtlwdVI.d.ts} +2 -2
  9. package/dist/{ItemsInputNormalizer-D-MH8MBs.js → ItemsInputNormalizer-BkSvmfAW.js} +2 -2
  10. package/dist/{ItemsInputNormalizer-D-MH8MBs.js.map → ItemsInputNormalizer-BkSvmfAW.js.map} +1 -1
  11. package/dist/{ItemsInputNormalizer-C_dpn76M.d.cts → ItemsInputNormalizer-pLrWwUAP.d.cts} +3 -3
  12. package/dist/{RunIntentService-CEF-sFfI.d.cts → RunIntentService-BitgkKaT.d.cts} +18 -4
  13. package/dist/{RunIntentService-BVur7x9n.d.ts → RunIntentService-DYpqfu6D.d.ts} +18 -4
  14. package/dist/{agentMcpTypes-ZiNbNsEi.d.cts → agentMcpTypes-DGIwk6Ue.d.cts} +201 -4
  15. package/dist/bootstrap/index.cjs +3 -3
  16. package/dist/bootstrap/index.d.cts +63 -7
  17. package/dist/bootstrap/index.d.ts +5 -5
  18. package/dist/bootstrap/index.js +3 -3
  19. package/dist/{bootstrap-BxuTFTLB.cjs → bootstrap-BEu1fJBM.cjs} +175 -4
  20. package/dist/bootstrap-BEu1fJBM.cjs.map +1 -0
  21. package/dist/{bootstrap-D_Yyi0wL.js → bootstrap-CSeInbj1.js} +173 -4
  22. package/dist/bootstrap-CSeInbj1.js.map +1 -0
  23. package/dist/browser.cjs +4 -2
  24. package/dist/browser.d.cts +4 -4
  25. package/dist/browser.d.ts +3 -3
  26. package/dist/browser.js +3 -3
  27. package/dist/contracts.d.cts +5 -5
  28. package/dist/contracts.d.ts +2 -2
  29. package/dist/{di-BlEKdoZS.cjs → di-C-2ep8NZ.cjs} +44 -1
  30. package/dist/di-C-2ep8NZ.cjs.map +1 -0
  31. package/dist/{di-0Wop7z1y.js → di-D9Mv3kF3.js} +33 -2
  32. package/dist/di-D9Mv3kF3.js.map +1 -0
  33. package/dist/{executionPersistenceContracts-BgZMRsTa.d.cts → executionPersistenceContracts-CN9d7AnL.d.cts} +2 -2
  34. package/dist/{index-62Ba9f7D.d.ts → index-CqZeNGAp.d.ts} +343 -101
  35. package/dist/{index-zWGtEhrf.d.ts → index-rllWL4r-.d.ts} +459 -5
  36. package/dist/index.cjs +91 -161
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +458 -97
  39. package/dist/index.d.ts +5 -5
  40. package/dist/index.js +74 -159
  41. package/dist/index.js.map +1 -1
  42. package/dist/{params-B5SENSzZ.d.cts → params-DRUr0F5v.d.cts} +2 -2
  43. package/dist/{runtime-cxmUkk0l.js → runtime-6-U2Cou5.js} +690 -18
  44. package/dist/runtime-6-U2Cou5.js.map +1 -0
  45. package/dist/{runtime-DBzq5YBi.cjs → runtime-DjYXgOo0.cjs} +749 -17
  46. package/dist/runtime-DjYXgOo0.cjs.map +1 -0
  47. package/dist/testing.cjs +3 -3
  48. package/dist/testing.d.cts +3 -3
  49. package/dist/testing.d.ts +3 -3
  50. package/dist/testing.js +3 -3
  51. package/package.json +1 -1
  52. package/src/authoring/defineHumanApprovalNode.types.ts +379 -0
  53. package/src/authoring/index.ts +6 -0
  54. package/src/binaries/DefaultExecutionBinaryServiceFactory.ts +27 -2
  55. package/src/binaries/DefaultNodeBinaryAttachmentServiceFactory.ts +14 -0
  56. package/src/binaries/boundedReadBinary.types.ts +90 -0
  57. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +29 -0
  58. package/src/contracts/CodemationTelemetryAttributeNames.ts +10 -0
  59. package/src/contracts/credentialTypes.ts +10 -0
  60. package/src/contracts/hitlSeamTypes.ts +34 -0
  61. package/src/contracts/humanTaskStoreTypes.ts +48 -0
  62. package/src/contracts/inboxChannelTypes.ts +58 -0
  63. package/src/contracts/index.ts +3 -0
  64. package/src/contracts/runTypes.ts +61 -3
  65. package/src/contracts/runtimeTypes.ts +131 -0
  66. package/src/contracts/workspaceFileTypes.ts +73 -0
  67. package/src/credentials/CredentialMaterialProvider.types.ts +61 -0
  68. package/src/credentials/ManagedCredentialMaterialWriteError.ts +14 -0
  69. package/src/credentials/ManagedMaterialFetchError.ts +16 -0
  70. package/src/execution/ActivationEnqueueService.ts +16 -0
  71. package/src/execution/DefaultExecutionContextFactory.ts +11 -0
  72. package/src/execution/NodeExecutionSnapshotFactory.ts +7 -1
  73. package/src/execution/NodeExecutor.ts +60 -1
  74. package/src/execution/NodeExecutorFactory.ts +12 -2
  75. package/src/execution/NodeSuspensionHandler.ts +220 -0
  76. package/src/execution/PersistedRunStateTerminalBuilder.ts +5 -2
  77. package/src/execution/RunStateSemantics.ts +5 -0
  78. package/src/execution/RunSuspendedError.ts +21 -0
  79. package/src/index.ts +42 -0
  80. package/src/orchestration/Engine.ts +12 -2
  81. package/src/orchestration/EngineWaiters.ts +1 -1
  82. package/src/orchestration/NodeExecutionRequestHandlerService.ts +25 -2
  83. package/src/orchestration/RunContinuationService.ts +226 -2
  84. package/src/orchestration/TestSuiteOrchestrator.ts +5 -4
  85. package/src/runtime/RunIntentService.ts +3 -0
  86. package/src/workflow/dsl/ChainCursorResolver.ts +36 -0
  87. package/dist/InMemoryRunDataFactory-C7YItvHG.d.cts +0 -123
  88. package/dist/bootstrap-BxuTFTLB.cjs.map +0 -1
  89. package/dist/bootstrap-D_Yyi0wL.js.map +0 -1
  90. package/dist/di-0Wop7z1y.js.map +0 -1
  91. package/dist/di-BlEKdoZS.cjs.map +0 -1
  92. package/dist/runtime-DBzq5YBi.cjs.map +0 -1
  93. package/dist/runtime-cxmUkk0l.js.map +0 -1
@@ -1,4 +1,4 @@
1
- const require_di = require('./di-BlEKdoZS.cjs');
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
- async openReadStream(attachment) {
666
- return await this.storage.openReadStream(attachment.storageKey);
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
- const raw = await Promise.resolve(node$1.execute(args));
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-DBzq5YBi.cjs.map
7136
+ //# sourceMappingURL=runtime-DjYXgOo0.cjs.map