@codemation/core 0.8.1 → 0.10.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 (100) hide show
  1. package/CHANGELOG.md +386 -0
  2. package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
  3. package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
  4. package/dist/{InMemoryRunDataFactory-1iz7_SnO.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
  5. package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
  6. package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
  7. package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
  8. package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
  9. package/dist/{RunIntentService-BqhmdoA1.d.ts → RunIntentService-BE9CAkbf.d.ts} +966 -471
  10. package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-siBSjaaY.d.cts} +859 -493
  11. package/dist/bootstrap/index.cjs +5 -2
  12. package/dist/bootstrap/index.d.cts +212 -135
  13. package/dist/bootstrap/index.d.ts +4 -4
  14. package/dist/bootstrap/index.js +3 -3
  15. package/dist/{bootstrap-Bx1u4cbS.cjs → bootstrap-Cm5ruQxx.cjs} +253 -2
  16. package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
  17. package/dist/{bootstrap-BoknFKnw.js → bootstrap-D3r505ko.js} +236 -3
  18. package/dist/bootstrap-D3r505ko.js.map +1 -0
  19. package/dist/{index-CVs9rVhl.d.ts → index-DeLl1Tne.d.ts} +632 -230
  20. package/dist/index.cjs +323 -176
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +544 -91
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +299 -166
  25. package/dist/index.js.map +1 -1
  26. package/dist/{runtime-DUW6tIJ1.js → runtime-BGNbRnqs.js} +934 -75
  27. package/dist/runtime-BGNbRnqs.js.map +1 -0
  28. package/dist/{runtime-Dvo2ru5A.cjs → runtime-DKXJwTNv.cjs} +1028 -73
  29. package/dist/runtime-DKXJwTNv.cjs.map +1 -0
  30. package/dist/testing.cjs +4 -4
  31. package/dist/testing.cjs.map +1 -1
  32. package/dist/testing.d.cts +2 -2
  33. package/dist/testing.d.ts +2 -2
  34. package/dist/testing.js +3 -3
  35. package/package.json +7 -2
  36. package/src/ai/AiHost.ts +42 -14
  37. package/src/authoring/DefinedCollectionRegistry.ts +17 -0
  38. package/src/authoring/defineCollection.types.ts +181 -0
  39. package/src/authoring/definePollingTrigger.types.ts +396 -0
  40. package/src/authoring/definePollingTriggerInternals.ts +74 -0
  41. package/src/authoring/index.ts +19 -0
  42. package/src/bootstrap/index.ts +9 -0
  43. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +8 -0
  44. package/src/browser.ts +1 -0
  45. package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
  46. package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
  47. package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
  48. package/src/contracts/assertionTypes.ts +63 -0
  49. package/src/contracts/baseTypes.ts +12 -0
  50. package/src/contracts/collectionTypes.ts +44 -0
  51. package/src/contracts/credentialTypes.ts +23 -1
  52. package/src/contracts/executionPersistenceContracts.ts +30 -0
  53. package/src/contracts/index.ts +4 -0
  54. package/src/contracts/runTypes.ts +37 -1
  55. package/src/contracts/runtimeTypes.ts +42 -0
  56. package/src/contracts/telemetryTypes.ts +8 -0
  57. package/src/contracts/testTriggerTypes.ts +66 -0
  58. package/src/contracts/workflowTypes.ts +36 -7
  59. package/src/contracts.ts +59 -0
  60. package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
  61. package/src/events/index.ts +1 -0
  62. package/src/events/runEvents.ts +74 -0
  63. package/src/execution/ChildExecutionScopeFactory.ts +55 -0
  64. package/src/execution/DefaultExecutionContextFactory.ts +6 -0
  65. package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
  66. package/src/execution/NodeExecutor.ts +10 -2
  67. package/src/execution/NodeInstanceFactory.ts +13 -1
  68. package/src/execution/NodeInstantiationError.ts +16 -0
  69. package/src/execution/NodeRunStateWriter.ts +7 -0
  70. package/src/execution/NodeRunStateWriterFactory.ts +7 -0
  71. package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
  72. package/src/execution/index.ts +2 -0
  73. package/src/index.ts +8 -0
  74. package/src/orchestration/AbortControllerFactory.ts +9 -0
  75. package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
  76. package/src/orchestration/RunContinuationService.ts +3 -0
  77. package/src/orchestration/RunStartService.ts +122 -3
  78. package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
  79. package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
  80. package/src/orchestration/TriggerRuntimeService.ts +34 -7
  81. package/src/orchestration/index.ts +9 -0
  82. package/src/runtime/EngineFactory.ts +12 -0
  83. package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
  84. package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
  85. package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
  86. package/src/triggers/polling/index.ts +5 -0
  87. package/src/types/index.ts +12 -9
  88. package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
  89. package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
  90. package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
  91. package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
  92. package/src/workflow/index.ts +3 -0
  93. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
  94. package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
  95. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
  96. package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
  97. package/dist/bootstrap-BoknFKnw.js.map +0 -1
  98. package/dist/bootstrap-Bx1u4cbS.cjs.map +0 -1
  99. package/dist/runtime-DUW6tIJ1.js.map +0 -1
  100. package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
