@codemation/core 0.8.1 → 1.0.1

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 (59) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-kxQA5NLt.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineWorkflowRunnerService-Ba2AvBnL.d.cts} +2 -2
  4. package/dist/{InMemoryRunDataFactory-1iz7_SnO.d.cts → InMemoryRunDataFactory-Ou4tQUOS.d.cts} +2 -2
  5. package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-Dyh_dH0k.d.cts} +528 -467
  6. package/dist/{RunIntentService-BqhmdoA1.d.ts → RunIntentService-dteLjNiT.d.ts} +568 -462
  7. package/dist/bootstrap/index.cjs +2 -2
  8. package/dist/bootstrap/index.d.cts +3 -3
  9. package/dist/bootstrap/index.d.ts +3 -3
  10. package/dist/bootstrap/index.js +2 -2
  11. package/dist/{bootstrap-BoknFKnw.js → bootstrap-CL68rqWg.js} +3 -2
  12. package/dist/bootstrap-CL68rqWg.js.map +1 -0
  13. package/dist/{bootstrap-Bx1u4cbS.cjs → bootstrap-Cko6udwL.cjs} +3 -2
  14. package/dist/bootstrap-Cko6udwL.cjs.map +1 -0
  15. package/dist/{index-CVs9rVhl.d.ts → index-CyfGTfU1.d.ts} +84 -14
  16. package/dist/index.cjs +10 -11
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +129 -14
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +2 -11
  21. package/dist/index.js.map +1 -1
  22. package/dist/{runtime-DUW6tIJ1.js → runtime-284ok0cm.js} +201 -31
  23. package/dist/runtime-284ok0cm.js.map +1 -0
  24. package/dist/{runtime-Dvo2ru5A.cjs → runtime-B3Og-_St.cjs} +223 -29
  25. package/dist/runtime-B3Og-_St.cjs.map +1 -0
  26. package/dist/testing.cjs +2 -2
  27. package/dist/testing.d.cts +2 -2
  28. package/dist/testing.d.ts +2 -2
  29. package/dist/testing.js +2 -2
  30. package/package.json +1 -1
  31. package/src/ai/AiHost.ts +42 -14
  32. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +4 -0
  33. package/src/browser.ts +1 -0
  34. package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
  35. package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
  36. package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
  37. package/src/contracts/executionPersistenceContracts.ts +30 -0
  38. package/src/contracts/runTypes.ts +10 -0
  39. package/src/contracts/runtimeTypes.ts +8 -0
  40. package/src/contracts/telemetryTypes.ts +8 -0
  41. package/src/contracts/workflowTypes.ts +6 -0
  42. package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
  43. package/src/events/index.ts +1 -0
  44. package/src/events/runEvents.ts +25 -0
  45. package/src/execution/ChildExecutionScopeFactory.ts +58 -0
  46. package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
  47. package/src/execution/NodeExecutor.ts +10 -2
  48. package/src/execution/NodeRunStateWriter.ts +7 -0
  49. package/src/execution/NodeRunStateWriterFactory.ts +7 -0
  50. package/src/execution/index.ts +1 -0
  51. package/src/index.ts +1 -0
  52. package/src/orchestration/RunStartService.ts +8 -1
  53. package/src/runtime/EngineFactory.ts +1 -0
  54. package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
  55. package/src/workflow/index.ts +1 -0
  56. package/dist/bootstrap-BoknFKnw.js.map +0 -1
  57. package/dist/bootstrap-Bx1u4cbS.cjs.map +0 -1
  58. package/dist/runtime-DUW6tIJ1.js.map +0 -1
  59. package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
package/dist/testing.cjs CHANGED
@@ -1,6 +1,6 @@
1
- const require_runtime = require('./runtime-Dvo2ru5A.cjs');
1
+ const require_runtime = require('./runtime-B3Og-_St.cjs');
2
2
  const require_InMemoryRunEventBusRegistry = require('./InMemoryRunEventBusRegistry-B0_C4OnP.cjs');
3
- const require_bootstrap = require('./bootstrap-Bx1u4cbS.cjs');
3
+ const require_bootstrap = require('./bootstrap-Cko6udwL.cjs');
4
4
  let tsyringe = require("tsyringe");
5
5
  tsyringe = require_runtime.__toESM(tsyringe);
6
6
 
