@codemation/core-nodes 1.0.0 → 1.0.2

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 CHANGED
@@ -1,5 +1,50 @@
1
1
  # @codemation/core-nodes
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`ed75183`](https://github.com/MadeRelevant/codemation/commit/ed75183f51ae71b06aa2e57ae4fc48ce9db2e4ce) - Establish "per Item per Call" identity end-to-end so the workflow run inspector reports, visualizes, and dashboards multi-item AI agents correctly.
8
+
9
+ Previously, an orchestrator agent that processed N items emitted one flat list of LLM rounds and tool calls — the bottom execution tree, the right-panel agent timeline, cost dashboards, and the realtime event stream all collapsed iterations into one bucket, making sub-agent fan-outs (and parallel item processing in general) unreadable.
10
+
11
+ **What changed**
12
+ - **Engine** (`@codemation/core`): `NodeExecutor` mints a `NodeIterationId` per item inside per-item runnable activations and stamps it (with `itemIndex`) onto `NodeExecutionContext`. Connection invocations, telemetry spans (`gen_ai.chat.completion`, `agent.tool.call`), metric points (`codemation.cost.estimated`, `codemation.agent.turns`, `codemation.agent.tool_calls`), and run events all carry the per-item identity. New `ChildExecutionScopeFactory` re-roots `NodeExecutionContext` for sub-agents so credentials and iteration ids resolve correctly across the orchestrator → tool → sub-agent boundary.
13
+ - **Sub-agent credentials** (`@codemation/core-nodes`): `NodeBackedToolRuntime.resolveNodeCtx` no longer re-wraps `args.ctx.nodeId` with `ConnectionNodeIdFactory.toolConnectionNodeId` — the caller already pre-wraps it. The previous double-nesting produced exponentially deep node ids (`AIAgentNode:2__conn__tool__conn__searchInMail__conn__tool__conn__searchInMail__conn__llm`) that didn't match user-bound credential slots. Sub-agent OpenAI / API-key slots resolve again.
14
+ - **Realtime events**: new `connectionInvocationStarted` / `connectionInvocationCompleted` / `connectionInvocationFailed` events carry the full `ConnectionInvocationRecord` (incl. `iterationId`, `itemIndex`, `parentInvocationId`) and surgical reducers update the run cache without waiting for a coarse `runSaved` snapshot. Run-query polling dropped from 250 ms → 5 s now that WebSocket events drive most updates.
15
+ - **Persistence** (`@codemation/host`): Prisma `ExecutionInstance` model gains `iteration_id`, `item_index`, `parent_invocation_id` columns + index (sqlite + postgres migrations); `PrismaWorkflowRunRepository` round-trips them on read/save and via `ExecutionInstanceDto`. Without this the cold reload of a finished run silently flattens the per-item tree because `runSaved` events stream through Prisma. Telemetry tables already carried these columns from Phase 4; both sides now agree.
16
+ - **Iteration projection / cost queries** (`@codemation/host`): new `RunIterationProjectionFactory` projects `RunIterationRecord`s from connection invocations + iteration cost metrics and `GetIterationCostQueryHandler` serves per-iteration cost rollups for dashboards.
17
+ - **Inspector view model** (`@codemation/next-host`): `NodeInspectorTelemetryPresenter` groups LLM and tool spans by `iterationId` into "Item N" accordion entries (single-item agents fall back to flat layout). New `FocusedInvocationModelFactory` powers item-level prev/next navigation when a specific invocation is selected — the breadcrumb shows "Item X of Y" and nav targets the first invocation of adjacent items. Tool spans now interleave chronologically with LLM rounds (request → tools → response) instead of LLM rounds first then orphan tools at the bottom.
18
+ - **Bottom execution tree** (`@codemation/next-host`): new `ExecutionTreeItemGroupInjector` injects synthetic "Item N" parent rows between an agent and its connection invocations when the agent processed 2+ items. Single-item activations are left untouched; sub-agent invocations whose `parentInvocationId` already points at a tool-call row stay nested under the orchestrator's specific tool call.
19
+ - **Sub-agent credential boundary**: `ChildExecutionScopeFactory.forSubAgent` ensures sub-agent `NodeExecutionContext` keeps the parent invocation id and span context intact so trace nesting and credential resolution agree on the connection-node id.
20
+ - **Tests**: new unit + UI suites for each layer (sub-agent scope, item-group injector, focused invocation model, agent timeline per-item grouping, chronological ordering, Prisma iterationId round trip, item-aware properties panel, connection-invocation event publisher) and a runnable `apps/test-dev` sample (`agentSubAgentToolFanout`) that exercises the orchestrator → sub-agent fan-out across 2 items end-to-end.
21
+
22
+ - Updated dependencies [[`ed75183`](https://github.com/MadeRelevant/codemation/commit/ed75183f51ae71b06aa2e57ae4fc48ce9db2e4ce)]:
23
+ - @codemation/core@1.0.1
24
+
25
+ ## 1.0.1
26
+
27
+ ### Patch Changes
28
+
29
+ - [#95](https://github.com/MadeRelevant/codemation/pull/95) [`328c975`](https://github.com/MadeRelevant/codemation/commit/328c9759d45b711c177ea9a360ed4960ffdf5ffa) Thanks [@cblokland90](https://github.com/cblokland90)! - Workflow-canvas icon system redesign: LTR-oriented control-flow icons, pixel-perfect Split / Aggregate SVGs, and a single icon renderer shared by the canvas and the execution tree panel.
30
+
31
+ **Why**
32
+
33
+ The canvas reads left-to-right, but Lucide's `split`, `merge`, and `git-*` family are oriented vertically (top-to-bottom git-graph convention), so `If` and `Merge` nodes rendered 90° off from the flow direction. The execution-tree panel also ran a parallel icon-rendering path that only understood Lucide names — every time a plugin node set an icon as `builtin:<id>`, `si:<slug>`, or a URL, the tree panel silently fell back to a type-substring-guessed Lucide glyph (e.g. `"wait".includes("ai")` → the agent Bot icon). That duplication is gone.
34
+
35
+ **What changed**
36
+ - **Rotation suffix**: `NodeConfigBase.icon` now accepts an optional `@rot=<0|90|180|270>` tail modifier on any icon token (`lucide:`, `builtin:`, `si:`, or URL). Parsed by a strict tail regex so URLs with `@` (for example `http://user@host/icon.svg`) are unaffected, and non-orthogonal angles are rejected so glyphs stay pixel-crisp.
37
+ - **LTR control-flow icons**:
38
+ - `If`: `lucide:split@rot=90` — Y-fork with the single leg on the left and two arms fanning right.
39
+ - `Merge`: `lucide:merge@rot=90` — chevron pointing right, two arms merging from the left.
40
+ - **Pixel-perfect builtin SVGs** shipped under `packages/next-host/public/canvas-icons/builtin/`:
41
+ - `builtin:split-rows`: 1 source square on the left, tree trunk / spine, 3 output lines on the right.
42
+ - `builtin:aggregate-rows`: mirror — 3 input lines on the left converging through a spine into 1 summary square on the right.
43
+ - Stroke-based SVGs (matching Lucide stroke weight next to them on the canvas).
44
+ - **`@codemation/core-nodes` built-in node icon updates** that plug into the above: `If` (rotated split), `Merge` (rotated merge), `Split` (`builtin:split-rows`), `Aggregate` (`builtin:aggregate-rows`), `Wait` (`lucide:hourglass`), `MapData` (`lucide:square-pen`), `HttpRequest` (`lucide:globe`), `WebhookTrigger` (`lucide:webhook`), `NoOp` (`lucide:circle-dashed`).
45
+ - **One renderer, role-only fallback**: `WorkflowExecutionInspectorTreePanelContent` now renders `<WorkflowCanvasNodeIcon />` so `builtin:`, `si:`, URLs, and `@rot=…` resolve identically in the execution tree panel and on the canvas. `WorkflowNodeIconResolver.resolveFallback` shrank from 11 branches of type-substring guessing to 4 role mappings (`agent` / `nestedAgent` → `Bot`, `languageModel` → `Brain`, `tool` → `Wrench`, else → `Boxes`). Plugin nodes that forget to set an icon now get a generic `Boxes` — a clear visual signal rather than a silent substring mismatch.
46
+ - **New test file** `test/canvas/workflowCanvasNodeIcon.test.tsx` pins 12 behaviours across the rotation parser and the role-only fallback, including a regression case that the pre-fix `"wait".includes("ai")` mis-mapping is impossible now.
47
+
3
48
  ## 1.0.0
4
49
 
5
50
  ### Major Changes
package/dist/index.cjs CHANGED
@@ -2883,7 +2883,7 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
2883
2883
  async executePlannedToolCall(args) {
2884
2884
  const { plannedToolCall, ctx } = args;
2885
2885
  const toolCallInputsByPort = AgentToolCallPortMap.fromInput(plannedToolCall.toolCall.input ?? {});
2886
- const invocationId = __codemation_core.ConnectionInvocationIdFactory.create();
2886
+ const invocationId = plannedToolCall.invocationId;
2887
2887
  const startedAt = /* @__PURE__ */ new Date();
2888
2888
  const span = ctx.telemetry.startChildSpan({
2889
2889
  name: "agent.tool.call",
@@ -2891,7 +2891,10 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
2891
2891
  startedAt,
2892
2892
  attributes: {
2893
2893
  [__codemation_core.CodemationTelemetryAttributeNames.connectionInvocationId]: invocationId,
2894
- [__codemation_core.CodemationTelemetryAttributeNames.toolName]: plannedToolCall.binding.config.name
2894
+ [__codemation_core.CodemationTelemetryAttributeNames.toolName]: plannedToolCall.binding.config.name,
2895
+ ...ctx.iterationId ? { [__codemation_core.CodemationTelemetryAttributeNames.iterationId]: ctx.iterationId } : {},
2896
+ ...typeof ctx.itemIndex === "number" ? { [__codemation_core.CodemationTelemetryAttributeNames.iterationIndex]: ctx.itemIndex } : {},
2897
+ ...ctx.parentInvocationId ? { [__codemation_core.CodemationTelemetryAttributeNames.parentInvocationId]: ctx.parentInvocationId } : {}
2895
2898
  }
2896
2899
  });
2897
2900
  await ctx.nodeState?.markRunning({
@@ -2899,8 +2902,23 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
2899
2902
  activationId: ctx.activationId,
2900
2903
  inputsByPort: toolCallInputsByPort
2901
2904
  });
2905
+ await ctx.nodeState?.appendConnectionInvocation({
2906
+ invocationId,
2907
+ connectionNodeId: plannedToolCall.nodeId,
2908
+ parentAgentNodeId: ctx.nodeId,
2909
+ parentAgentActivationId: ctx.activationId,
2910
+ status: "running",
2911
+ managedInput: this.toJsonValue(plannedToolCall.toolCall.input),
2912
+ queuedAt: startedAt.toISOString(),
2913
+ startedAt: startedAt.toISOString(),
2914
+ iterationId: ctx.iterationId,
2915
+ parentInvocationId: ctx.parentInvocationId
2916
+ });
2902
2917
  try {
2903
- const result = await plannedToolCall.binding.execute(plannedToolCall.toolCall.input ?? {});
2918
+ const result = await plannedToolCall.binding.execute(plannedToolCall.toolCall.input ?? {}, {
2919
+ parentSpan: span,
2920
+ parentInvocationId: invocationId
2921
+ });
2904
2922
  const serialized = typeof result === "string" ? result : JSON.stringify(result);
2905
2923
  const finishedAt = /* @__PURE__ */ new Date();
2906
2924
  await ctx.nodeState?.markCompleted({
@@ -2933,7 +2951,9 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
2933
2951
  managedOutput: this.toJsonValue(result),
2934
2952
  queuedAt: startedAt.toISOString(),
2935
2953
  startedAt: startedAt.toISOString(),
2936
- finishedAt: finishedAt.toISOString()
2954
+ finishedAt: finishedAt.toISOString(),
2955
+ iterationId: ctx.iterationId,
2956
+ parentInvocationId: ctx.parentInvocationId
2937
2957
  });
2938
2958
  return {
2939
2959
  toolName: plannedToolCall.binding.config.name,
@@ -3074,7 +3094,9 @@ let AgentToolExecutionCoordinator = class AgentToolExecutionCoordinator$1 {
3074
3094
  },
3075
3095
  queuedAt: args.startedAt.toISOString(),
3076
3096
  startedAt: args.startedAt.toISOString(),
3077
- finishedAt: finishedAt.toISOString()
3097
+ finishedAt: finishedAt.toISOString(),
3098
+ iterationId: args.ctx.iterationId,
3099
+ parentInvocationId: args.ctx.parentInvocationId
3078
3100
  });
3079
3101
  }
3080
3102
  createRepairPayload(args) {
@@ -3135,13 +3157,14 @@ AgentToolExecutionCoordinator = __decorate([
3135
3157
 
3136
3158
  //#endregion
3137
3159
  //#region src/nodes/NodeBackedToolRuntime.ts
3138
- var _ref$1, _ref2$1, _ref3$1;
3160
+ var _ref$1, _ref2$1, _ref3$1, _ref4$1;
3139
3161
  let NodeBackedToolRuntime = class NodeBackedToolRuntime$1 {
3140
- constructor(nodeResolver, itemExprResolver, outputNormalizer, outputBehaviorResolver) {
3162
+ constructor(nodeResolver, itemExprResolver, outputNormalizer, outputBehaviorResolver, childExecutionScopeFactory) {
3141
3163
  this.nodeResolver = nodeResolver;
3142
3164
  this.itemExprResolver = itemExprResolver;
3143
3165
  this.outputNormalizer = outputNormalizer;
3144
3166
  this.outputBehaviorResolver = outputBehaviorResolver;
3167
+ this.childExecutionScopeFactory = childExecutionScopeFactory;
3145
3168
  }
3146
3169
  async execute(config$1, args) {
3147
3170
  const nodeInput = config$1.toNodeItem({
@@ -3152,10 +3175,7 @@ let NodeBackedToolRuntime = class NodeBackedToolRuntime$1 {
3152
3175
  ctx: args.ctx,
3153
3176
  node: config$1.node
3154
3177
  });
3155
- const nodeCtx = {
3156
- ...args.ctx,
3157
- config: config$1.node
3158
- };
3178
+ const nodeCtx = this.resolveNodeCtx(config$1, args);
3159
3179
  const resolvedNode = this.nodeResolver.resolve(config$1.node.type);
3160
3180
  const outputs = await this.executeResolvedNode(resolvedNode, nodeInput, nodeCtx);
3161
3181
  return config$1.toToolOutput({
@@ -3168,6 +3188,35 @@ let NodeBackedToolRuntime = class NodeBackedToolRuntime$1 {
3168
3188
  outputs
3169
3189
  });
3170
3190
  }
3191
+ /**
3192
+ * Returns a re-rooted child ctx for nested-agent tools (so their LLM/tool connection ids derive
3193
+ * from the tool connection node, telemetry parents under the tool-call span, and connection
3194
+ * invocations carry `parentInvocationId`). Plain runnable tools (non-agent) keep the orchestrator
3195
+ * ctx with only `config` swapped — no nesting concern.
3196
+ *
3197
+ * The caller (`AIAgentNode.createItemScopedTools`) already wraps the orchestrator ctx via
3198
+ * `ConnectionCredentialExecutionContextFactory.forConnectionNode`, so `args.ctx.nodeId` is the
3199
+ * tool's own connection node id (e.g. `AIAgentNode:2__conn__tool__searchInMail`). We pass that
3200
+ * through as the sub-agent's `nodeId`; deriving another `toolConnectionNodeId(args.ctx.nodeId,
3201
+ * config.name)` here would prepend a duplicate `__conn__tool__<name>` segment and exponentially
3202
+ * deepen ids on each invocation, which also breaks credential resolution because user-provided
3203
+ * bindings sit on the single-level connection node id.
3204
+ */
3205
+ resolveNodeCtx(config$1, args) {
3206
+ const isNestedAgent = __codemation_core.AgentConfigInspector.isAgentNodeConfig(config$1.node);
3207
+ const hooks = args.hooks;
3208
+ if (!isNestedAgent || !hooks?.parentSpan || !hooks.parentInvocationId) return {
3209
+ ...args.ctx,
3210
+ config: config$1.node
3211
+ };
3212
+ return this.childExecutionScopeFactory.forSubAgent({
3213
+ parentCtx: args.ctx,
3214
+ childNodeId: args.ctx.nodeId,
3215
+ childConfig: config$1.node,
3216
+ parentInvocationId: hooks.parentInvocationId,
3217
+ parentSpan: hooks.parentSpan
3218
+ });
3219
+ }
3171
3220
  async executeResolvedNode(resolvedNode, nodeInput, ctx) {
3172
3221
  if (this.isMultiInputNode(resolvedNode)) return await resolvedNode.executeMulti({ in: [nodeInput] }, ctx);
3173
3222
  if (this.isRunnableNode(resolvedNode)) {
@@ -3205,11 +3254,13 @@ NodeBackedToolRuntime = __decorate([
3205
3254
  __decorateParam(1, (0, __codemation_core.inject)(__codemation_core.ItemExprResolver)),
3206
3255
  __decorateParam(2, (0, __codemation_core.inject)(__codemation_core.NodeOutputNormalizer)),
3207
3256
  __decorateParam(3, (0, __codemation_core.inject)(__codemation_core.RunnableOutputBehaviorResolver)),
3257
+ __decorateParam(4, (0, __codemation_core.inject)(__codemation_core.ChildExecutionScopeFactory)),
3208
3258
  __decorateMetadata("design:paramtypes", [
3209
3259
  Object,
3210
3260
  typeof (_ref$1 = typeof __codemation_core.ItemExprResolver !== "undefined" && __codemation_core.ItemExprResolver) === "function" ? _ref$1 : Object,
3211
3261
  typeof (_ref2$1 = typeof __codemation_core.NodeOutputNormalizer !== "undefined" && __codemation_core.NodeOutputNormalizer) === "function" ? _ref2$1 : Object,
3212
- typeof (_ref3$1 = typeof __codemation_core.RunnableOutputBehaviorResolver !== "undefined" && __codemation_core.RunnableOutputBehaviorResolver) === "function" ? _ref3$1 : Object
3262
+ typeof (_ref3$1 = typeof __codemation_core.RunnableOutputBehaviorResolver !== "undefined" && __codemation_core.RunnableOutputBehaviorResolver) === "function" ? _ref3$1 : Object,
3263
+ typeof (_ref4$1 = typeof __codemation_core.ChildExecutionScopeFactory !== "undefined" && __codemation_core.ChildExecutionScopeFactory) === "function" ? _ref4$1 : Object
3213
3264
  ])
3214
3265
  ], NodeBackedToolRuntime);
3215
3266
 
@@ -3412,7 +3463,7 @@ let AIAgentNode = class AIAgentNode$1 {
3412
3463
  return {
3413
3464
  config: entry.config,
3414
3465
  inputSchema: entry.runtime.inputSchema,
3415
- execute: async (input) => {
3466
+ execute: async (input, hooks) => {
3416
3467
  const validated = entry.runtime.inputSchema.parse(input);
3417
3468
  return await entry.runtime.execute({
3418
3469
  config: entry.config,
@@ -3420,7 +3471,8 @@ let AIAgentNode = class AIAgentNode$1 {
3420
3471
  ctx: toolCredentialContext,
3421
3472
  item,
3422
3473
  itemIndex,
3423
- items
3474
+ items,
3475
+ hooks
3424
3476
  });
3425
3477
  }
3426
3478
  };
@@ -3470,11 +3522,36 @@ let AIAgentNode = class AIAgentNode$1 {
3470
3522
  activationId: ctx.activationId,
3471
3523
  inputsByPort: itemInputsByPort
3472
3524
  });
3525
+ await ctx.nodeState?.appendConnectionInvocation({
3526
+ invocationId,
3527
+ connectionNodeId: languageModelConnectionNodeId,
3528
+ parentAgentNodeId: ctx.nodeId,
3529
+ parentAgentActivationId: ctx.activationId,
3530
+ status: "queued",
3531
+ managedInput: summarizedInput,
3532
+ queuedAt: startedAt.toISOString(),
3533
+ iterationId: ctx.iterationId,
3534
+ itemIndex: ctx.itemIndex,
3535
+ parentInvocationId: ctx.parentInvocationId
3536
+ });
3473
3537
  await ctx.nodeState?.markRunning({
3474
3538
  nodeId: languageModelConnectionNodeId,
3475
3539
  activationId: ctx.activationId,
3476
3540
  inputsByPort: itemInputsByPort
3477
3541
  });
3542
+ await ctx.nodeState?.appendConnectionInvocation({
3543
+ invocationId,
3544
+ connectionNodeId: languageModelConnectionNodeId,
3545
+ parentAgentNodeId: ctx.nodeId,
3546
+ parentAgentActivationId: ctx.activationId,
3547
+ status: "running",
3548
+ managedInput: summarizedInput,
3549
+ queuedAt: startedAt.toISOString(),
3550
+ startedAt: startedAt.toISOString(),
3551
+ iterationId: ctx.iterationId,
3552
+ itemIndex: ctx.itemIndex,
3553
+ parentInvocationId: ctx.parentInvocationId
3554
+ });
3478
3555
  try {
3479
3556
  const tools = this.buildToolSet(itemScopedTools);
3480
3557
  const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
@@ -3490,11 +3567,12 @@ let AIAgentNode = class AIAgentNode$1 {
3490
3567
  });
3491
3568
  const turnResult = this.extractTurnResult(result);
3492
3569
  const finishedAt = /* @__PURE__ */ new Date();
3570
+ const managedOutput = this.summarizeTurnOutput(turnResult);
3493
3571
  await ctx.nodeState?.markCompleted({
3494
3572
  nodeId: languageModelConnectionNodeId,
3495
3573
  activationId: ctx.activationId,
3496
3574
  inputsByPort: itemInputsByPort,
3497
- outputs: AgentOutputFactory.fromUnknown({ content: turnResult.text })
3575
+ outputs: AgentOutputFactory.fromUnknown(managedOutput)
3498
3576
  });
3499
3577
  await span.attachArtifact({
3500
3578
  kind: "ai.messages",
@@ -3518,10 +3596,13 @@ let AIAgentNode = class AIAgentNode$1 {
3518
3596
  parentAgentActivationId: ctx.activationId,
3519
3597
  status: "completed",
3520
3598
  managedInput: summarizedInput,
3521
- managedOutput: turnResult.text,
3599
+ managedOutput,
3522
3600
  queuedAt: startedAt.toISOString(),
3523
3601
  startedAt: startedAt.toISOString(),
3524
- finishedAt: finishedAt.toISOString()
3602
+ finishedAt: finishedAt.toISOString(),
3603
+ iterationId: ctx.iterationId,
3604
+ itemIndex: ctx.itemIndex,
3605
+ parentInvocationId: ctx.parentInvocationId
3525
3606
  });
3526
3607
  return turnResult;
3527
3608
  } catch (error) {
@@ -3557,11 +3638,36 @@ let AIAgentNode = class AIAgentNode$1 {
3557
3638
  activationId: ctx.activationId,
3558
3639
  inputsByPort: itemInputsByPort
3559
3640
  });
3641
+ await ctx.nodeState?.appendConnectionInvocation({
3642
+ invocationId,
3643
+ connectionNodeId: languageModelConnectionNodeId,
3644
+ parentAgentNodeId: ctx.nodeId,
3645
+ parentAgentActivationId: ctx.activationId,
3646
+ status: "queued",
3647
+ managedInput: summarizedInput,
3648
+ queuedAt: startedAt.toISOString(),
3649
+ iterationId: ctx.iterationId,
3650
+ itemIndex: ctx.itemIndex,
3651
+ parentInvocationId: ctx.parentInvocationId
3652
+ });
3560
3653
  await ctx.nodeState?.markRunning({
3561
3654
  nodeId: languageModelConnectionNodeId,
3562
3655
  activationId: ctx.activationId,
3563
3656
  inputsByPort: itemInputsByPort
3564
3657
  });
3658
+ await ctx.nodeState?.appendConnectionInvocation({
3659
+ invocationId,
3660
+ connectionNodeId: languageModelConnectionNodeId,
3661
+ parentAgentNodeId: ctx.nodeId,
3662
+ parentAgentActivationId: ctx.activationId,
3663
+ status: "running",
3664
+ managedInput: summarizedInput,
3665
+ queuedAt: startedAt.toISOString(),
3666
+ startedAt: startedAt.toISOString(),
3667
+ iterationId: ctx.iterationId,
3668
+ itemIndex: ctx.itemIndex,
3669
+ parentInvocationId: ctx.parentInvocationId
3670
+ });
3565
3671
  try {
3566
3672
  const callOptions = this.resolveCallOptions(model, guardrails.modelInvocationOptions);
3567
3673
  const outputSchema = structuredOptions?.strict && !this.isZodSchema(schema) ? ai.Output.object({ schema: (0, ai.jsonSchema)(schema) }) : ai.Output.object({ schema });
@@ -3607,7 +3713,10 @@ let AIAgentNode = class AIAgentNode$1 {
3607
3713
  managedOutput: this.resultToJsonValue(result.experimental_output),
3608
3714
  queuedAt: startedAt.toISOString(),
3609
3715
  startedAt: startedAt.toISOString(),
3610
- finishedAt: finishedAt.toISOString()
3716
+ finishedAt: finishedAt.toISOString(),
3717
+ iterationId: ctx.iterationId,
3718
+ itemIndex: ctx.itemIndex,
3719
+ parentInvocationId: ctx.parentInvocationId
3611
3720
  });
3612
3721
  return result.experimental_output;
3613
3722
  } catch (error) {
@@ -3638,6 +3747,24 @@ let AIAgentNode = class AIAgentNode$1 {
3638
3747
  providerOptions: overrides?.providerOptions ?? defaults.providerOptions
3639
3748
  };
3640
3749
  }
3750
+ /**
3751
+ * Build a no-code-friendly output payload for an LLM round.
3752
+ *
3753
+ * Always includes `content` (matching the canvas snapshot shape used elsewhere) and adds a
3754
+ * `toolCalls` array when the round produced tool calls so the execution inspector surfaces the
3755
+ * planned calls instead of just an empty `""` for tool-only rounds.
3756
+ */
3757
+ summarizeTurnOutput(turnResult) {
3758
+ if (turnResult.toolCalls.length === 0) return { content: turnResult.text };
3759
+ const toolCalls = turnResult.toolCalls.map((toolCall) => ({
3760
+ name: toolCall.name,
3761
+ args: this.resultToJsonValue(toolCall.input) ?? null
3762
+ }));
3763
+ return {
3764
+ content: turnResult.text,
3765
+ toolCalls
3766
+ };
3767
+ }
3641
3768
  extractTurnResult(result) {
3642
3769
  const usage = this.extractUsageFromResult(result);
3643
3770
  const text = result.text;
@@ -3681,7 +3808,10 @@ let AIAgentNode = class AIAgentNode$1 {
3681
3808
  attributes: {
3682
3809
  [__codemation_core.CodemationTelemetryAttributeNames.connectionInvocationId]: invocationId,
3683
3810
  [__codemation_core.GenAiTelemetryAttributeNames.operationName]: "chat",
3684
- [__codemation_core.GenAiTelemetryAttributeNames.requestModel]: this.resolveChatModelName(ctx.config.chatModel)
3811
+ [__codemation_core.GenAiTelemetryAttributeNames.requestModel]: this.resolveChatModelName(ctx.config.chatModel),
3812
+ ...ctx.iterationId ? { [__codemation_core.CodemationTelemetryAttributeNames.iterationId]: ctx.iterationId } : {},
3813
+ ...typeof ctx.itemIndex === "number" ? { [__codemation_core.CodemationTelemetryAttributeNames.iterationIndex]: ctx.itemIndex } : {},
3814
+ ...ctx.parentInvocationId ? { [__codemation_core.CodemationTelemetryAttributeNames.parentInvocationId]: ctx.parentInvocationId } : {}
3685
3815
  }
3686
3816
  });
3687
3817
  }
@@ -3731,11 +3861,26 @@ let AIAgentNode = class AIAgentNode$1 {
3731
3861
  return chatModel$1.modelName ?? chatModel$1.name;
3732
3862
  }
3733
3863
  async markQueuedTools(plannedToolCalls, ctx) {
3734
- for (const plannedToolCall of plannedToolCalls) await ctx.nodeState?.markQueued({
3735
- nodeId: plannedToolCall.nodeId,
3736
- activationId: ctx.activationId,
3737
- inputsByPort: AgentToolCallPortMap.fromInput(plannedToolCall.toolCall.input ?? {})
3738
- });
3864
+ const queuedAt = (/* @__PURE__ */ new Date()).toISOString();
3865
+ for (const plannedToolCall of plannedToolCalls) {
3866
+ await ctx.nodeState?.markQueued({
3867
+ nodeId: plannedToolCall.nodeId,
3868
+ activationId: ctx.activationId,
3869
+ inputsByPort: AgentToolCallPortMap.fromInput(plannedToolCall.toolCall.input ?? {})
3870
+ });
3871
+ await ctx.nodeState?.appendConnectionInvocation({
3872
+ invocationId: plannedToolCall.invocationId,
3873
+ connectionNodeId: plannedToolCall.nodeId,
3874
+ parentAgentNodeId: ctx.nodeId,
3875
+ parentAgentActivationId: ctx.activationId,
3876
+ status: "queued",
3877
+ managedInput: this.resultToJsonValue(plannedToolCall.toolCall.input),
3878
+ queuedAt,
3879
+ iterationId: ctx.iterationId,
3880
+ itemIndex: ctx.itemIndex,
3881
+ parentInvocationId: ctx.parentInvocationId
3882
+ });
3883
+ }
3739
3884
  }
3740
3885
  planToolCalls(bindings, toolCalls, parentNodeId) {
3741
3886
  const invocationCountByToolName = /* @__PURE__ */ new Map();
@@ -3748,7 +3893,8 @@ let AIAgentNode = class AIAgentNode$1 {
3748
3893
  binding,
3749
3894
  toolCall,
3750
3895
  invocationIndex,
3751
- nodeId: __codemation_core.ConnectionNodeIdFactory.toolConnectionNodeId(parentNodeId, binding.config.name)
3896
+ nodeId: __codemation_core.ConnectionNodeIdFactory.toolConnectionNodeId(parentNodeId, binding.config.name),
3897
+ invocationId: __codemation_core.ConnectionInvocationIdFactory.create()
3752
3898
  };
3753
3899
  });
3754
3900
  }
@@ -3776,7 +3922,10 @@ let AIAgentNode = class AIAgentNode$1 {
3776
3922
  },
3777
3923
  queuedAt: args.startedAt.toISOString(),
3778
3924
  startedAt: args.startedAt.toISOString(),
3779
- finishedAt: finishedAt.toISOString()
3925
+ finishedAt: finishedAt.toISOString(),
3926
+ iterationId: args.ctx.iterationId,
3927
+ itemIndex: args.ctx.itemIndex,
3928
+ parentInvocationId: args.ctx.parentInvocationId
3780
3929
  });
3781
3930
  return effectiveError;
3782
3931
  }
@@ -4049,6 +4198,7 @@ var HttpRequest = class {
4049
4198
  kind = "node";
4050
4199
  type = HttpRequestNode;
4051
4200
  execution = { hint: "local" };
4201
+ icon = "lucide:globe";
4052
4202
  constructor(name, args = {}, retryPolicy = __codemation_core.RetryPolicy.defaultForHttp) {
4053
4203
  this.name = name;
4054
4204
  this.args = args;
@@ -4090,7 +4240,7 @@ var Aggregate = class {
4090
4240
  type = AggregateNode;
4091
4241
  execution = { hint: "local" };
4092
4242
  keepBinaries = true;
4093
- icon = "lucide:layers";
4243
+ icon = "builtin:aggregate-rows";
4094
4244
  constructor(name, aggregate, id) {
4095
4245
  this.name = name;
4096
4246
  this.aggregate = aggregate;
@@ -4185,7 +4335,7 @@ var If = class {
4185
4335
  kind = "node";
4186
4336
  type = IfNode;
4187
4337
  execution = { hint: "local" };
4188
- icon = "lucide:split";
4338
+ icon = "lucide:split@rot=90";
4189
4339
  declaredOutputPorts = ["true", "false"];
4190
4340
  constructor(name, predicate, id) {
4191
4341
  this.name = name;
@@ -4250,7 +4400,7 @@ var Split = class {
4250
4400
  * Mirrors {@link MapData}'s empty-output behavior.
4251
4401
  */
4252
4402
  continueWhenEmptyOutput = true;
4253
- icon = "lucide:ungroup";
4403
+ icon = "builtin:split-rows";
4254
4404
  constructor(name, getElements, id) {
4255
4405
  this.name = name;
4256
4406
  this.getElements = getElements;
@@ -4321,6 +4471,7 @@ var MapData = class {
4321
4471
  execution = { hint: "local" };
4322
4472
  /** Zero mapped items should still allow downstream nodes to run. */
4323
4473
  continueWhenEmptyOutput = true;
4474
+ icon = "lucide:square-pen";
4324
4475
  keepBinaries;
4325
4476
  constructor(name, map, options = {}) {
4326
4477
  this.name = name;
@@ -4390,7 +4541,7 @@ MergeNode = __decorate([(0, __codemation_core.node)({ packageName: "@codemation/
4390
4541
  var Merge = class {
4391
4542
  kind = "node";
4392
4543
  type = MergeNode;
4393
- icon = "lucide:git-merge";
4544
+ icon = "lucide:merge@rot=90";
4394
4545
  constructor(name, cfg = { mode: "passThrough" }, id) {
4395
4546
  this.name = name;
4396
4547
  this.cfg = cfg;
@@ -4415,6 +4566,7 @@ var NoOp = class {
4415
4566
  kind = "node";
4416
4567
  type = NoOpNode;
4417
4568
  execution = { hint: "local" };
4569
+ icon = "lucide:circle-dashed";
4418
4570
  constructor(name = "NoOp", id) {
4419
4571
  this.name = name;
4420
4572
  this.id = id;
@@ -4520,6 +4672,7 @@ var Wait = class {
4520
4672
  execution = { hint: "local" };
4521
4673
  /** Pass-through empty batches should still advance to downstream nodes. */
4522
4674
  continueWhenEmptyOutput = true;
4675
+ icon = "lucide:hourglass";
4523
4676
  constructor(name, milliseconds, id) {
4524
4677
  this.name = name;
4525
4678
  this.milliseconds = milliseconds;
@@ -4570,7 +4723,7 @@ WebhookTriggerNode = __decorate([(0, __codemation_core.node)({ packageName: "@co
4570
4723
  var WebhookTrigger = class WebhookTrigger {
4571
4724
  kind = "trigger";
4572
4725
  type = WebhookTriggerNode;
4573
- icon = "lucide:globe";
4726
+ icon = "lucide:webhook";
4574
4727
  constructor(name, args, handler = WebhookTrigger.defaultHandler, id) {
4575
4728
  this.name = name;
4576
4729
  this.args = args;