@codemation/core-nodes-ocr 0.2.2 → 0.2.4

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.
@@ -1,4 +1,4 @@
1
- import { l as WorkflowDefinition, n as TypeToken, t as Container, u as AnyCredentialType } from "./runtimeTypes-ffl603pJ.cjs";
1
+ import { c as Container, l as TypeToken, s as WorkflowDefinition, u as AnyCredentialType } from "./runtimeTypes-WCvsnJMY.cjs";
2
2
  import { BetterAuthOptions } from "better-auth";
3
3
 
4
4
  //#region ../core/src/contracts/mcpTypes.d.ts
@@ -303,8 +303,6 @@ interface AppConfig {
303
303
  }>;
304
304
  readonly auth?: CodemationAuthConfig;
305
305
  readonly whitelabel: CodemationWhitelabelConfig;
306
- readonly webSocketPort: number;
307
- readonly webSocketBindHost: string;
308
306
  }
309
307
  //#endregion
310
308
  //#region ../host/src/presentation/config/CodemationAppContext.d.ts
@@ -1,4 +1,4 @@
1
- import { a as TypeToken, c as WorkflowDefinition, i as Container, l as AnyCredentialType, n as DefinedCollection, o as EngineExecutionLimitsPolicyConfig, s as McpServerDeclaration, t as CollectionDefinition } from "./index-C2KJPzqN.js";
1
+ import { a as TypeToken, c as McpServerDeclaration, i as Container, l as AnyCredentialType, n as DefinedCollection, o as EngineExecutionLimitsPolicyConfig, r as WorkflowDefinition, t as CollectionDefinition } from "./index-DF2ht42F.js";
2
2
  import { ZodType, z } from "zod";
3
3
  import { BetterAuthOptions } from "better-auth";
4
4
 
@@ -243,8 +243,6 @@ interface AppConfig {
243
243
  }>;
244
244
  readonly auth?: CodemationAuthConfig;
245
245
  readonly whitelabel: CodemationWhitelabelConfig;
246
- readonly webSocketPort: number;
247
- readonly webSocketBindHost: string;
248
246
  }
249
247
  //#endregion
250
248
  //#region ../host/src/presentation/config/CodemationAppContext.d.ts
@@ -1,6 +1,6 @@
1
1
  import { a as __toDynamicImportESM, i as __commonJS, n as require_auth_errors, o as __toESM, r as require_token_error, t as require_token_util } from "./token-util-EUxa8JtH.js";
2
- import { n as analyzeImageNode, o as azureContentUnderstandingCredentialType, r as analyzeDocumentNode, t as analyzeInvoiceNode } from "./analyzeInvoiceNode-uVwe3GHD.js";
3
- import { AgentConfigInspector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, CallableToolConfig, ChildExecutionScopeFactory, CodemationTelemetryAttributeNames, CodemationTelemetryMetricNames, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, GenAiTelemetryAttributeNames, ItemExprResolver, ItemsInputNormalizer, NodeBackedToolConfig, NodeOutputNormalizer, RunnableOutputBehaviorResolver, chatModel, defineCredential, defineNode, emitPorts, getOriginIndexFromItem, inject, injectable, isPortsEmission, node } from "@codemation/core";
2
+ import { n as analyzeImageNode, o as azureContentUnderstandingCredentialType, r as analyzeDocumentNode, t as analyzeInvoiceNode } from "./analyzeInvoiceNode-BqZsN8iL.js";
3
+ import { AgentConfigInspector, AgentGuardrailDefaults, AgentMessageConfigNormalizer, CallableToolConfig, ChildExecutionScopeFactory, CodemationTelemetryAttributeNames, CodemationTelemetryMetricNames, ConnectionInvocationIdFactory, ConnectionNodeIdFactory, CoreTokens, GenAiTelemetryAttributeNames, InboxChannelResolverToken, ItemExprResolver, ItemsInputNormalizer, NodeBackedToolConfig, NodeOutputNormalizer, RunnableOutputBehaviorResolver, SuspensionRequest, chatModel, defineCredential, defineHumanApprovalNode, defineNode, emitPorts, getOriginIndexFromItem, inject, injectable, isPortsEmission, node } from "@codemation/core";
4
4
  import dns from "node:dns/promises";
5
5
  import * as z4 from "zod/v4";
6
6
  import { z } from "zod/v4";
@@ -8281,6 +8281,59 @@ var OpenAiChatModelPresets = class {
8281
8281
  };
8282
8282
  const openAiChatModelPresets = new OpenAiChatModelPresets();