@@ -1,5 +1,5 @@
1
- import { A as NodeOutputs, E as NodeId, F as ParentExecutionRef, G as TriggerNodeConfig, H as RunnableNodeConfig, Ht as NodeExecutionRequest, Jr as CredentialSessionService, R as RunDataFactory, Vt as NodeExecutionContext, Wt as NodeExecutionScheduler, Xt as RunnableNode, Y as WorkflowDefinition, Zt as RunnableNodeExecuteArgs, ci as TypeToken, en as TriggerNode, f as Items, in as TriggerSetupStateRepository, jt as ExecutionContextFactory, k as NodeOffloadPolicy, ln as WorkflowRunnerService, n as InMemoryLiveWorkflowRepository, ni as Container, nn as TriggerSetupContext, qn as RunResult, r as Engine, t as RunIntentService, tr as WorkflowExecutionRepository, tt as WorkflowId, u as Item, wi as RunEventBus, xi as EngineExecutionLimitsPolicy } from "./RunIntentService-S-1lW-gS.cjs";
2
- import { a as WorkflowSnapshotCodec, n as EngineRuntimeRegistrationOptions, t as EngineWorkflowRunnerService } from "./EngineWorkflowRunnerService-DzOCa1BW.cjs";
1
+ import { A as NodeOffloadPolicy, E as NodeId, Ei as WorkflowExecutionRepository, Gt as NodeExecutionScheduler, Ht as NodeExecutionContext, I as ParentExecutionRef, K as TriggerNodeConfig, Mt as ExecutionContextFactory, Or as TypeToken, Qt as RunnableNodeExecuteArgs, Sr as Container, U as RunnableNodeConfig, Ut as NodeExecutionRequest, Vr as EngineExecutionLimitsPolicy, Wr as RunEventBus, X as WorkflowDefinition, Zt as RunnableNode, an as TriggerSetupStateRepository, f as Items, j as NodeOutputs, mr as CredentialSessionService, n as InMemoryLiveWorkflowRepository, nt as WorkflowId, r as Engine, rn as TriggerSetupContext, t as RunIntentService, tn as TriggerNode, u as Item, un as WorkflowRunnerService, vi as RunResult, z as RunDataFactory } from "./RunIntentService-Dyh_dH0k.cjs";
2
+ import { a as WorkflowSnapshotCodec, n as EngineRuntimeRegistrationOptions, t as EngineWorkflowRunnerService } from "./EngineWorkflowRunnerService-Ba2AvBnL.cjs";
3
3
  import { DependencyContainer, InjectionToken } from "tsyringe";
4
4
  import { ZodType } from "zod";
5
5
 
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { $n as NodeExecutionContext, Ht as NodeOutputs, Ia as RunEventBus, Na as EngineExecutionLimitsPolicy, Rt as NodeId, St as Item, Un as ExecutionContextFactory, Vt as NodeOffloadPolicy, Xt as RunDataFactory, ai as RunResult, ba as TypeToken, cr as RunnableNode, en as RunnableNodeConfig, er as NodeExecutionRequest, fr as TriggerNode, gr as TriggerSetupStateRepository, l as WorkflowSnapshotCodec, lr as RunnableNodeExecuteArgs, ma as Container, mr as TriggerSetupContext, n as InMemoryLiveWorkflowRepository, nr as NodeExecutionScheduler, oa as CredentialSessionService, pi as WorkflowExecutionRepository, pn as WorkflowId, qt as ParentExecutionRef, r as EngineWorkflowRunnerService, rn as TriggerNodeConfig, sn as WorkflowDefinition, t as RunIntentService, u as Engine, wt as Items, xr as WorkflowRunnerService } from "./RunIntentService-BqhmdoA1.js";
2
- import { t as EngineRuntimeRegistrationOptions } from "./EngineRuntimeRegistration.types-BP6tsaNP.js";
1
+ import { $t as RunDataFactory, Ba as WorkflowExecutionRepository, Bt as NodeId, Et as Items, Gt as NodeOutputs, Hi as TypeToken, Ii as Container, Kn as ExecutionContextFactory, Ma as RunResult, Oi as CredentialSessionService, Wt as NodeOffloadPolicy, Xt as ParentExecutionRef, _r as TriggerSetupContext, ar as NodeExecutionScheduler, dr as RunnableNode, fr as RunnableNodeExecuteArgs, gn as WorkflowId, hr as TriggerNode, ia as RunEventBus, l as WorkflowSnapshotCodec, n as InMemoryLiveWorkflowRepository, nr as NodeExecutionContext, on as TriggerNodeConfig, r as EngineWorkflowRunnerService, rn as RunnableNodeConfig, rr as NodeExecutionRequest, t as RunIntentService, ta as EngineExecutionLimitsPolicy, u as Engine, un as WorkflowDefinition, wr as WorkflowRunnerService, wt as Item, yr as TriggerSetupStateRepository } from "./RunIntentService-dteLjNiT.js";
2
+ import { t as EngineRuntimeRegistrationOptions } from "./EngineRuntimeRegistration.types-kxQA5NLt.js";
3
3
  import { DependencyContainer, InjectionToken } from "tsyringe";
4
4
  import { ZodType } from "zod";
5
5
 
package/dist/testing.js CHANGED
@@ -1,6 +1,6 @@
1
- import { A as NodeExecutor, D as PersistedWorkflowTokenRegistry, E as WorkflowSnapshotCodec, F as InProcessRetryRunner, L as DefaultExecutionContextFactory, R as AllWorkflowsActiveWorkflowActivationPolicy, Rt as CoreTokens, T as NodeInstanceFactory, a as InMemoryLiveWorkflowRepository, b as DefaultDrivingScheduler, i as RunIntentService, it as emitPorts, l as Engine, st as DefaultAsyncSleeper, u as InMemoryRunDataFactory, v as InlineDrivingScheduler, y as HintOnlyOffloadPolicy } from "./runtime-DUW6tIJ1.js";
1
+ import { A as NodeExecutor, D as PersistedWorkflowTokenRegistry, E as WorkflowSnapshotCodec, F as InProcessRetryRunner, Ht as CoreTokens, L as DefaultExecutionContextFactory, R as AllWorkflowsActiveWorkflowActivationPolicy, T as NodeInstanceFactory, a as InMemoryLiveWorkflowRepository, b as DefaultDrivingScheduler, i as RunIntentService, it as emitPorts, l as Engine, st as DefaultAsyncSleeper, u as InMemoryRunDataFactory, v as InlineDrivingScheduler, y as HintOnlyOffloadPolicy } from "./runtime-284ok0cm.js";
2
2
  import { n as WorkflowBuilder, t as InMemoryRunEventBus } from "./InMemoryRunEventBusRegistry-C2U83Hmv.js";