@@ -0,0 +1,63 @@
1
+ import type { JsonValue, NodeId } from "./workflowTypes";
2
+
3
+ /**
4
+ * One assertion emitted by an assertion-emitting node (a node whose config sets
5
+ * `emitsAssertions: true`). Each emitted item on `main` carries one of these as `item.json`.
6
+ *
7
+ * Pass/fail is derived from `score >= (passThreshold ?? 0.5)` — see {@link deriveAssertionPassed}.
8
+ * The `errored` marker is for cases where the assertion code itself threw (distinct from
9
+ * "the assertion was evaluated and the score was low") and is treated as a hard fail in rollups
10
+ * regardless of `score`.
11
+ */
12
+ export interface AssertionResult {
13
+ readonly name: string;
14
+ /** 0..1 score. Source of truth for pass/fail (compared against `passThreshold`). */
15
+ readonly score: number;
16
+ /** 0..1 threshold for "passed". When omitted, consumers default to 0.5. */
17
+ readonly passThreshold?: number;
18
+ /** True when evaluating the assertion threw — treated as fail regardless of `score`. */
19
+ readonly errored?: true;
20
+ /** What the assertion expected. Free-form JSON; UIs render with a JSON viewer. */
21
+ readonly expected?: JsonValue;
22
+ /** What the workflow actually produced. */
23
+ readonly actual?: JsonValue;
24
+ /** Short human-readable explanation, especially for fails / errors. */
25
+ readonly message?: string;
26
+ /** Bag of supplemental fields (e.g. judge prompt, judge raw response, comparison method). */
27
+ readonly details?: Readonly<Record<string, JsonValue>>;
28
+ }
29
+
30
+ /**
31
+ * Default {@link AssertionResult.passThreshold} when authors omit it. Boolean-style assertions
32
+ * (assertEqual / contains / etc.) emit `score: 1` or `score: 0` so this default works for them;
33
+ * AI-judge assertions are expected to set their own threshold.
34
+ */
35
+ export const DEFAULT_ASSERTION_PASS_THRESHOLD = 0.5;
36
+
37
+ /**
38
+ * Derive whether an assertion result is considered "passing" using the score-based contract:
39
+ * `errored` always fails, otherwise `score >= (passThreshold ?? 0.5)`. This is the canonical
40
+ * derivation — UI and rollup code should call it rather than inlining the comparison so future
41
+ * tweaks (e.g. NaN handling) land in one place.
42
+ */
43
+ export function deriveAssertionPassed(result: {
44
+ readonly score: number;
45
+ readonly passThreshold?: number;
46
+ readonly errored?: true;
47
+ }): boolean {
48
+ if (result.errored === true) return false;
49
+ const threshold = result.passThreshold ?? DEFAULT_ASSERTION_PASS_THRESHOLD;
50
+ return result.score >= threshold;
51
+ }
52
+
53
+ /**
54
+ * Provenance for a persisted {@link AssertionResult}: which node produced it and where in the
55
+ * per-item iteration tree it landed. Filled in by the host-side persister, not the node itself.
56
+ */
57
+ export interface AssertionResultProvenance {
58
+ readonly nodeId: NodeId;
59
+ /** Per-item iteration id when the emitting node ran inside a per-item loop. */
60
+ readonly iterationId?: string;
61
+ /** Item index (0-based) within the activation that produced this assertion. */
62
+ readonly itemIndex?: number;
63
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Minimal base types that have no dependencies on other contracts.
3
+ * Used by credentialTypes, workflowTypes, and other contract layers
4
+ * to avoid circular dependencies.
5
+ */
6
+
7
+ export type WorkflowId = string;
8
+ export type NodeId = string;
9
+ export type OutputPortKey = string;
10
+ export type InputPortKey = string;
11
+ export type PersistedTokenId = string;
12
+ export type NodeConnectionName = string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Represents a typed store for a single collection.
3
+ * All rows include auto-managed id, created_at, and updated_at fields.
4
+ */
5
+ export interface CollectionStore<TRow extends Record<string, unknown> = Record<string, unknown>> {
6
+ /**
7
+ * Insert a new row. id, created_at, and updated_at are auto-populated.
8
+ */
9
+ insert(row: TRow): Promise<TRow & { id: string; created_at: Date; updated_at: Date }>;
10
+
11
+ /**
12
+ * Get a single row by id.
13
+ */
14
+ get(id: string): Promise<(TRow & { id: string; created_at: Date; updated_at: Date }) | null>;
15
+
16
+ /**
17
+ * Find a single row matching the provided filter.
18
+ */
19
+ findOne(filter: Partial<TRow>): Promise<(TRow & { id: string; created_at: Date; updated_at: Date }) | null>;
20
+
21
+ /**
22
+ * List rows with optional pagination and filtering.
23
+ */
24
+ list(opts?: {
25
+ limit?: number;
26
+ offset?: number;
27
+ where?: Partial<TRow>;
28
+ }): Promise<{ rows: ReadonlyArray<TRow & { id: string; created_at: Date; updated_at: Date }>; total: number }>;
29
+
30
+ /**
31
+ * Update a row by id with partial data.
32
+ */
33
+ update(id: string, patch: Partial<TRow>): Promise<TRow & { id: string; created_at: Date; updated_at: Date }>;
34
+
35
+ /**
36
+ * Delete a row by id. Hard delete only (no soft delete).
37
+ */
38
+ delete(id: string): Promise<{ deleted: boolean }>;
39
+ }
40
+
41
+ /**
42
+ * Runtime collections context: keyed by collection name.
43
+ */
44
+ export type CollectionsContext = Readonly<Record<string, CollectionStore>>;
@@ -1,4 +1,4 @@
1
- import type { NodeId, WorkflowId } from "./workflowTypes";
1
+ import type { NodeId, WorkflowId } from "./baseTypes";
2
2
 
3
3
  export type CredentialTypeId = string;
4
4
  export type CredentialInstanceId = string;
@@ -91,6 +91,28 @@ export type CredentialOAuth2AuthDefinition = Readonly<
91
91
  clientIdFieldKey?: string;
92
92
  clientSecretFieldKey?: string;
93
93
  }