8283
8283
 
8284
+ //#endregion
8285
+ //#region ../core-nodes/src/chatModels/ManagedHmacSignerFactory.types.ts
8286
+ /**
8287
+ * Creates an HMAC-signing fetch wrapper that authenticates requests to
8288
+ * Codemation managed services (LLM broker, doc-scanner) with the
8289
+ * Codemation-Hmac v=1 scheme.
8290
+ *
8291
+ * Mirrors HmacRequestSigner from @codemation/host/pairing without importing
8292
+ * that package (which would create a circular dependency since @codemation/host
8293
+ * depends on @codemation/core-nodes).
8294
+ *
8295
+ * @param workspaceId - Workspace identifier injected by the CP provisioner.
8296
+ * @param pairingSecret - Base64-encoded 32-byte HMAC key injected by the provisioner.
8297
+ * @param options - Optional behaviour flags and test seams.
8298
+ */
8299
+ function managedHmacFetchFactory(workspaceId, pairingSecret, options) {
8300
+ const signBody = options?.signBody ?? true;
8301
+ return async (input, init) => {
8302
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
8303
+ const method = init?.method ?? "POST";
8304
+ let bodyForSigning = "";
8305
+ if (signBody && init?.body !== void 0 && init.body !== null) bodyForSigning = typeof init.body === "string" ? init.body : await new Response(init.body).text();
8306
+ const authHeader = buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyForSigning, options);
8307
+ const headers = new Headers(init?.headers);
8308
+ headers.set("Authorization", authHeader);
8309
+ const outgoingBody = signBody ? bodyForSigning || init?.body : init?.body;
8310
+ return fetch(input, {
8311
+ ...init,
8312
+ body: outgoingBody,
8313
+ headers
8314
+ });
8315
+ };
8316
+ }
8317
+ /**
8318
+ * Produces a Codemation-Hmac v=1 Authorization header value.
8319
+ * Algorithm must match HmacVerifier.computeSignature() in the control-plane.
8320
+ */
8321
+ function buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body, overrides) {
8322
+ const ts = overrides?.now ? overrides.now() : Math.floor(Date.now() / 1e3);
8323
+ const nonce = overrides?.nonce ? overrides.nonce() : randomBytes(16).toString("base64");
8324
+ const parsed = new URL(url);
8325
+ const path = (parsed.pathname + parsed.search).toLowerCase();
8326
+ const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
8327
+ const baseString = [
8328
+ method.toUpperCase(),
8329
+ path,
8330
+ ts,
8331
+ nonce,
8332
+ bodyHash
8333
+ ].join("\n");
8334
+ return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${createHmac("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
8335
+ }
8336
+
8284
8337
  //#endregion
8285
8338
  //#region ../core-nodes/src/chatModels/CodemationChatModelFactory.ts
8286
8339
  let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
@@ -8290,7 +8343,7 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
8290
8343
  const workspaceId = process.env["WORKSPACE_ID"];
8291
8344
  const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
8292
8345
  if (!workspaceId || !pairingSecret) throw new Error("Codemation managed AI not available in this environment (workspace pairing is not configured).");
8293
- const hmacFetch = this.buildHmacSignedFetch(workspaceId, pairingSecret);
8346
+ const hmacFetch = managedHmacFetchFactory(workspaceId, pairingSecret);
8294
8347
  const languageModel = createOpenAI({
8295
8348
  baseURL: `${gatewayUrl}/v1`,
8296
8349
  apiKey: "codemation-managed",
@@ -8306,52 +8359,6 @@ let CodemationChatModelFactory = class CodemationChatModelFactory$1 {
8306
8359
  }
8307
8360
  });
8308
8361
  }
8309
- /**
8310
- * Creates an HMAC-signed fetch wrapper for use with AI SDK's createOpenAI.
8311
- * Each call signs the request body with the workspace pairing secret so the
8312
- * LLM broker can authenticate the workspace without a user-managed API key.
8313
- *
8314
- * Mirrors HmacRequestSigner from @codemation/host/pairing without importing
8315
- * that package (which would create a circular dependency since @codemation/host
8316
- * depends on @codemation/core-nodes).
8317
- */
8318
- buildHmacSignedFetch(workspaceId, pairingSecret) {
8319
- return async (input, init) => {
8320
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
8321
- const method = init?.method ?? "POST";
8322
- let bodyString = "";
8323
- if (init?.body !== void 0 && init.body !== null) if (typeof init.body === "string") bodyString = init.body;
8324
- else bodyString = await new Response(init.body).text();
8325
- const authHeader = this.buildHmacAuthHeader(workspaceId, pairingSecret, method, url, bodyString);
8326
- const headers = new Headers(init?.headers);
8327
- headers.set("Authorization", authHeader);
8328
- const effectiveBody = bodyString || init?.body;
8329
- return fetch(input, {
8330
- ...init,
8331
- body: effectiveBody,
8332
- headers
8333
- });
8334
- };
8335
- }
8336
- /**
8337
- * Produces a Codemation-Hmac v1 Authorization header value.
8338
- * The algorithm must match HmacVerifier.computeSignature() in the control-plane.
8339
- */
8340
- buildHmacAuthHeader(workspaceId, pairingSecret, method, url, body) {
8341
- const ts = Math.floor(Date.now() / 1e3);
8342
- const nonce = randomBytes(16).toString("base64");
8343
- const parsed = new URL(url);
8344
- const path = (parsed.pathname + parsed.search).toLowerCase();
8345
- const bodyHash = createHash("sha256").update(body, "utf8").digest("hex");
8346
- const baseString = [
8347
- method.toUpperCase(),
8348
- path,
8349
- ts,
8350
- nonce,
8351
- bodyHash
8352
- ].join("\n");
8353
- return `Codemation-Hmac v=1,workspaceId=${workspaceId},ts=${ts},nonce=${nonce},sig=${createHmac("sha256", Buffer.from(pairingSecret, "base64")).update(baseString, "utf8").digest("base64")}`;
8354
- }
8355
8362
  };