3
- import { n as InMemoryWorkflowExecutionRepository, t as EngineRuntimeRegistrar } from "./bootstrap-BoknFKnw.js";
3
+ import { n as InMemoryWorkflowExecutionRepository, t as EngineRuntimeRegistrar } from "./bootstrap-CL68rqWg.js";
4
4
  import { container } from "tsyringe";
5
5
 
6
6
  //#region src/testing/RejectingCredentialSessionService.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemation/core",
3
- "version": "0.8.1",
3
+ "version": "1.0.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/ai/AiHost.ts CHANGED
@@ -37,6 +37,15 @@ export type ToolExecuteArgs<TConfig extends ToolConfig = ToolConfig, TInput = un
37
37
  item: Item;
38
38
  itemIndex: number;
39
39
  items: Items;
40
+ /**
41
+ * Optional sub-agent boundary hooks: when present, the live `agent.tool.call` span and the
42
+ * planned tool-call invocationId are forwarded so node-backed runtimes can re-root their child
43
+ * execution scope. Plain function tools may safely ignore these hooks.
44
+ */
45
+ hooks?: Readonly<{
46
+ parentSpan?: import("../contracts/telemetryTypes").TelemetrySpanScope;
47
+ parentInvocationId?: import("../contracts/runTypes").ConnectionInvocationId;
48
+ }>;
40
49
  }>;
41
50
 
42
51
  export interface Tool<
@@ -142,30 +151,49 @@ export interface ChatModelConfig {
142
151
  getCredentialRequirements?(): ReadonlyArray<CredentialRequirement>;
143
152
  }
144
153
 