94
+ | {
95
+ kind: "oauth2";
96
+ /**
97
+ * Free-form provider identifier for telemetry, DB rows, and Better Auth provider naming.
98
+ * Not used for any registry lookup — URLs come from {@link authorizeUrl} / {@link tokenUrl}.
99
+ */
100
+ providerId: string;
101
+ /**
102
+ * Authorization endpoint. May contain `{publicFieldKey}` placeholders that the runtime
103
+ * substitutes from the credential's resolved public config (URL-encoded).
104
+ * Example: `https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize`
105
+ */
106
+ authorizeUrl: string;
107
+ /** Token endpoint. Same templating rules as {@link authorizeUrl}. */
108
+ tokenUrl: string;
109
+ /** Optional userinfo endpoint. Same templating rules as {@link authorizeUrl}. */
110
+ userInfoUrl?: string;
111
+ scopes: ReadonlyArray<string>;
112
+ scopesFromPublicConfig?: CredentialOAuth2ScopesFromPublicConfig;
113
+ clientIdFieldKey?: string;
114
+ clientSecretFieldKey?: string;
115
+ }
94
116
  >;
95
117
 
96
118
  export type CredentialAuthDefinition = CredentialOAuth2AuthDefinition;
@@ -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 {
@@ -1,3 +1,6 @@
1
+ export * from "./baseTypes";
2
+ export * from "./assertionTypes";
3
+ export * from "./collectionTypes";
1
4
  export * from "./credentialTypes";
2
5
  export * from "./emitPorts";
3
6
  export * from "./executionPersistenceContracts";
@@ -6,6 +9,7 @@ export * from "./params";
6
9
  export * from "./itemExpr";
7
10
  export * from "./runtimeTypes";
8
11
  export * from "./telemetryTypes";
12
+ export * from "./testTriggerTypes";
9
13
  export * from "./runFinishedAtFactory";
10
14
  export * from "./runTypes";
11
15
  export * from "./webhookTypes";
@@ -1,5 +1,5 @@
1
1
  import type { TypeToken } from "../di";
2
- import type { RunEventBus } from "../events/runEvents";
2
+ import type { RunEventBus, TestCaseRunStatus } from "../events/runEvents";
3
3
  import type {
4
4
  Edge,
5
5
  InputPortKey,
@@ -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,
@@ -19,6 +20,23 @@ import type {
19
20
  WorkflowNodeConnection,
20
21
  } from "./workflowTypes";
21
22
 
23
+ /**
24
+ * Test-suite linkage for a run. When set, this run was started by a TestSuiteOrchestrator
25
+ * as one test case inside a TestSuiteRun. The `IsTestRun` node and host-side persisters key
26
+ * off the presence of this field. Subworkflow runs inherit it from their parent run.
27
+ */
28
+ export interface RunTestContext {
29
+ readonly testSuiteRunId: string;
30
+ readonly testCaseIndex: number;
31
+ /**
32
+ * Optional human-friendly label for this test case (e.g. an email subject when fixtures
33
+ * are loaded from a mailbox). Resolved per item by `TestTrigger.caseLabel(item)` if set,
34
+ * persisted on `Run.test_case_label` so the Tests-tab tree-table can show "RFQ for batch 14"
35
+ * instead of "run_1777755971399_bbb86beac1396".
36
+ */
37
+ readonly testCaseLabel?: string;
38
+ }
39
+
22
40
  export interface RunExecutionOptions {
23
41
  /** Run-intent override: force the inline scheduler and bypass node-level offload decisions. */
24
42
  localOnly?: boolean;
@@ -35,6 +53,8 @@ export interface RunExecutionOptions {
35
53
  maxNodeActivations?: number;
36
54
  /** Effective cap after engine policy merge (subworkflow nesting). */
37
55
  maxSubworkflowDepth?: number;
56
+ /** Present iff started by a TestSuiteOrchestrator; propagates to subworkflow runs via {@link ParentExecutionRef.testContext}. */
57
+ testContext?: RunTestContext;
38
58
  }
39
59
 
40
60
  /** Engine-owned counters persisted with the run (worker-safe). */
@@ -154,6 +174,12 @@ export interface ConnectionInvocationRecord {
154
174
  readonly startedAt?: string;
155
175
  readonly finishedAt?: string;
156
176
  readonly updatedAt: string;
177
+ /** Per-item iteration id minted by the engine when this invocation occurred inside a runnable node's per-item loop. */
178
+ readonly iterationId?: NodeIterationId;
179
+ /** Item index (0-based) of the iteration that produced this invocation. */
180
+ readonly itemIndex?: number;
181
+ /** When set, this invocation was produced inside a sub-agent triggered by the named parent invocation. */
182
+ readonly parentInvocationId?: ConnectionInvocationId;
157
183
  }
158
184
 
159
185
  /** Arguments for appending a {@link ConnectionInvocationRecord} (engine fills run/workflow ids and timestamps). */
@@ -169,6 +195,9 @@ export type ConnectionInvocationAppendArgs = Readonly<{
169
195
  queuedAt?: string;
170
196
  startedAt?: string;
171
197
  finishedAt?: string;
198
+ iterationId?: NodeIterationId;
199
+ itemIndex?: number;
200
+ parentInvocationId?: ConnectionInvocationId;
172
201
  }>;
173
202
 
174
203
  export interface RunCurrentState {
@@ -210,6 +239,13 @@ export interface RunSummary {
210
239
  workflowId: WorkflowId;
211
240
  startedAt: string;
212
241
  status: RunStatus;
242
+ /**
243
+ * Test-case status for runs dispatched as part of a TestSuiteRun. Carries the
244
+ * assertion-rollup-corrected outcome the test orchestrator persists onto the row, so the
245
+ * executions list can show "failed" for a run whose workflow completed cleanly but whose
246
+ * assertions caught regressions. Absent for non-test runs and legacy rows.
247
+ */
248
+ testCaseStatus?: TestCaseRunStatus;
213
249
  /** ISO timestamp when the run finished (derived from node snapshots or store `updatedAt`); omit while running/pending. */
214
250
  finishedAt?: string;
215
251
  parent?: ParentExecutionRef;
@@ -2,14 +2,17 @@ import type { ReadableStream as BinaryReadableStream } from "node:stream/web";
2
2
  import type { TypeToken } from "../di";
3
3
  import type { RunEventBus } from "../events/runEvents";
4
4
  import type { CredentialSessionService } from "./credentialTypes";
5
+ import type { CollectionsContext } from "./collectionTypes";
5
6
  import type { ExecutionTelemetry, ExecutionTelemetryFactory, NodeExecutionTelemetry } from "./telemetryTypes";
6
7
  import type {
7
8
  ConnectionInvocationAppendArgs,
9
+ ConnectionInvocationId,
8
10
  NodeInputsByPort,
9
11
  PersistedWorkflowSnapshot,
10
12
  PersistedWorkflowTokenRegistryLike,
11
13
  RunExecutionOptions,
12
14
  RunResult,
15
+ RunTestContext,
13
16
  WorkflowExecutionRepository,
14
17
  } from "./runTypes";
15
18
  import type { WorkflowActivationPolicy } from "./workflowActivationPolicy";
@@ -25,6 +28,7 @@ import type {
25
28
  NodeActivationId,
26
29
  NodeConfigBase,
27
30
  NodeId,
31
+ NodeIterationId,
28
32
  NodeOutputs,
29
33
  RunnableNodeConfig,
30
34
  OutputPortKey,
@@ -154,6 +158,21 @@ export interface ExecutionContext {
154
158
  telemetry: ExecutionTelemetry;
155
159
  binary: ExecutionBinaryService;
156
160
  getCredential<TSession = unknown>(slotKey: string): Promise<TSession>;
161
+ /** Per-item iteration id, set by {@link NodeExecutor} on the ctx passed into runnable `execute`. */
162
+ iterationId?: NodeIterationId;
163
+ /** Item index (0-based) within the current activation's batch; set alongside {@link iterationId}. */
164
+ itemIndex?: number;
165
+ /** When set, this ctx is executing inside a sub-agent triggered by the named parent invocation. */
166
+ parentInvocationId?: ConnectionInvocationId;
167
+ /**
168
+ * Present iff the run was started by a TestSuiteOrchestrator. The {@link IsTestRunNode}
169
+ * branches on this; assertion-emitting nodes use it to decide whether to record results.
170
+ */
171
+ testContext?: RunTestContext;
172
+ /**
173
+ * Collections registered in the codemation config, keyed by collection name.
174
+ */
175
+ readonly collections?: CollectionsContext;
157
176
  }
158
177
 
159
178
  export interface ExecutionContextFactory {
@@ -169,6 +188,7 @@ export interface ExecutionContextFactory {
169
188
  nodeState?: NodeExecutionStatePublisher;
170
189
  telemetry?: ExecutionTelemetry;
171
190
  getCredential<TSession = unknown>(slotKey: string): Promise<TSession>;
191
+ testContext?: RunTestContext;
172
192
  }): ExecutionContext;
173
193
  }
174
194
 
@@ -180,6 +200,24 @@ export interface NodeExecutionContext<TConfig extends NodeConfigBase = NodeConfi
180
200
  binary: NodeBinaryAttachmentService;
181
201
  }
182
202
 
203
+ export interface PollingTriggerHandle {
204
+ /**
205
+ * Start the polling loop. The runtime registers its own cleanup handle so callers do not need to
206
+ * call {@link TriggerSetupContext.registerCleanup} for the loop.
207
+ * @returns The state returned by the first cycle (or `undefined` when the overlap guard fired).
208
+ */
209
+ start<TState, TItem>(args: {
210
+ intervalMs: number;
211
+ seedState?: TState;
212
+ runCycle: (cycleCtx: {
213
+ previousState: TState | undefined;
214
+ signal: AbortSignal;
215
+ }) => Promise<{ items: Items<TItem>; nextState: TState }>;
216
+ }): Promise<TState | undefined>;
217
+ /** Convenience dedup-window helper. */
218
+ readonly dedup: import("../triggers/polling/PollingTriggerDedupWindow").PollingTriggerDedupWindow;
219
+ }
220
+
183
221
  export interface TriggerSetupContext<
184
222
  TConfig extends TriggerNodeConfig<any, any> = TriggerNodeConfig<any, any>,
185
223
  TSetupState extends JsonValue | undefined = TriggerNodeSetupState<TConfig>,
@@ -189,6 +227,8 @@ export interface TriggerSetupContext<
189
227
  previousState: TSetupState;
190
228
  registerCleanup(cleanup: TriggerCleanupHandle): void;
191
229
  emit(items: Items): Promise<void>;
230
+ /** Generic polling-trigger surface. Pre-binds trigger id, emit, and registerCleanup. */
231
+ readonly polling: PollingTriggerHandle;
192
232
  }
193
233
 
194
234
  export interface TriggerTestItemsContext<
@@ -431,4 +471,6 @@ export interface EngineDeps {
431
471
  workflowPolicyRuntimeDefaults?: WorkflowPolicyRuntimeDefaults;
432
472
  /** When set, logs inactive-workflow skips at boot and trigger start/stop on activation changes. */
433
473
  triggerRuntimeDiagnostics?: TriggerRuntimeDiagnostics;
474
+ /** When set, the polling-trigger runtime uses this logger for cycle info/debug/error. */
475
+ pollingTriggerLogger?: import("../triggers/polling/PollingTriggerLogger").PollingTriggerLogger;
434
476
  }
@@ -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 {
@@ -0,0 +1,66 @@
1
+ import type { Item, NodeId, WorkflowId } from "./workflowTypes";
2
+ import type { TriggerNodeConfig } from "./workflowTypes";
3
+
4
+ /**
5
+ * Identifier minted by the host (or in-memory test runner) for one execution of a test suite.
6
+ * One TestSuiteRun produces N child workflow runs, one per item yielded by `generateItems`.
7
+ */
8
+ export type TestSuiteRunId = string;
9
+
10
+ /**
11
+ * Setup context passed to a {@link TestTriggerNodeConfig.generateItems} callback. Distinct from
12
+ * {@link import("./runtimeTypes").TriggerSetupContext} on purpose: test triggers are not
13
+ * activated by the live trigger lifecycle (webhooks, cron, polling) and never call `emit` —
14
+ * the orchestrator pulls from the iterable they return and dispatches one run per item.
15
+ */
16
+ export interface TestTriggerSetupContext<
17
+ TConfig extends TestTriggerNodeConfig<unknown> = TestTriggerNodeConfig<unknown>,
18
+ > {
19
+ readonly workflowId: WorkflowId;
20
+ readonly nodeId: NodeId;
21
+ readonly config: TConfig;
22
+ readonly testSuiteRunId: TestSuiteRunId;
23
+ /**
24
+ * Resolves a credential session for a slot declared on this trigger's
25
+ * {@link import("./workflowTypes").NodeConfigBase.getCredentialRequirements}. Same contract as
26
+ * {@link import("./runtimeTypes").ExecutionContext.getCredential}.
27
+ */
28
+ getCredential<TSession = unknown>(slotKey: string): Promise<TSession>;
29
+ /** AbortSignal raised when the suite is cancelled — long-running pulls should bail out. */
30
+ readonly signal: AbortSignal;
31
+ }
32
+
33
+ /**
34
+ * A trigger config that emits **test cases**. Each item yielded by {@link generateItems}
35
+ * becomes one workflow run (with `executionOptions.testContext` set), so 10 yielded items
36
+ * → 10 runs marked under the same TestSuiteRun.
37
+ *
38
+ * The trigger is otherwise a normal {@link TriggerNodeConfig} (so the canvas treats it like
39
+ * any other trigger), but its `triggerKind` is `"test"` so the live activation policy skips it.
40
+ */
41
+ export interface TestTriggerNodeConfig<TOutputJson = unknown> extends TriggerNodeConfig<TOutputJson, undefined> {
42
+ readonly triggerKind: "test";
43
+ /**
44
+ * Author-supplied async iterable of items, evaluated lazily. Implementations may fetch from
45
+ * credentialed APIs, read fixture files, or yield hard-coded items. The orchestrator iterates
46
+ * and dispatches one run per item, with concurrency capped by {@link concurrency} (default 4).
47
+ */
48
+ generateItems(ctx: TestTriggerSetupContext<TestTriggerNodeConfig<TOutputJson>>): AsyncIterable<Item<TOutputJson>>;
49
+ /** Per-suite-run cap on simultaneously-executing test cases. Default: 4. */
50
+ readonly concurrency?: number;
51
+ /**
52
+ * Free-form description of where the test cases come from — surfaced in the node properties
53
+ * panel and the suite-detail header so authors revisiting the workflow six months later
54
+ * remember which mailbox / folder / fixture file the cases originate from.
55
+ *
56
+ * Example: `"All emails in the Gmail label \"test/triage-fixtures\" — 14 messages as of 2026-05-03."`
57
+ */
58
+ readonly description?: string;
59
+ /**
60
+ * Resolves a human-readable label for one yielded test case (e.g. email subject). The
61
+ * orchestrator calls this once per yielded item, persists the result on the run, and the
62
+ * Tests-tab UI uses it to render the case row instead of the opaque runId. Return
63
+ * `undefined` to fall back to "Case #N".
64
+ */
65
+ caseLabel?(item: Item<TOutputJson>): string | undefined;
66
+ }
@@ -3,13 +3,18 @@ import type { ZodType } from "zod";
3
3
  import type { TypeToken } from "../di";
4
4
  import type { CredentialRequirement } from "./credentialTypes";
5
5
  import type { RetryPolicySpec } from "./retryPolicySpec.types";
6
+ import type { InputPortKey, NodeConnectionName, NodeId, OutputPortKey, WorkflowId } from "./baseTypes";
7
+
8
+ export type {
9
+ InputPortKey,
10
+ NodeConnectionName,
11
+ NodeId,
12
+ OutputPortKey,
13
+ PersistedTokenId,
14
+ WorkflowId,
15
+ } from "./baseTypes";
6
16
 
7
- export type WorkflowId = string;
8
- export type NodeId = string;
9
17
  export type NodeIdRef<TJson = unknown> = NodeId & Readonly<{ __codemationNodeJson?: TJson }>;
10
- export type OutputPortKey = string;
11
- export type InputPortKey = string;
12
- export type PersistedTokenId = string;
13
18
 
14
19
  export type NodeKind = "trigger" | "node";
15
20
  export type JsonPrimitive = string | number | boolean | null;
@@ -26,8 +31,6 @@ export interface Edge {
26
31
  to: { nodeId: NodeId; input: InputPortKey };
27
32
  }
28
33
 
29
- export type NodeConnectionName = string;
30
-
31
34
  /**
32
35
  * Named connection from a parent node to child nodes that exist in {@link WorkflowDefinition.nodes}
33
36
  * but are not traversed by the main execution graph. Parents are commonly executable nodes, but may
@@ -91,6 +94,14 @@ export interface NodeConfigBase {
91
94
  readonly declaredOutputPorts?: ReadonlyArray<OutputPortKey>;
92
95
  readonly declaredInputPorts?: ReadonlyArray<InputPortKey>;
93
96
  getCredentialRequirements?(): ReadonlyArray<CredentialRequirement>;
97
+ /**
98
+ * Marker: this node emits {@link import("./assertionTypes").AssertionResult}-shaped items on its
99
+ * `main` port. The TestSuiteOrchestrator (and host-side TestAssertionPersister) listen for
100
+ * `nodeCompleted` events from nodes with this flag set, and persist their output items as
101
+ * TestAssertion records (only when the run carries a `testContext`). Set on assertion node
102
+ * configs (e.g. `AssertionNodeConfig`, `StringEqualsAssertionNodeConfig`).
103
+ */
104
+ readonly emitsAssertions?: true;
94
105
  }
95
106
 
96
107
  export declare const runnableNodeInputType: unique symbol;
@@ -127,6 +138,12 @@ export interface TriggerNodeConfig<
127
138
  readonly kind: "trigger";
128
139
  readonly [triggerNodeOutputType]?: TOutputJson;
129
140
  readonly [triggerNodeSetupStateType]?: TSetupState;
141
+ /**
142
+ * Distinguishes triggers driven by the live activation policy (webhooks, cron, polling) from
143
+ * triggers driven only by the {@link TestSuiteOrchestrator}. `WorkflowActivation` skips
144
+ * `"test"` triggers; the orchestrator skips `"live"` triggers. Defaults to `"live"` when omitted.
145
+ */
146
+ readonly triggerKind?: "live" | "test";
130
147
  }
131
148
 
132
149
  export type RunnableNodeInputJson<TConfig extends RunnableNodeConfig<any, any>> =
@@ -194,6 +211,12 @@ export type NodeOutputs = Partial<Record<OutputPortKey, Items>>;
194
211
 
195
212
  export type RunId = string;
196
213
  export type NodeActivationId = string;
214
+ /**
215
+ * One per-item iteration of a runnable node's execute loop. Refines `NodeActivationId` for
216
+ * per-item connection invocations and telemetry. Undefined when the executing node is a batch
217
+ * node or trigger that does not iterate items.
218
+ */
219
+ export type NodeIterationId = string;
197
220
 
198
221
  export interface ParentExecutionRef {
199
222
  runId: RunId;
@@ -205,6 +228,12 @@ export interface ParentExecutionRef {
205
228
  engineMaxNodeActivations?: number;
206
229
  /** Effective max subworkflow depth from the parent run (propagated to child policy merge). */
207
230
  engineMaxSubworkflowDepth?: number;
231
+ /**
232
+ * Test-suite linkage inherited by the child subworkflow run. Set by whichever node
233
+ * spawns the subworkflow when its own `ctx.testContext` is present, so assertions
234
+ * emitted inside a subworkflow land under the correct parent test case.
235
+ */
236
+ testContext?: import("./runTypes").RunTestContext;
208
237
  }
209
238
 
210
239
  export interface RunDataSnapshot {
@@ -0,0 +1,59 @@
1
+ // Pure-type-only re-exports. Use this for type-only consumers that should not drag in runtime DSL or factory code.
2
+ // This subpath prevents unnecessary compile-graph bloat for packages that only need types like NodeId, Items, etc.
3
+
4
+ export type * from "./contracts/baseTypes";
5
+ export type * from "./contracts/assertionTypes";
6
+ // assertionTypes also exports a runtime helper for deriving pass/fail from a score+threshold.
7
+ // We keep the type-only re-export above and surface the helper explicitly here so UI consumers
8
+ // (next-host) don't need to re-implement the comparison.
9
+ export { deriveAssertionPassed, DEFAULT_ASSERTION_PASS_THRESHOLD } from "./contracts/assertionTypes";
10
+ export type * from "./contracts/params";
11
+ export type * from "./contracts/retryPolicySpec.types";
12
+ export type * from "./contracts/CostCatalogContract";
13
+ export type * from "./contracts/executionPersistenceContracts";
14
+ export type * from "./contracts/runtimeTypes";
15
+ export type * from "./contracts/telemetryTypes";
16
+ export type * from "./contracts/testTriggerTypes";
17
+ export type * from "./contracts/runTypes";
18
+ export type * from "./contracts/webhookTypes";
19
+ export type * from "./contracts/workflowTypes";
20
+
21
+ // credentialTypes mixes types (Credential* interfaces) with runtime (CredentialUnboundError class).
22
+ // Export type-only subset for pure type consumers.
23
+ export type {
24
+ CredentialTypeId,
25
+ CredentialInstanceId,
26
+ CredentialMaterialSourceKind,
27
+ CredentialSetupStatus,
28
+ CredentialHealthStatus,
29
+ CredentialFieldSchema,
30
+ CredentialRequirement,
31
+ CredentialBindingKey,
32
+ CredentialBinding,
33
+ CredentialHealth,
34
+ OAuth2ProviderFromPublicConfig,
35
+ CredentialOAuth2ScopesFromPublicConfig,
36
+ CredentialOAuth2AuthDefinition,
37
+ CredentialAuthDefinition,
38
+ CredentialAdvancedSectionPresentation,
39
+ CredentialTypeDefinition,
40
+ CredentialJsonRecord,
41
+ CredentialInstanceRecord,
42
+ CredentialSessionFactoryArgs,
43
+ CredentialSessionFactory,
44
+ CredentialHealthTester,
45
+ CredentialType,
46
+ AnyCredentialType,
47
+ CredentialSessionService,
48
+ CredentialTypeRegistry,
49
+ } from "./contracts/credentialTypes";
50
+
51
+ // CostTrackingTelemetryContract mixes types with const runtime values (metric/attribute names).
52
+ // Export type-only subset for pure type consumers.
53
+ export type {
54
+ CostTrackingComponent,
55
+ CostTrackingUsageRecord,
56
+ CostTrackingPriceQuote,
57
+ CostTrackingTelemetry,
58
+ CostTrackingTelemetryFactory,
59
+ } from "./contracts/CostTrackingTelemetryContract";
@@ -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
+ }