8356
8363
  CodemationChatModelFactory = __decorate([chatModel({ packageName: "@codemation/core-nodes" })], CodemationChatModelFactory);
8357
8364
 
@@ -8730,12 +8737,22 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
8730
8737
  this.repairPolicy = repairPolicy;
8731
8738
  }
8732
8739
  async execute(args) {
8740
+ if (args.plannedToolCalls.filter((c) => c.binding.humanApproval !== void 0).length > 0 && args.plannedToolCalls.length > 1) return args.plannedToolCalls.map((c) => ({
8741
+ toolName: c.binding.config.name,
8742
+ toolCallId: c.toolCall.id ?? c.binding.config.name,
8743
+ result: { error: c.binding.humanApproval !== void 0 ? `HITL tool '${c.binding.config.name}' cannot be called alongside other tools in the same turn; call it alone.` : `deferred: a HITL tool in the same turn blocked execution. Retry this tool alone in the next turn.` },
8744
+ serialized: JSON.stringify({ error: c.binding.humanApproval !== void 0 ? `HITL tool '${c.binding.config.name}' cannot be called alongside other tools in the same turn; call it alone.` : `deferred: a HITL tool in the same turn blocked execution. Retry this tool alone in the next turn.` })
8745
+ }));
8733
8746
  const results = await Promise.allSettled(args.plannedToolCalls.map(async (plannedToolCall) => await this.executePlannedToolCall({
8734
8747
  ...args,
8735
8748
  plannedToolCall
8736
8749
  })));
8737
8750
  const rejected = results.find((result) => result.status === "rejected");
8738
- if (rejected?.status === "rejected") throw rejected.reason instanceof Error ? rejected.reason : new Error(String(rejected.reason));
8751
+ if (rejected?.status === "rejected") {
8752
+ const reason = rejected.reason;
8753
+ if (reason instanceof SuspensionRequest) throw reason;
8754
+ throw reason instanceof Error ? reason : new Error(String(reason));
8755
+ }
8739
8756
  return results.filter((result) => result.status === "fulfilled").map((result) => result.value);
8740
8757
  }
8741
8758
  async executePlannedToolCall(args) {
@@ -8820,6 +8837,34 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
8820
8837
  result
8821
8838
  };