145
- export interface LangChainChatModelLike {
146
- invoke(input: unknown, options?: unknown): Promise<unknown>;
147
- bindTools?(tools: ReadonlyArray<unknown>): LangChainChatModelLike;
148
- withStructuredOutput?(
149
- outputSchema: ZodSchemaAny,
150
- config?: ChatModelStructuredOutputOptions,
151
- ): LangChainStructuredOutputModelLike;
154
+ /**
155
+ * Provider-neutral chat language model wrapper returned by a {@link ChatModelFactory}.
156
+ *
157
+ * Thin adapter around an AI SDK `LanguageModelV2` (from `@ai-sdk/provider`) plus the call-site
158
+ * defaults Codemation needs at every generate/stream: the provider label, the model name used for
159
+ * pricing / telemetry, and the default invocation options (max output tokens, temperature,
160
+ * provider-specific overrides).
161
+ *
162
+ * The consumer (AIAgentNode / AgentStructuredOutputRunner) passes `languageModel` directly into
163
+ * `generateText({ model, ... })` from the `ai` package.
164
+ */
165
+ export interface ChatLanguageModel {
166
+ /** AI SDK `LanguageModelV2` instance (kept `unknown` to avoid leaking the SDK type into `@codemation/core`). */
167
+ readonly languageModel: unknown;
168
+ /** Stable pricing/telemetry key — e.g. `"gpt-4.1-nano"`. */
169
+ readonly modelName: string;
170
+ /** Provider label — e.g. `"openai"`. Used for cost tracking. */
171
+ readonly provider?: string;
172
+ /** Defaults merged into every call. Consumers may override per-invocation. */
173
+ readonly defaultCallOptions?: ChatLanguageModelCallOptions;
152
174
  }
153
175
 
154
- export interface LangChainStructuredOutputModelLike {
155
- invoke(input: unknown, options?: unknown): Promise<unknown>;
176
+ export interface ChatLanguageModelCallOptions {
177
+ readonly maxOutputTokens?: number;
178
+ readonly temperature?: number;
179
+ readonly providerOptions?: Readonly<Record<string, Readonly<Record<string, JsonValue>>>>;
156
180
  }
157
181
 
158
- export interface ChatModelStructuredOutputOptions {
159
- readonly method?: "jsonSchema" | "functionCalling" | "jsonMode";
182
+ /**
183
+ * Options for a structured-output generate call. Mirrors
184
+ * `generateText({ output: Output.object(...) })` from the `ai` package.
185
+ */
186
+ export interface StructuredOutputOptions {
187
+ /** Optional schema name — used by some providers as the JSON schema name attribute. */
188
+ readonly schemaName?: string;
189
+ /** When `true`, the consumer should pass a strict-mode-compatible JSON Schema record. */
160
190
  readonly strict?: boolean;
161
- readonly includeRaw?: boolean;
162
- readonly tools?: ReadonlyArray<unknown>;
163
191
  }
164
192
 
165
193
  export interface ChatModelFactory<TConfig extends ChatModelConfig = ChatModelConfig> {
166
194
  create(
167
195
  args: Readonly<{ config: TConfig; ctx: NodeExecutionContext<any> }>,
168
- ): Promise<LangChainChatModelLike> | LangChainChatModelLike;
196
+ ): Promise<ChatLanguageModel> | ChatLanguageModel;
169
197
  }
170
198
 
171
199
  export type NodeBackedToolInputMapperArgs<
@@ -2,6 +2,7 @@ import { instanceCachingFactory, type DependencyContainer } from "../../di";
2
2
  import { CoreTokens } from "../../di";
3
3
  import { EngineExecutionLimitsPolicyFactory } from "../../policies/executionLimits/EngineExecutionLimitsPolicyFactory";
4
4
  import {
5
+ ChildExecutionScopeFactory,
5
6
  DefaultAsyncSleeper,
6
7
  InProcessRetryRunnerFactory,
7
8
  ItemExprResolver,
@@ -50,6 +51,9 @@ export class EngineRuntimeRegistrar {
50
51
  if (!container.isRegistered(RunnableOutputBehaviorResolver, true)) {
51
52
  container.registerSingleton(RunnableOutputBehaviorResolver, RunnableOutputBehaviorResolver);
52
53
  }
54
+ if (!container.isRegistered(ChildExecutionScopeFactory, true)) {
55
+ container.registerSingleton(ChildExecutionScopeFactory, ChildExecutionScopeFactory);
56
+ }
53
57
  container.registerSingleton(EngineExecutionLimitsPolicyFactory, EngineExecutionLimitsPolicyFactory);
54
58
  container.registerSingleton(NodeInstanceFactoryFactory, NodeInstanceFactoryFactory);
55
59
  container.registerSingleton(DefaultAsyncSleeper, DefaultAsyncSleeper);
package/src/browser.ts CHANGED
@@ -9,6 +9,7 @@ export type {
9
9
  } from "./ai/AgentConnectionNodeCollector";
10
10
  export type { AgentNodeConfig } from "./ai/AiHost";
11
11
  export { ConnectionNodeIdFactory } from "./workflow/definition/ConnectionNodeIdFactory";
12
+ export { NodeIterationIdFactory } from "./workflow/definition/NodeIterationIdFactory";
12
13
  export * from "./contracts/credentialTypes";
13
14
  export * from "./contracts/runtimeTypes";
14
15
  export * from "./contracts/runFinishedAtFactory";
@@ -9,4 +9,10 @@ export class CodemationTelemetryAttributeNames {
9
9
  static readonly connectionInvocationId = "codemation.connection.invocation_id";
10
10
  static readonly toolName = "codemation.tool.name";
11
11
  static readonly traceParentRunId = "codemation.parent.run.id";
12
+ /** Per-item iteration that emitted this span/metric. Set on spans recorded inside a runnable per-item loop. */
13
+ static readonly iterationId = "codemation.iteration.id";
14
+ /** Item index (0-based) of the iteration. */
15
+ static readonly iterationIndex = "codemation.iteration.index";
16
+ /** Set when this span/metric was recorded under a sub-agent triggered by an outer LLM/tool call. */
17
+ static readonly parentInvocationId = "codemation.parent.invocation_id";
12
18
  }
@@ -1,15 +1,6 @@
1
- import type { NodeActivationId, NodeId } from "./workflowTypes";
2
- import type { NodeExecutionTelemetry, TelemetryChildSpanStart, TelemetrySpanScope } from "./telemetryTypes";
1
+ import type { NodeExecutionTelemetry } from "./telemetryTypes";
3
2
  import { NoOpTelemetrySpanScope } from "./NoOpTelemetrySpanScope";
4
3
 
5
4
  export class NoOpNodeExecutionTelemetry {
6
- static readonly value: NodeExecutionTelemetry = {
7
- ...NoOpTelemetrySpanScope.value,
8
- forNode(_: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>): NodeExecutionTelemetry {
9
- return NoOpNodeExecutionTelemetry.value;
10
- },
11
- startChildSpan(_: TelemetryChildSpanStart): TelemetrySpanScope {
12
- return NoOpTelemetrySpanScope.value;
13
- },
14
- };
5
+ static readonly value: NodeExecutionTelemetry = NoOpTelemetrySpanScope.nodeExecutionTelemetryValue;
15
6
  }
@@ -1,6 +1,9 @@
1
+ import type { NodeActivationId, NodeId } from "./workflowTypes";
1
2
  import type {
3
+ NodeExecutionTelemetry,
2
4
  TelemetryArtifactAttachment,
3
5
  TelemetryArtifactReference,
6
+ TelemetryChildSpanStart,
4
7
  TelemetryMetricRecord,
5
8
  TelemetrySpanEnd,
6
9
  TelemetrySpanEventRecord,
@@ -8,15 +11,48 @@ import type {
8
11
  } from "./telemetryTypes";
9
12
  import { NoOpTelemetryArtifactReference } from "./NoOpTelemetryArtifactReference";
10
13
 
14
+ /**
15
+ * Standalone no-op {@link NodeExecutionTelemetry} value used as the return for `asNodeTelemetry`.
16
+ *
17
+ * Defined here (instead of in `NoOpNodeExecutionTelemetry.ts`) so that {@link NoOpTelemetrySpanScope}
18
+ * can return it without importing the other module — both no-ops share this leaf.
19
+ */
20
+ const noOpNodeExecutionTelemetry: NodeExecutionTelemetry = {
21
+ traceId: "00000000000000000000000000000000",
22
+ spanId: "0000000000000000",
23
+ addSpanEvent(_: TelemetrySpanEventRecord): void {},
24
+ recordMetric(_: TelemetryMetricRecord): void {},
25
+ attachArtifact(_: TelemetryArtifactAttachment): TelemetryArtifactReference {
26
+ return NoOpTelemetryArtifactReference.value;
27
+ },
28
+ end(_: TelemetrySpanEnd = {}): void {},
29
+ asNodeTelemetry(_: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>): NodeExecutionTelemetry {
30
+ return noOpNodeExecutionTelemetry;
31
+ },
32
+ forNode(_: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>): NodeExecutionTelemetry {
33
+ return noOpNodeExecutionTelemetry;
34
+ },
35
+ startChildSpan(_: TelemetryChildSpanStart): TelemetrySpanScope {
36
+ return noOpTelemetrySpanScope;
37
+ },
38
+ };
39
+
40
+ const noOpTelemetrySpanScope: TelemetrySpanScope = {
41
+ traceId: "00000000000000000000000000000000",
42
+ spanId: "0000000000000000",
43
+ addSpanEvent(_: TelemetrySpanEventRecord): void {},
44
+ recordMetric(_: TelemetryMetricRecord): void {},
45
+ attachArtifact(_: TelemetryArtifactAttachment): TelemetryArtifactReference {
46
+ return NoOpTelemetryArtifactReference.value;
47
+ },
48
+ end(_: TelemetrySpanEnd = {}): void {},
49
+ asNodeTelemetry(_: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>): NodeExecutionTelemetry {
50
+ return noOpNodeExecutionTelemetry;
51
+ },
52
+ };
53
+
11
54
  export class NoOpTelemetrySpanScope {
12
- static readonly value: TelemetrySpanScope = {
13
- traceId: "00000000000000000000000000000000",
14
- spanId: "0000000000000000",
15
- addSpanEvent(_: TelemetrySpanEventRecord): void {},
16
- recordMetric(_: TelemetryMetricRecord): void {},
17
- attachArtifact(_: TelemetryArtifactAttachment): TelemetryArtifactReference {
18
- return NoOpTelemetryArtifactReference.value;
19
- },
20
- end(_: TelemetrySpanEnd = {}): void {},
21
- };
55
+ static readonly value: TelemetrySpanScope = noOpTelemetrySpanScope;
56
+ /** Internal: the shared no-op {@link NodeExecutionTelemetry} that {@link NoOpNodeExecutionTelemetry} re-exposes. */
57
+ static readonly nodeExecutionTelemetryValue: NodeExecutionTelemetry = noOpNodeExecutionTelemetry;
22
58
  }
@@ -148,6 +148,30 @@ export interface WorkflowRunDetailDto {
148
148
  readonly mutableState?: PersistedMutableRunState;
149
149
  readonly slotStates: ReadonlyArray<SlotExecutionStateDto>;
150
150
  readonly executionInstances: ReadonlyArray<ExecutionInstanceDto>;
151
+ readonly iterations?: ReadonlyArray<RunIterationDto>;
152
+ }
153
+
154
+ /**
155
+ * Per-item iteration projected from connection invocations and node activations.
156
+ *
157
+ * One iteration = one item processed by an agent within an activation. Multiple invocations
158
+ * (LLM rounds, tool calls) belonging to the same iteration share the iterationId.
159
+ */
160
+ export interface RunIterationDto {
161
+ readonly iterationId: string;
162
+ readonly agentNodeId: NodeId;
163
+ readonly activationId: NodeActivationId;
164
+ readonly itemIndex: number;
165
+ readonly itemSummary?: string;
166
+ readonly status: NodeExecutionStatus;
167
+ readonly startedAt?: string;
168
+ readonly finishedAt?: string;
169
+ readonly invocationIds: ReadonlyArray<string>;
170
+ readonly parentInvocationId?: string;
171
+ /** Estimated cost rolled up from telemetry cost metric points, keyed by ISO currency code (e.g. "USD"). Values are minor units (cents-of-cents per the metric's `cost.currency_scale`). */
172
+ readonly estimatedCostMinorByCurrency?: Readonly<Record<string, number>>;
173
+ /** Currency scale (denominator) per currency, when present on the metric points. Joined with `estimatedCostMinorByCurrency` to format human-readable amounts. */
174
+ readonly estimatedCostCurrencyScaleByCurrency?: Readonly<Record<string, number>>;
151
175
  }
152
176
 
153
177
  export interface SlotExecutionStateDto {
@@ -178,6 +202,12 @@ export interface ExecutionInstanceDto {
178
202
  readonly inputJson?: JsonValue;
179
203
  readonly outputJson?: JsonValue;
180
204
  readonly error?: Readonly<NodeExecutionError>;
205
+ /** Per-item iteration that produced this instance. Set on connectionInvocation rows produced inside per-item runnable loops. */
206
+ readonly iterationId?: string;
207
+ /** Item index (0-based) of the iteration. */
208
+ readonly itemIndex?: number;
209
+ /** Parent invocation id when this instance was emitted by a sub-agent triggered by an outer LLM/tool call. */
210
+ readonly parentInvocationId?: string;
181
211
  }
182
212
 
183
213
  export interface WorkflowDetailSelectionState {
@@ -7,6 +7,7 @@ import type {
7
7
  JsonValue,
8
8
  NodeActivationId,
9
9
  NodeId,
10
+ NodeIterationId,
10
11
  NodeKind,
11
12
  NodeOutputs,
12
13
  OutputPortKey,
@@ -154,6 +155,12 @@ export interface ConnectionInvocationRecord {
154
155
  readonly startedAt?: string;
155
156
  readonly finishedAt?: string;
156
157
  readonly updatedAt: string;
158
+ /** Per-item iteration id minted by the engine when this invocation occurred inside a runnable node's per-item loop. */
159
+ readonly iterationId?: NodeIterationId;
160
+ /** Item index (0-based) of the iteration that produced this invocation. */
161
+ readonly itemIndex?: number;
162
+ /** When set, this invocation was produced inside a sub-agent triggered by the named parent invocation. */
163
+ readonly parentInvocationId?: ConnectionInvocationId;
157
164
  }
158
165
 
159
166
  /** Arguments for appending a {@link ConnectionInvocationRecord} (engine fills run/workflow ids and timestamps). */
@@ -169,6 +176,9 @@ export type ConnectionInvocationAppendArgs = Readonly<{
169
176
  queuedAt?: string;
170
177
  startedAt?: string;
171
178
  finishedAt?: string;
179
+ iterationId?: NodeIterationId;
180
+ itemIndex?: number;
181
+ parentInvocationId?: ConnectionInvocationId;
172
182
  }>;
173
183
 
174
184
  export interface RunCurrentState {
@@ -5,6 +5,7 @@ import type { CredentialSessionService } from "./credentialTypes";
5
5
  import type { ExecutionTelemetry, ExecutionTelemetryFactory, NodeExecutionTelemetry } from "./telemetryTypes";
6
6
  import type {
7
7
  ConnectionInvocationAppendArgs,
8
+ ConnectionInvocationId,
8
9
  NodeInputsByPort,
9
10
  PersistedWorkflowSnapshot,
10
11
  PersistedWorkflowTokenRegistryLike,
@@ -25,6 +26,7 @@ import type {
25
26
  NodeActivationId,
26
27
  NodeConfigBase,
27
28
  NodeId,
29
+ NodeIterationId,
28
30
  NodeOutputs,
29
31
  RunnableNodeConfig,
30
32
  OutputPortKey,
@@ -154,6 +156,12 @@ export interface ExecutionContext {
154
156
  telemetry: ExecutionTelemetry;
155
157
  binary: ExecutionBinaryService;
156
158
  getCredential<TSession = unknown>(slotKey: string): Promise<TSession>;
159
+ /** Per-item iteration id, set by {@link NodeExecutor} on the ctx passed into runnable `execute`. */
160
+ iterationId?: NodeIterationId;
161
+ /** Item index (0-based) within the current activation's batch; set alongside {@link iterationId}. */
162
+ itemIndex?: number;
163
+ /** When set, this ctx is executing inside a sub-agent triggered by the named parent invocation. */
164
+ parentInvocationId?: ConnectionInvocationId;
157
165
  }
158
166
 
159
167
  export interface ExecutionContextFactory {
@@ -73,6 +73,14 @@ export interface TelemetrySpanScope extends TelemetryScope {
73
73
  readonly traceId: string;
74
74
  readonly spanId: string;
75
75
  end(args?: TelemetrySpanEnd): Promise<void> | void;
76
+ /**
77
+ * Lift this span into a {@link NodeExecutionTelemetry} scoped to a different (nodeId, activationId).
78
+ * Children created via the returned telemetry's `startChildSpan` get this span as their parent.
79
+ *
80
+ * Used at the sub-agent boundary so that nested runtime telemetry parents under the agent.tool.call
81
+ * span instead of the orchestrator's node-level span.
82
+ */
83
+ asNodeTelemetry(args: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>): NodeExecutionTelemetry;
76
84
  }
77
85
 
78
86
  export interface NodeExecutionTelemetry extends ExecutionTelemetry, TelemetrySpanScope {
@@ -194,6 +194,12 @@ export type NodeOutputs = Partial<Record<OutputPortKey, Items>>;
194
194
 
195
195
  export type RunId = string;
196
196
  export type NodeActivationId = string;
197
+ /**
198
+ * One per-item iteration of a runnable node's execute loop. Refines `NodeActivationId` for
199
+ * per-item connection invocations and telemetry. Undefined when the executing node is a batch
200
+ * node or trigger that does not iterate items.
201
+ */
202
+ export type NodeIterationId = string;
197
203
 
198
204
  export interface ParentExecutionRef {
199
205
  runId: RunId;
@@ -0,0 +1,46 @@
1
+ import type { ConnectionInvocationRecord } from "../contracts/runTypes";
2
+ import type { ParentExecutionRef } from "../types";
3
+ import type { RunEventBus } from "./runEvents";
4
+
5
+ /**
6
+ * Publishes per-invocation lifecycle records onto the run {@link RunEventBus}.
7
+ *
8
+ * Surgical, per-invocation events let the UI update the right-side inspector
9
+ * timeline as each LLM round / tool call transitions through `running` → `completed`
10
+ * (or `failed`) without depending on a coarse `runSaved` poll.
11
+ */
12
+ export class ConnectionInvocationEventPublisher {
13
+ constructor(
14
+ private readonly eventBus: RunEventBus | undefined,
15
+ private readonly parent: ParentExecutionRef | undefined,
16
+ ) {}
17
+
18
+ async publish(record: ConnectionInvocationRecord): Promise<void> {
19
+ if (!this.eventBus) return;
20
+ const kind = this.kindFor(record);
21
+ if (!kind) return;
22
+ await this.eventBus.publish({
23
+ kind,
24
+ runId: record.runId,
25
+ workflowId: record.workflowId,
26
+ parent: this.parent,
27
+ at: record.updatedAt,
28
+ record,
29
+ });
30
+ }
31
+
32
+ private kindFor(
33
+ record: ConnectionInvocationRecord,
34
+ ): "connectionInvocationStarted" | "connectionInvocationCompleted" | "connectionInvocationFailed" | undefined {
35
+ if (record.status === "running" || record.status === "queued") {
36
+ return "connectionInvocationStarted";
37
+ }
38
+ if (record.status === "completed") {
39
+ return "connectionInvocationCompleted";
40
+ }
41
+ if (record.status === "failed") {
42
+ return "connectionInvocationFailed";
43
+ }
44
+ return undefined;
45
+ }
46
+ }
@@ -1,3 +1,4 @@
1
+ export { ConnectionInvocationEventPublisher } from "./ConnectionInvocationEventPublisher";
1
2
  export { NodeEventPublisher } from "./NodeEventPublisher";
2
3
  export { InMemoryRunEventBus } from "./InMemoryRunEventBusRegistry";
3
4
  export { EventPublishingWorkflowExecutionRepository } from "./EventPublishingWorkflowExecutionRepository";
@@ -1,3 +1,4 @@
1
+ import type { ConnectionInvocationRecord } from "../contracts/runTypes";
1
2
  import type { NodeExecutionSnapshot, ParentExecutionRef, PersistedRunState, RunId, WorkflowId } from "../types";
2
3
 
3
4
  export type RunEvent =
@@ -41,6 +42,30 @@ export type RunEvent =
41
42
  parent?: ParentExecutionRef;
42
43
  at: string;
43
44
  snapshot: NodeExecutionSnapshot;
45
+ }>
46
+ | Readonly<{
47
+ kind: "connectionInvocationStarted";
48
+ runId: RunId;
49
+ workflowId: WorkflowId;
50
+ parent?: ParentExecutionRef;
51
+ at: string;
52
+ record: ConnectionInvocationRecord;
53
+ }>
54
+ | Readonly<{
55
+ kind: "connectionInvocationCompleted";
56
+ runId: RunId;
57
+ workflowId: WorkflowId;
58
+ parent?: ParentExecutionRef;
59
+ at: string;
60
+ record: ConnectionInvocationRecord;
61
+ }>
62
+ | Readonly<{
63
+ kind: "connectionInvocationFailed";
64
+ runId: RunId;
65
+ workflowId: WorkflowId;
66
+ parent?: ParentExecutionRef;
67
+ at: string;
68
+ record: ConnectionInvocationRecord;
44
69
  }>;
45
70
 
46
71
  export interface RunEventSubscription {
@@ -0,0 +1,58 @@
1
+ import { CoreTokens } from "../di/CoreTokens";
2
+ import { inject, injectable } from "../di";
3
+ import type {
4
+ ActivationIdFactory,
5
+ ConnectionInvocationId,
6
+ NodeExecutionContext,
7
+ NodeId,
8
+ RunnableNodeConfig,
9
+ TelemetrySpanScope,
10
+ } from "../types";
11
+
12
+ /**
13
+ * Builds a re-rooted child execution context for sub-agent (and other deeply-nested) invocations.
14
+ *
15
+ * At the orchestrator's `agent.tool.call` boundary the inner runtime needs a ctx whose:
16
+ * - `nodeId` is the tool's connection node id (so inner LLM/tool connection ids derive correctly),
17
+ * - `activationId` is fresh (so its connection-invocation rows are uniquely identifiable),
18
+ * - `telemetry` parents children under the tool-call span (not the orchestrator's node span),
19
+ * - `binary` is scoped to the new (nodeId, activationId),
20
+ * - `parentInvocationId` points back to the tool-call invocation for downstream lineage.
21
+ */
22
+ @injectable()
23
+ export class ChildExecutionScopeFactory {
24
+ constructor(
25
+ @inject(CoreTokens.ActivationIdFactory)
26
+ private readonly activationIdFactory: ActivationIdFactory,
27
+ ) {}
28
+
29
+ forSubAgent<TConfig extends RunnableNodeConfig<any, any>>(
30
+ args: Readonly<{
31
+ parentCtx: NodeExecutionContext<TConfig>;
32
+ childNodeId: NodeId;
33
+ childConfig: TConfig;
34
+ parentInvocationId: ConnectionInvocationId;
35
+ parentSpan: TelemetrySpanScope;
36
+ }>,
37
+ ): NodeExecutionContext<TConfig> {
38
+ const childActivationId = this.activationIdFactory.makeActivationId();
39
+ const childTelemetry = args.parentSpan.asNodeTelemetry({
40
+ nodeId: args.childNodeId,
41
+ activationId: childActivationId,
42
+ });
43
+ const childBinary = args.parentCtx.binary.forNode({
44
+ nodeId: args.childNodeId,
45
+ activationId: childActivationId,
46
+ });
47
+ return {
48
+ ...args.parentCtx,
49
+ nodeId: args.childNodeId,
50
+ activationId: childActivationId,
51
+ config: args.childConfig,
52
+ telemetry: childTelemetry,
53
+ binary: childBinary,
54
+ parentInvocationId: args.parentInvocationId,
55
+ iterationId: undefined,
56
+ };
57
+ }
58
+ }
@@ -62,6 +62,15 @@ export class ExecutionTelemetryCostTrackingDecoratorFactory {
62
62
  costTracking: args.costTracking.forScope(nodeTelemetry),
63
63
  });
64
64
  },
65
+ asNodeTelemetry: (
66
+ rescope: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>,
67
+ ): NodeExecutionTelemetry => {
68
+ const nodeTelemetry = args.telemetry.asNodeTelemetry(rescope);
69
+ return this.decorateNodeExecutionTelemetry({
70
+ telemetry: nodeTelemetry,
71
+ costTracking: args.costTracking.forScope(nodeTelemetry),
72
+ });
73
+ },
65
74
  };
66
75
  }
67
76
 
@@ -79,6 +88,15 @@ export class ExecutionTelemetryCostTrackingDecoratorFactory {
79
88
  artifact: TelemetryArtifactAttachment,
80
89
  ): Promise<TelemetryArtifactReference> | TelemetryArtifactReference => args.scope.attachArtifact(artifact),
81
90
  end: (endArgs?: TelemetrySpanEnd) => args.scope.end(endArgs),
91
+ asNodeTelemetry: (
92
+ rescope: Readonly<{ nodeId: NodeId; activationId: NodeActivationId }>,
93
+ ): NodeExecutionTelemetry => {
94
+ const nodeTelemetry = args.scope.asNodeTelemetry(rescope);
95
+ return this.decorateNodeExecutionTelemetry({
96
+ telemetry: nodeTelemetry,
97
+ costTracking: args.costTracking.forScope(nodeTelemetry),
98
+ });
99
+ },
82
100
  };
83
101
  }
84
102
  }
@@ -13,6 +13,7 @@ import type {
13
13
  TriggerNode,
14
14
  WorkflowNodeInstanceFactory,
15
15
  } from "../types";
16
+ import { NodeIterationIdFactory } from "../workflow/definition/NodeIterationIdFactory";
16
17
 
17
18
  import { FanInMergeByOriginMerger } from "./FanInMergeByOriginMerger";
18
19
  import { ItemExprResolver } from "./ItemExprResolver";
@@ -158,13 +159,20 @@ export class NodeExecutor {
158
159
  const parsed = inputSchema.parse(item.json);
159
160
  const runnableCtx = request.ctx as NodeExecutionContext<RunnableNodeConfig>;
160
161
  const resolvedCtx = await this.itemExprResolver.resolveConfigForItem(runnableCtx, item, i, inputBatch);
161
- const ctx = this.pickExecutionContext(runnableCtx, resolvedCtx);
162
+ const baseCtx = this.pickExecutionContext(runnableCtx, resolvedCtx);
163
+ // Mint a per-item iteration id and stamp it (with the item index) onto the ctx so connection
164
+ // invocations and telemetry written from inside `node.execute` carry the per-item identity.
165
+ const iterationCtx = {
166
+ ...baseCtx,
167
+ iterationId: NodeIterationIdFactory.create(),
168
+ itemIndex: i,
169
+ } as NodeExecutionContext<RunnableNodeConfig>;
162
170
  const args: RunnableNodeExecuteArgs = {
163
171
  input: parsed,
164
172
  item,
165
173
  itemIndex: i,
166
174
  items: inputBatch,
167
- ctx,
175
+ ctx: iterationCtx,
168
176
  };
169
177
  const raw = await Promise.resolve(node.execute(args));
170
178
  const normalized = this.outputNormalizer.normalizeExecuteResult({
@@ -28,6 +28,7 @@ export class NodeRunStateWriter implements NodeExecutionStatePublisher {
28
28
  kind: "nodeQueued" | "nodeStarted" | "nodeCompleted" | "nodeFailed",
29
29
  snapshot: NodeExecutionSnapshot,
30
30
  ) => Promise<void>,
31
+ private readonly publishConnectionInvocationEvent?: (record: ConnectionInvocationRecord) => Promise<void>,
31
32
  ) {}
32
33
 
33
34
  markQueued(args: {
@@ -148,11 +149,17 @@ export class NodeRunStateWriter implements NodeExecutionStatePublisher {
148
149
  startedAt: args.startedAt,
149
150
  finishedAt: args.finishedAt,
150
151
  updatedAt,
152
+ iterationId: args.iterationId,
153
+ itemIndex: args.itemIndex,
154
+ parentInvocationId: args.parentInvocationId,
151
155
  };
152
156
  await this.workflowExecutionRepository.save({
153
157
  ...state,
154
158
  connectionInvocations: [...(state.connectionInvocations ?? []), record],
155
159
  });
160
+ if (this.publishConnectionInvocationEvent) {
161
+ await this.publishConnectionInvocationEvent(record);
162
+ }
156
163
  });
157
164
  }
158
165