8822
8839
  } catch (error) {
8840
+ if (error instanceof SuspensionRequest) {
8841
+ const pendingToolCallId = plannedToolCall.toolCall.id ?? plannedToolCall.binding.config.name;
8842
+ const checkpoint = {
8843
+ conversation: args.conversationSnapshot ? [...args.conversationSnapshot] : [],
8844
+ turnCount: args.turnCount ?? 0,
8845
+ toolCallCount: args.toolCallCount ?? 0,
8846
+ pendingToolCallId,
8847
+ agentName: args.agentName,
8848
+ modelId: args.modelId ?? ""
8849
+ };
8850
+ const agentReasoning = this.extractLastAssistantText(args.conversationSnapshot ?? []);
8851
+ const augmented = new SuspensionRequest({
8852
+ ...error.request,
8853
+ metadata: {
8854
+ ...error.request.metadata,
8855
+ agentCheckpoint: checkpoint,
8856
+ pendingToolCallId,
8857
+ agentReasoning,
8858
+ onRejected: plannedToolCall.binding.humanApproval?.onRejected ?? "return"
8859
+ }
8860
+ });
8861
+ await span.end({
8862
+ status: "error",
8863
+ statusMessage: "suspended",
8864
+ endedAt: /* @__PURE__ */ new Date()
8865
+ });
8866
+ throw augmented;
8867
+ }
8823
8868
  const classification = this.errorClassifier.classify({
8824
8869
  error,
8825
8870
  toolName: plannedToolCall.binding.config.name,
@@ -8995,6 +9040,24 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
8995
9040
  extractErrorDetails(error) {
8996
9041
  return error.details;
8997
9042
  }
9043
+ /**
9044
+ * Extracts the text content from the last assistant message in the conversation snapshot.
9045
+ * Used to populate `agentReasoning` in the HITL suspension metadata.
9046
+ */
9047
+ extractLastAssistantText(conversation) {
9048
+ for (let i = conversation.length - 1; i >= 0; i--) {
9049
+ const msg = conversation[i];
9050
+ if (msg?.role !== "assistant") continue;
9051
+ const content = msg.content;
9052
+ if (typeof content === "string") return content;
9053
+ if (Array.isArray(content)) {
9054
+ const textParts = content.filter((part) => typeof part === "object" && part.type === "text").map((part) => part.text);
9055
+ if (textParts.length > 0) return textParts.join("");
9056
+ }
9057
+ break;
9058
+ }
9059
+ return "";
9060
+ }
8998
9061
  serializeIssue(issue) {
8999
9062
  const result = {
9000
9063
  path: [...issue.path],
@@ -15325,6 +15388,7 @@ var AgentItemPortMap = class {
15325
15388
  //#endregion
15326
15389
  //#region ../core-nodes/src/nodes/AIAgentNode.ts
15327
15390
  var _ref, _ref2, _ref3, _ref4, _ref5;
15391
+ const HITL_SOLO_CONSTRAINT_SENTENCE = "This tool requires human approval and may take time. Call it alone — do not invoke other tools in the same turn. Your turn will be paused until a decision is made.";
15328
15392
  let AIAgentNode = class AIAgentNode$1 {
15329
15393
  kind = "node";
15330
15394
  outputPorts = ["main"];
@@ -15342,13 +15406,90 @@ let AIAgentNode = class AIAgentNode$1 {
15342
15406
  this.connectionCredentialExecutionContextFactory = this.executionHelpers.createConnectionCredentialExecutionContextFactory(credentialSessions);
15343
15407
  }
15344
15408
  async execute(args) {
15345
- const prepared = await this.getOrPrepareExecution(args.ctx);
15409
+ const { ctx } = args;
15410
+ if (ctx.resumeContext) return this.executeResumed(args, ctx.resumeContext);
15411
+ const prepared = await this.getOrPrepareExecution(ctx);
15346
15412
  const itemWithMappedJson = {
15347
15413
  ...args.item,
15348
15414
  json: args.input
15349
15415
  };
15350
15416
  return (await this.runAgentForItem(prepared, itemWithMappedJson, args.itemIndex, args.items)).json;
15351
15417
  }
15418
+ /**
15419
+ * Resume path: re-enters the agent loop after a HITL suspension.
15420
+ * Reconstructs the conversation from the checkpoint, injects the human decision
15421
+ * as a tool_result, and continues the loop from where it suspended.
15422
+ */
15423
+ async executeResumed(args, resumeContext) {
15424
+ const { ctx } = args;
15425
+ const taskMetadata = resumeContext.task.metadata ?? {};
15426
+ const checkpoint = taskMetadata["agentCheckpoint"];
15427
+ const onRejected = taskMetadata["onRejected"] ?? "return";
15428
+ if (!checkpoint) {
15429
+ const prepared$1 = await this.getOrPrepareExecution(ctx);
15430
+ const itemWithMappedJson = {
15431
+ ...args.item,
15432
+ json: args.input
15433
+ };
15434
+ return (await this.runAgentForItem(prepared$1, itemWithMappedJson, args.itemIndex, args.items)).json;
15435
+ }
15436
+ if (resumeContext.decision.kind === "decided" && resumeContext.decision.value === null && onRejected === "halt") return;
15437
+ const decision = this.normalizeDecision(resumeContext);
15438
+ if (decision.status === "rejected" && onRejected === "halt") return;
15439
+ const prepared = await this.getOrPrepareExecution(ctx);
15440
+ const item = args.item;
15441
+ const itemInputsByPort = AgentItemPortMap.fromItem(item);
15442
+ const itemScopedTools = this.createItemScopedTools(prepared.resolvedTools, ctx, item, args.itemIndex, args.items);
15443
+ const toolResultEntry = {
15444
+ toolName: checkpoint.pendingToolCallId,
15445
+ toolCallId: checkpoint.pendingToolCallId,
15446
+ result: decision,
15447
+ serialized: JSON.stringify(decision)
15448
+ };
15449
+ const conversation = [...checkpoint.conversation, AgentMessageFactory.createToolResultsMessage([toolResultEntry])];
15450
+ const loopResult = await this.runTurnLoopUntilFinalAnswer({
15451
+ prepared,
15452
+ itemInputsByPort,
15453
+ itemScopedTools,
15454
+ conversation,
15455
+ resumedTurnCount: checkpoint.turnCount,
15456
+ resumedToolCallCount: checkpoint.toolCallCount
15457
+ });
15458
+ await ctx.telemetry.recordMetric({
15459
+ name: CodemationTelemetryMetricNames.agentTurns,
15460
+ value: loopResult.turnCount
15461
+ });
15462
+ await ctx.telemetry.recordMetric({
15463
+ name: CodemationTelemetryMetricNames.agentToolCalls,
15464
+ value: loopResult.toolCallCount
15465
+ });
15466
+ const outputJson = await this.resolveFinalOutputJson(prepared, itemInputsByPort, conversation, loopResult.finalText, itemScopedTools.length > 0);
15467
+ return this.buildOutputItem(item, outputJson).json;
15468
+ }
15469
+ /**
15470
+ * Normalizes a {@link ResumeContext} decision into a flat JSON-serializable shape
15471
+ * suitable for injection as a tool_result content.
15472
+ */
15473
+ normalizeDecision(resumeContext) {
15474
+ const { decision } = resumeContext;
15475
+ if (decision.kind === "decided") {
15476
+ const value = decision.value;
15477
+ return {
15478
+ status: (typeof value === "object" && value !== null && "approved" in value ? Boolean(value["approved"]) : true) ? "approved" : "rejected",
15479
+ value: decision.value,
15480
+ actor: decision.actor,
15481
+ decidedAt: decision.decidedAt.toISOString()
15482
+ };
15483
+ }
15484
+ if (decision.kind === "timed_out") return {
15485
+ status: "timed_out",
15486
+ at: decision.at.toISOString()
15487
+ };
15488
+ return {
15489
+ status: "auto_accepted",
15490
+ at: decision.at.toISOString()
15491
+ };
15492
+ }
15352
15493
  async getOrPrepareExecution(ctx) {
15353
15494
  let pending = this.preparedByExecutionContext.get(ctx);
15354
15495
  if (!pending) {
@@ -15469,7 +15610,7 @@ let AIAgentNode = class AIAgentNode$1 {
15469
15610
  const { prepared, itemInputsByPort, itemScopedTools, conversation } = args;
15470
15611
  const { ctx, guardrails, toolLoadingStrategy } = prepared;
15471
15612
  let finalText = "";
15472
- let toolCallCount = 0;
15613
+ let toolCallCount = args.resumedToolCallCount ?? 0;
15473
15614
  let turnCount = 0;
15474
15615
  const repairAttemptsByToolName = /* @__PURE__ */ new Map();
15475
15616
  /** Tool IDs surfaced by find_tools across all prior turns in this item run. */
@@ -15510,11 +15651,17 @@ let AIAgentNode = class AIAgentNode$1 {
15510
15651
  const plannedToolCalls = this.planToolCalls(itemScopedTools, coordinatorCalls, ctx.nodeId);
15511
15652
  toolCallCount += plannedToolCalls.length;
15512
15653
  await this.markQueuedTools(plannedToolCalls, ctx);
15654
+ const assistantMsg = result.assistantMessage ?? AgentMessageFactory.createAssistantWithToolCalls(result.text, result.toolCalls);
15655
+ const conversationWithAssistant = [...conversation, assistantMsg];
15513
15656
  const executed = await this.toolExecutionCoordinator.execute({
15514
15657
  plannedToolCalls,
15515
15658
  ctx,
15516
15659
  agentName: this.getAgentDisplayName(ctx),
15517
- repairAttemptsByToolName
15660
+ repairAttemptsByToolName,
15661
+ conversationSnapshot: conversationWithAssistant,
15662
+ turnCount,
15663
+ toolCallCount,
15664
+ modelId: this.resolveChatModelName(ctx.config.chatModel)
15518
15665
  });
15519
15666
  coordinatorExecutedCalls.push(...executed);
15520
15667
  }
@@ -15576,6 +15723,7 @@ let AIAgentNode = class AIAgentNode$1 {
15576
15723
  connectionNodeId: ConnectionNodeIdFactory.toolConnectionNodeId(ctx.nodeId, entry.config.name),
15577
15724
  getCredentialRequirements: () => entry.config.getCredentialRequirements?.() ?? []
15578
15725
  });
15726
+ const hitlBehavior = this.resolveHumanApprovalBehavior(entry.config);
15579
15727
  return {
15580
15728
  config: entry.config,
15581
15729
  inputSchema: entry.runtime.inputSchema,
@@ -15590,11 +15738,22 @@ let AIAgentNode = class AIAgentNode$1 {
15590
15738
  items,
15591
15739
  hooks
15592
15740
  });
15593
- }
15741
+ },
15742
+ ...hitlBehavior !== void 0 ? { humanApproval: hitlBehavior } : {}
15594
15743
  };
15595
15744
  });
15596
15745
  }
15597
15746
  /**
15747
+ * Detects whether a tool config is backed by a `defineHumanApprovalNode` marker
15748
+ * and returns the HITL behavior config, or `undefined` when not a HITL tool.
15749
+ */
15750
+ resolveHumanApprovalBehavior(config) {
15751
+ if (!this.isNodeBackedToolConfig(config)) return void 0;
15752
+ const marker$3 = config.node.humanApprovalToolBehavior;
15753
+ if (marker$3 === void 0) return void 0;
15754
+ return { onRejected: marker$3.onRejected ?? "return" };
15755
+ }
15756
+ /**
15598
15757
  * Invoke a text turn using the merged tool set from item-scoped tools (coordinator-managed)
15599
15758
  * and strategy tools (find_tools + discovered MCP tools).
15600
15759
  * Strategy tools take precedence for names that overlap.
@@ -15624,6 +15783,8 @@ let AIAgentNode = class AIAgentNode$1 {
15624
15783
  /**
15625
15784
  * Builds a ToolSet from resolved tools for strategy initialization.
15626
15785
  * The strategy uses this for its "always-included" node-backed tool descriptions.
15786
+ * HITL tools (detected via the `humanApprovalToolBehavior` field set by `defineHumanApprovalNode`) get the solo-constraint sentence
15787
+ * appended to their description.
15627
15788
  */
15628
15789
  buildToolSetFromResolved(resolvedTools) {
15629
15790
  if (resolvedTools.length === 0) return {};
@@ -15633,8 +15794,10 @@ let AIAgentNode = class AIAgentNode$1 {
15633
15794
  schemaName: entry.config.name,
15634
15795
  requireObjectRoot: true
15635
15796
  });
15797
+ const baseDescription = entry.config.description ?? entry.runtime.defaultDescription;
15798
+ const description = this.resolveHumanApprovalBehavior(entry.config) !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : baseDescription;
15636
15799
  toolSet[entry.config.name] = {
15637
- description: entry.config.description ?? entry.runtime.defaultDescription,
15800
+ description,
15638
15801
  inputSchema: jsonSchema(schemaRecord)
15639
15802
  };
15640
15803
  }
@@ -15662,8 +15825,10 @@ let AIAgentNode = class AIAgentNode$1 {
15662
15825
  schemaName: entry.config.name,
15663
15826
  requireObjectRoot: true
15664
15827
  });
15828
+ const baseDescription = entry.config.description;
15829
+ const description = entry.humanApproval !== void 0 && baseDescription !== void 0 ? `${baseDescription} ${HITL_SOLO_CONSTRAINT_SENTENCE}` : entry.humanApproval !== void 0 ? HITL_SOLO_CONSTRAINT_SENTENCE : baseDescription;
15665
15830
  toolSet[entry.config.name] = {
15666
- description: entry.config.description,
15831
+ description,
15667
15832
  inputSchema: jsonSchema(schemaRecord)
15668
15833
  };
15669
15834
  }
@@ -15831,7 +15996,11 @@ let AIAgentNode = class AIAgentNode$1 {
15831
15996
  });
15832
15997
  try {
15833
15998
  const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
15834
- const outputSchema$1 = structuredOptions?.strict && !this.isZodSchema(schema) ? output_exports.object({ schema: jsonSchema(schema) }) : output_exports.object({ schema });
15999
+ const schemaRecord = this.isZodSchema(schema) ? this.executionHelpers.createJsonSchemaRecord(schema, {
16000
+ schemaName: structuredOptions?.schemaName ?? "structured_output",
16001
+ requireObjectRoot: true
16002
+ }) : schema;
16003
+ const outputSchema$1 = output_exports.object({ schema: jsonSchema(schemaRecord) });
15835
16004
  const result = await generateText({
15836
16005
  model: model.languageModel,
15837
16006
  messages: [...messages],
@@ -17755,6 +17924,173 @@ const collectionDeleteNode = defineNode({
17755
17924
  }
17756
17925
  });
17757
17926
 
17927
+ //#endregion
17928
+ //#region ../core-nodes/src/nodes/InboxApprovalNode.types.ts
17929
+ function resolveSubjectField(field, item) {
17930
+ return typeof field === "function" ? field({ item }) : field;
17931
+ }
17932
+ /**
17933
+ * Auto-detecting inbox approval node.
17934
+ *
17935
+ * Uses `ctx.resolve(InboxChannelResolverToken)` to pick the right inbox channel
17936
+ * at runtime:
17937
+ * - In managed mode (PairingConfig present): routes to the control-plane inbox.
17938
+ * - Otherwise: routes to the local inbox.
17939
+ *
17940
+ * Authors use this node directly; no extra wiring needed per deployment mode.
17941
+ */
17942
+ const inboxApproval = defineHumanApprovalNode({
17943
+ key: "inbox.approval",
17944
+ title: "Inbox Approval",
17945
+ description: "Suspend and wait for a human reviewer to approve or reject.",
17946
+ icon: "lucide:inbox",
17947
+ channel: "inbox",
17948
+ configSchema: z$1.object({
17949
+ title: z$1.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
17950
+ body: z$1.custom((v$1) => typeof v$1 === "string" || typeof v$1 === "function"),
17951
+ priority: z$1.enum([
17952
+ "low",
17953
+ "normal",
17954
+ "high"
17955
+ ]).default("normal"),
17956
+ timeout: z$1.string().default("24h"),
17957
+ onTimeout: z$1.enum(["halt", "auto-accept"]).default("halt")
17958
+ }),
17959
+ decisionSchema: z$1.object({
17960
+ approved: z$1.boolean(),
17961
+ note: z$1.string().optional()
17962
+ }),
17963
+ defaultTimeout: "24h",
17964
+ defaultOnTimeout: "halt",
17965
+ async deliver({ task, config, item }, ctx) {
17966
+ const resolver = ctx.resolve(InboxChannelResolverToken);
17967
+ if (!resolver) throw new Error("inboxApproval: no InboxChannelResolver registered. Ensure the host DI container is wired.");
17968
+ const { channel, workspaceId } = resolver.resolve();
17969
+ const subject = {
17970
+ title: resolveSubjectField(config.title, item),
17971
+ summary: resolveSubjectField(config.body, item),
17972
+ attributes: {
17973
+ workflowId: ctx.workflowId,
17974
+ item: item.json
17975
+ }
17976
+ };
17977
+ const delivery = await channel.deliver({
17978
+ task,
17979
+ subject,
17980
+ priority: config.priority,
17981
+ item,
17982
+ workspaceId
17983
+ });
17984
+ ctx.telemetry.addSpanEvent({
17985
+ name: "hitl.task.delivered",
17986
+ attributes: {
17987
+ taskId: task.taskId,
17988
+ channel: channel.kind
17989
+ }
17990
+ });
17991
+ return delivery;
17992
+ },
17993
+ async onDecision({ decision, actor, delivery }, ctx) {
17994
+ const resolver = ctx.resolve(InboxChannelResolverToken);
17995
+ if (!resolver) return;
17996
+ const { channel } = resolver.resolve();
17997
+ await channel.updateOnDecision?.({
17998
+ delivery,
17999
+ decision,
18000
+ actor
18001
+ });
18002
+ },
18003
+ async onTimeout({ delivery, policy }, ctx) {
18004
+ const resolver = ctx.resolve(InboxChannelResolverToken);
18005
+ if (!resolver) return;
18006
+ const { channel } = resolver.resolve();
18007
+ await channel.updateOnTimeout?.({
18008
+ delivery,
18009
+ policy
18010
+ });
18011
+ }
18012
+ });
18013
+
18014
+ //#endregion
18015
+ //#region ../core-nodes/src/nodes/codemationDocumentScannerNode.ts
18016
+ const ANALYZER_TYPES = [
18017
+ "document",
18018
+ "invoice",
18019
+ "image",
18020
+ "auto"
18021
+ ];
18022
+ const codemationDocumentScannerNode = defineNode({
18023
+ key: "codemation.document-scanner",
18024
+ title: "Codemation Document Scanner",
18025
+ description: "Analyzes a binary attachment (document or image) via the managed Codemation document-scanning service and returns markdown text plus structured fields. No Azure credential required — auth uses the workspace pairing secret. Enable includeConfidence to get per-field confidence scores (0–1).",
18026
+ icon: "lucide:scan-text",
18027
+ input: {
18028
+ binaryField: "data",
18029
+ analyzerType: "auto",
18030
+ contentType: void 0,
18031
+ includeConfidence: false,
18032
+ maxBytes: void 0
18033
+ },
18034
+ configSchema: z$1.object({
18035
+ binaryField: z$1.string().optional(),
18036
+ analyzerType: z$1.enum(ANALYZER_TYPES).optional(),
18037
+ contentType: z$1.string().optional(),
18038
+ includeConfidence: z$1.boolean().optional(),
18039
+ maxBytes: z$1.number().int().positive().optional()
18040
+ }),
18041
+ inspectorSummary({ config }) {
18042
+ const cfg = config;
18043
+ const rows = [{
18044
+ label: "Analyzer type",
18045
+ value: cfg.analyzerType ?? "auto"
18046
+ }];
18047
+ const binaryField = cfg.binaryField ?? "data";
18048
+ if (binaryField !== "data") rows.push({
18049
+ label: "Binary field",
18050
+ value: binaryField
18051
+ });
18052
+ if (cfg.includeConfidence) rows.push({
18053
+ label: "Confidence",
18054
+ value: "enabled"
18055
+ });
18056
+ if (cfg.contentType) rows.push({
18057
+ label: "Content type",
18058
+ value: cfg.contentType
18059
+ });
18060
+ return rows;
18061
+ },
18062
+ async execute({ item, ctx }, { config: rawConfig }) {
18063
+ const config = rawConfig;
18064
+ const gatewayUrl = process.env["DOC_SCANNER_GATEWAY_URL"];
18065
+ if (!gatewayUrl) throw new Error("Codemation Document Scanner not available in this environment (DOC_SCANNER_GATEWAY_URL is not set).");
18066
+ const workspaceId = process.env["WORKSPACE_ID"];
18067
+ const pairingSecret = process.env["WORKSPACE_PAIRING_SECRET"];
18068
+ if (!workspaceId || !pairingSecret) throw new Error("Codemation Document Scanner not available (workspace pairing is not configured).");
18069
+ const binaryField = config.binaryField ?? "data";
18070
+ const attachment = item.binary?.[binaryField];
18071
+ if (!attachment) throw new Error(`Codemation Document Scanner: no binary attachment at key "${binaryField}".`);
18072
+ const body = await ctx.binary.getBytes(attachment, config.maxBytes);
18073
+ const contentType = config.contentType ?? attachment.mimeType ?? "application/octet-stream";
18074
+ const analyzerType = config.analyzerType ?? "auto";
18075
+ const confidenceSuffix = config.includeConfidence ?? false ? "&confidence=true" : "";
18076
+ const url = `${gatewayUrl}/analyze?type=${encodeURIComponent(analyzerType)}${confidenceSuffix}`;
18077
+ const response = await managedHmacFetchFactory(workspaceId, pairingSecret, { signBody: false })(url, {
18078
+ method: "POST",
18079
+ body: body.buffer,
18080
+ headers: {
18081
+ "Content-Type": contentType,
18082
+ "Content-Length": String(body.byteLength),
18083
+ "X-Codemation-Caller": "workflow-node"
18084
+ }
18085
+ });
18086
+ if (!response.ok) {
18087
+ const text$1 = await response.text().catch(() => "(unreadable)");
18088
+ throw new Error(`Codemation Document Scanner: service responded ${response.status} ${response.statusText} — ${text$1}`);
18089
+ }
18090
+ return await response.json();
18091
+ }
18092
+ });
18093
+
17758
18094
  //#endregion
17759
18095
  //#region ../host/src/presentation/config/CodemationAuthoring.types.ts
17760
18096
  var CodemationAuthoringConfigFactory = class {