@codemation/core 0.10.2 → 0.11.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 +183 -0
  2. package/dist/CostCatalogContract-DZgcUBE4.d.cts +19 -0
  3. package/dist/{EngineRuntimeRegistration.types-ClLuY1FG.d.ts → EngineRuntimeRegistration.types-BQbS9_gs.d.ts} +2 -2
  4. package/dist/{EngineRuntimeRegistration.types-BryWi2mA.d.cts → EngineRuntimeRegistration.types-Cggm5GVY.d.cts} +8 -2
  5. package/dist/{InMemoryRunDataFactory-DeXNJt1O.d.cts → InMemoryRunDataFactory-C7YItvHG.d.cts} +9 -19
  6. package/dist/{InMemoryRunEventBusRegistry-sM4z4n_i.js → InMemoryRunEventBusRegistry-Bwunvt1T.js} +1 -1
  7. package/dist/{InMemoryRunEventBusRegistry-sM4z4n_i.js.map → InMemoryRunEventBusRegistry-Bwunvt1T.js.map} +1 -1
  8. package/dist/{InMemoryRunEventBusRegistry-VM3OWnHo.cjs → InMemoryRunEventBusRegistry-Sa86VxuV.cjs} +1 -1
  9. package/dist/{InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map → InMemoryRunEventBusRegistry-Sa86VxuV.cjs.map} +1 -1
  10. package/dist/ItemsInputNormalizer-C_dpn76M.d.cts +407 -0
  11. package/dist/ItemsInputNormalizer-CwdOhSAK.cjs +43 -0
  12. package/dist/ItemsInputNormalizer-CwdOhSAK.cjs.map +1 -0
  13. package/dist/ItemsInputNormalizer-D-MH8MBs.js +36 -0
  14. package/dist/ItemsInputNormalizer-D-MH8MBs.js.map +1 -0
  15. package/dist/ItemsInputNormalizer-_Mfcd3YU.d.ts +321 -0
  16. package/dist/RunIntentService-BVur7x9n.d.ts +285 -0
  17. package/dist/RunIntentService-CEF-sFfI.d.cts +206 -0
  18. package/dist/{RunIntentService-BqNjrksF.d.cts → agentMcpTypes-ZiNbNsEi.d.cts} +1717 -1809
  19. package/dist/bootstrap/index.cjs +4 -2
  20. package/dist/bootstrap/index.d.cts +8 -3
  21. package/dist/bootstrap/index.d.ts +6 -4
  22. package/dist/bootstrap/index.js +4 -2
  23. package/dist/{bootstrap-DtjQtuvi.cjs → bootstrap-BxuTFTLB.cjs} +41 -34
  24. package/dist/bootstrap-BxuTFTLB.cjs.map +1 -0
  25. package/dist/{bootstrap-BfFKGzyj.js → bootstrap-D_Yyi0wL.js} +9 -2
  26. package/dist/bootstrap-D_Yyi0wL.js.map +1 -0
  27. package/dist/browser.cjs +16 -0
  28. package/dist/browser.d.cts +4 -0
  29. package/dist/browser.d.ts +3 -0
  30. package/dist/browser.js +4 -0
  31. package/dist/contracts-CK0x6w_G.cjs +74 -0
  32. package/dist/contracts-CK0x6w_G.cjs.map +1 -0
  33. package/dist/contracts-DXdfTdpW.js +50 -0
  34. package/dist/contracts-DXdfTdpW.js.map +1 -0
  35. package/dist/contracts.cjs +6 -0
  36. package/dist/contracts.d.cts +5 -0
  37. package/dist/contracts.d.ts +2 -0
  38. package/dist/contracts.js +3 -0
  39. package/dist/di-0Wop7z1y.js +376 -0
  40. package/dist/di-0Wop7z1y.js.map +1 -0
  41. package/dist/di-BlEKdoZS.cjs +489 -0
  42. package/dist/di-BlEKdoZS.cjs.map +1 -0
  43. package/dist/executionPersistenceContracts-BgZMRsTa.d.cts +275 -0
  44. package/dist/{index-CJQtTY_M.d.ts → index-62Ba9f7D.d.ts} +114 -320
  45. package/dist/{RunIntentService-CI-F8qQ7.d.ts → index-zWGtEhrf.d.ts} +1811 -1914
  46. package/dist/index.cjs +76 -71
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +112 -656
  49. package/dist/index.d.ts +5 -3
  50. package/dist/index.js +52 -50
  51. package/dist/index.js.map +1 -1
  52. package/dist/params-B5SENSzZ.d.cts +44 -0
  53. package/dist/{runtime-_ywksLa6.cjs → runtime-DBzq5YBi.cjs} +125 -489
  54. package/dist/runtime-DBzq5YBi.cjs.map +1 -0
  55. package/dist/{runtime-DbMjpb5d.js → runtime-cxmUkk0l.js} +117 -369
  56. package/dist/runtime-cxmUkk0l.js.map +1 -0
  57. package/dist/testing.cjs +23 -21
  58. package/dist/testing.cjs.map +1 -1
  59. package/dist/testing.d.cts +4 -3
  60. package/dist/testing.d.ts +3 -2
  61. package/dist/testing.js +5 -3
  62. package/dist/testing.js.map +1 -1
  63. package/package.json +12 -17
  64. package/src/ai/AgentConnectionNodeCollector.ts +47 -5
  65. package/src/authoring/defineNode.types.ts +21 -1
  66. package/src/authoring/definePollingTrigger.types.ts +20 -0
  67. package/src/binaries/UnavailableBinaryStorage.ts +6 -0
  68. package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +9 -0
  69. package/src/browser.ts +1 -0
  70. package/src/contracts/AgentBindError.ts +11 -0
  71. package/src/contracts/CodemationTelemetryAttributeNames.ts +4 -0
  72. package/src/contracts/NoOpAgentMcpIntegration.ts +13 -0
  73. package/src/contracts/agentMcpTypes.ts +64 -0
  74. package/src/contracts/index.ts +4 -0
  75. package/src/contracts/mcpTypes.ts +29 -0
  76. package/src/contracts/runTypes.ts +8 -0
  77. package/src/contracts/runtimeTypes.ts +4 -0
  78. package/src/contracts/workflowTypes.ts +21 -0
  79. package/src/contracts.ts +3 -0
  80. package/src/credentials/OAuthFlowExecutor.types.ts +45 -0
  81. package/src/di/CoreTokens.ts +7 -0
  82. package/src/execution/InProcessRetryRunner.ts +31 -5
  83. package/src/execution/NodeExecutor.ts +27 -7
  84. package/src/execution/NodeRunStateWriter.ts +2 -0
  85. package/src/index.ts +10 -0
  86. package/src/orchestration/RunContinuationService.ts +6 -2
  87. package/src/runStorage/InMemoryBinaryStorageRegistry.ts +10 -0
  88. package/src/scheduler/InlineDrivingScheduler.ts +26 -22
  89. package/src/types/index.ts +1 -0
  90. package/src/validation/WorkflowEdgePortError.types.ts +16 -0
  91. package/src/validation/WorkflowEdgePortValidator.ts +52 -0
  92. package/src/workflow/definition/ConnectionInvocationIdFactory.ts +4 -3
  93. package/src/workflow/definition/ConnectionNodeIdFactory.ts +25 -0
  94. package/src/workflow/definition/NodeIterationIdFactory.ts +5 -3
  95. package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +42 -10
  96. package/tsdown.config.ts +1 -1
  97. package/dist/bootstrap-BfFKGzyj.js.map +0 -1
  98. package/dist/bootstrap-DtjQtuvi.cjs.map +0 -1
  99. package/dist/runtime-DbMjpb5d.js.map +0 -1
  100. package/dist/runtime-_ywksLa6.cjs.map +0 -1
@@ -6,7 +6,7 @@ import type {
6
6
  } from "../contracts/credentialTypes";
7
7
  import type { ParamDeep } from "../contracts/params";
8
8
  import type { RunnableNode, RunnableNodeExecuteArgs, NodeExecutionContext } from "../contracts/runtimeTypes";
9
- import type { Item, Items, RunnableNodeConfig } from "../contracts/workflowTypes";
9
+ import type { Item, Items, NodeInspectorSummaryRow, RunnableNodeConfig } from "../contracts/workflowTypes";
10
10
  import type { TypeToken } from "../di";
11
11
  import { node as persistedNode } from "../runtime-types/runtimeTypeDecorators.types";
12
12
  import type { ZodType } from "zod";
@@ -120,6 +120,15 @@ export interface DefineNodeOptions<
120
120
  readonly inputSchema?: ZodType<TInputJson>;
121
121
  /** Preserve inbound `item.binary` when `execute` returns plain JSON or item-shaped results without `binary`. */
122
122
  readonly keepBinaries?: boolean;
123
+ /**
124
+ * Static configuration summary surfaced in the workflow inspector — see
125
+ * {@link import("../contracts/workflowTypes").NodeConfigBase.inspectorSummary}.
126
+ *
127
+ * Receives the static config; returns 2–6 short label/value pairs (or `undefined` to skip).
128
+ */
129
+ readonly inspectorSummary?: (
130
+ args: Readonly<{ config: TConfig }>,
131
+ ) => ReadonlyArray<NodeInspectorSummaryRow> | undefined;
123
132
  execute(
124
133
  args: DefineNodeExecuteArgs<TConfig, TInputJson>,
125
134
  context: DefinedNodeRunContext<TConfig, TBindings>,
@@ -143,6 +152,9 @@ export interface DefineBatchNodeOptions<
143
152
  readonly input?: Readonly<Record<keyof TConfig & string, unknown>>;
144
153
  readonly configSchema?: z.ZodType<TConfig>;
145
154
  readonly credentials?: TBindings;
155
+ readonly inspectorSummary?: (
156
+ args: Readonly<{ config: TConfig }>,
157
+ ) => ReadonlyArray<NodeInspectorSummaryRow> | undefined;
146
158
  run(
147
159
  items: ReadonlyArray<TInputJson>,
148
160
  context: DefinedNodeRunContext<TConfig, TBindings>,
@@ -273,6 +285,10 @@ export function defineNode<
273
285
  getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
274
286
  return credentialRequirements;
275
287
  }
288
+
289
+ inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
290
+ return options.inspectorSummary?.({ config: this.config });
291
+ }
276
292
  };
277
293
 
278
294
  const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> = {
@@ -356,6 +372,10 @@ export function defineBatchNode<
356
372
  getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
357
373
  return credentialRequirements;
358
374
  }
375
+
376
+ inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
377
+ return options.inspectorSummary?.({ config: this.config });
378
+ }
359
379
  };
360
380
 
361
381
  const definition: DefinedNode<TKey, TConfig, TInputJson, TOutputJson, TBindings> = {
@@ -11,6 +11,7 @@ import type {
11
11
  Items,
12
12
  JsonValue,
13
13
  NodeExecutionContext,
14
+ NodeInspectorSummaryRow,
14
15
  NodeOutputs,
15
16
  TestableTriggerNode,
16
17
  TriggerNodeConfig,
@@ -99,6 +100,15 @@ export interface DefinePollingTriggerOptions<
99
100
  readonly configSchema?: ZodType<TConfig>;
100
101
  /** Credential bindings keyed by slot (same format as `defineNode`). */
101
102
  readonly credentials?: TBindings;
103
+ /**
104
+ * Static configuration summary surfaced in the workflow inspector — see
105
+ * {@link import("../contracts/workflowTypes").NodeConfigBase.inspectorSummary}.
106
+ *
107
+ * Receives the static config; returns 2–6 short label/value pairs (or `undefined` to skip).
108
+ */
109
+ readonly inspectorSummary?: (
110
+ args: Readonly<{ config: TConfig }>,
111
+ ) => ReadonlyArray<NodeInspectorSummaryRow> | undefined;
102
112
  /**
103
113
  * Called once when the trigger arms (or re-arms after a server restart) to provide the
104
114
  * initial value for `state` when no persisted state exists.
@@ -201,6 +211,9 @@ export class DefinedPollingTriggerConfig<TConfig extends CredentialJsonRecord, T
201
211
  icon: string | undefined,
202
212
  private readonly credentialRequirements: ReadonlyArray<CredentialRequirement>,
203
213
  public readonly id?: string,
214
+ private readonly inspectorSummaryFn?: (
215
+ args: Readonly<{ config: TConfig }>,
216
+ ) => ReadonlyArray<NodeInspectorSummaryRow> | undefined,
204
217
  ) {
205
218
  this.type = typeToken;
206
219
  this.icon = icon;
@@ -209,6 +222,10 @@ export class DefinedPollingTriggerConfig<TConfig extends CredentialJsonRecord, T
209
222
  getCredentialRequirements(): ReadonlyArray<CredentialRequirement> {
210
223
  return this.credentialRequirements;
211
224
  }
225
+
226
+ inspectorSummary(): ReadonlyArray<NodeInspectorSummaryRow> | undefined {
227
+ return this.inspectorSummaryFn?.({ config: this.cfg });
228
+ }
212
229
  }
213
230
 
214
231
  // ---------------------------------------------------------------------------
@@ -376,6 +393,9 @@ export function definePollingTrigger<
376
393
  options.icon,
377
394
  credentialRequirements,
378
395
  id,
396
+ options.inspectorSummary as
397
+ | ((args: Readonly<{ config: TConfig }>) => ReadonlyArray<NodeInspectorSummaryRow> | undefined)
398
+ | undefined,
379
399
  );
380
400
  },
381
401
 
@@ -16,4 +16,10 @@ export class UnavailableBinaryStorage implements BinaryStorage {
16
16
  }
17
17
 
18
18
  async delete(): Promise<void> {}
19
+
20
+ async deleteMany(): Promise<void> {}
21
+
22
+ async listByPrefix(): Promise<ReadonlyArray<string>> {
23
+ return [];
24
+ }
19
25
  }
@@ -1,5 +1,6 @@
1
1
  import { instanceCachingFactory, type DependencyContainer } from "../../di";
2
2
  import { CoreTokens } from "../../di";
3
+ import { NoOpAgentMcpIntegration } from "../../contracts/NoOpAgentMcpIntegration";
3
4
  import { EngineExecutionLimitsPolicyFactory } from "../../policies/executionLimits/EngineExecutionLimitsPolicyFactory";
4
5
  import {
5
6
  ChildExecutionScopeFactory,
@@ -39,6 +40,14 @@ export class EngineRuntimeRegistrar {
39
40
  this.registerDefaultActivationScheduler(container);
40
41
  this.registerEngine(container, options);
41
42
  this.registerIntentServices(container);
43
+ this.registerAgentMcpIntegration(container);
44
+ }
45
+
46
+ private registerAgentMcpIntegration(container: DependencyContainer): void {
47
+ if (container.isRegistered(CoreTokens.AgentMcpIntegration, true)) {
48
+ return;
49
+ }
50
+ container.registerInstance(CoreTokens.AgentMcpIntegration, new NoOpAgentMcpIntegration());
42
51
  }
43
52
 
44
53
  private registerSupportFactories(container: DependencyContainer): void {
package/src/browser.ts CHANGED
@@ -6,6 +6,7 @@ export type {
6
6
  AgentConnectionCredentialSource,
7
7
  AgentConnectionNodeDescriptor,
8
8
  AgentConnectionNodeRole,
9
+ McpServerResolver,
9
10
  } from "./ai/AgentConnectionNodeCollector";
10
11
  export type { AgentNodeConfig } from "./ai/AiHost";
11
12
  export { ConnectionNodeIdFactory } from "./workflow/definition/ConnectionNodeIdFactory";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Thrown at agent bind time when mcpServers declarations cannot be resolved.
3
+ * Causes include: unknown server id, missing credential instance, insufficient scopes,
4
+ * and ambiguous shorthand binding (multiple credential instances match).
5
+ */
6
+ export class AgentBindError extends Error {
7
+ constructor(message: string) {
8
+ super(message);
9
+ this.name = "AgentBindError";
10
+ }
11
+ }
@@ -15,4 +15,8 @@ export class CodemationTelemetryAttributeNames {
15
15
  static readonly iterationIndex = "codemation.iteration.index";
16
16
  /** Set when this span/metric was recorded under a sub-agent triggered by an outer LLM/tool call. */
17
17
  static readonly parentInvocationId = "codemation.parent.invocation_id";
18
+ /** MCP server id on spans created for callTool invocations. */
19
+ static readonly mcpServerId = "mcp.server_id";
20
+ /** MCP tool name on spans created for callTool invocations. */
21
+ static readonly mcpToolName = "mcp.tool_name";
18
22
  }
@@ -0,0 +1,13 @@
1
+ import type { AgentMcpIntegration, AgentMcpToolMap } from "./agentMcpTypes";
2
+
3
+ /**
4
+ * No-op implementation of AgentMcpIntegration.
5
+ * Registered by the core engine runtime as a fallback when the host does not
6
+ * supply a real implementation (e.g. in unit tests or headless engine setups).
7
+ * Always returns an empty tool map so the agent runs with node-backed tools only.
8
+ */
9
+ export class NoOpAgentMcpIntegration implements AgentMcpIntegration {
10
+ async prepareMcpTools(): Promise<AgentMcpToolMap> {
11
+ return new Map();
12
+ }
13
+ }
@@ -0,0 +1,64 @@
1
+ import type { NodeId, WorkflowId } from "./baseTypes";
2
+ import type { ConnectionInvocationAppendArgs, ConnectionInvocationId } from "./runTypes";
3
+ import type { TelemetrySpanEventRecord } from "./telemetryTypes";
4
+ import type { NodeActivationId, NodeIterationId } from "./workflowTypes";
5
+
6
+ /**
7
+ * Emitted as a span event when a credential is missing required scopes
8
+ * (bind-time) or when callTool returns a permission error (runtime).
9
+ * The credential type id can be looked up from the credential instance when needed.
10
+ */
11
+ export interface NeedsReconsentEvent {
12
+ readonly serverId: string;
13
+ readonly credentialInstanceId: string;
14
+ readonly missingScopesHint?: readonly string[];
15
+ }
16
+
17
+ /**
18
+ * An opaque MCP tool map: keyed by serverId → (toolName → tool definition).
19
+ * Typed as unknown so core does not depend on the AI SDK's ToolSet type.
20
+ * AIAgentNode (in core-nodes, which does depend on ai) casts this to
21
+ * ReadonlyMap<string, ToolSet> before passing to DeferredMetaToolStrategyFactory.
22
+ */
23
+ export type AgentMcpToolMap = ReadonlyMap<string, Readonly<Record<string, unknown>>>;
24
+
25
+ /**
26
+ * Contract implemented by the host. Resolves MCP server bindings for an agent run
27
+ * via the standard credential-binding table (one slot per declared server, keyed
28
+ * by `(workflowId, mcpConnectionNodeId, "credential")`), and returns a ready-to-use
29
+ * tool map with wrapped execute callbacks for telemetry and 403 detection.
30
+ * Core-nodes imports this interface so AIAgentNode can inject it without
31
+ * depending on the host.
32
+ */
33
+ export interface AgentMcpIntegration {
34
+ /**
35
+ * Look up the credential binding per server, validate scopes, open pool
36
+ * connections, and return a tool map keyed by serverId. Each tool's
37
+ * execute callback includes:
38
+ * - Telemetry child span (mcp.server_id, mcp.tool_name attributes)
39
+ * - 403/permission error detection → emits a NeedsReconsentEvent span event
40
+ *
41
+ * Throws `AgentBindError` on validation failures (missing server, unbound
42
+ * credential slot, missing credential instance, insufficient scopes).
43
+ */
44
+ prepareMcpTools(args: {
45
+ readonly workflowId: WorkflowId;
46
+ readonly agentNodeId: NodeId;
47
+ readonly serverIds: ReadonlyArray<string>;
48
+ readonly pinnedMcpTools: readonly string[];
49
+ readonly emitSpanEvent: (event: TelemetrySpanEventRecord) => void;
50
+ readonly startChildSpan: (args: { readonly name: string; readonly attributes?: Record<string, string> }) => {
51
+ readonly end: (args?: { status?: "ok" | "error"; statusMessage?: string }) => void;
52
+ };
53
+ /** Per-MCP-tool-call invocation appender. Optional; when omitted the wrapper emits only telemetry spans. */
54
+ readonly appendMcpInvocation?: (args: ConnectionInvocationAppendArgs) => Promise<void>;
55
+ /** Agent activation id to attach to each invocation record (used by canvas + inspector grouping). */
56
+ readonly parentAgentActivationId?: NodeActivationId;
57
+ /** Per-item iteration id when the agent runs inside a per-item loop. */
58
+ readonly iterationId?: NodeIterationId;
59
+ /** Item index (0-based) of the iteration that owns these tool calls. */
60
+ readonly itemIndex?: number;
61
+ /** Parent invocation id when this agent is itself executing as a sub-agent. */
62
+ readonly parentInvocationId?: ConnectionInvocationId;
63
+ }): Promise<AgentMcpToolMap>;
64
+ }
@@ -1,5 +1,9 @@
1
+ export * from "./AgentBindError";
2
+ export * from "./agentMcpTypes";
3
+ export * from "./NoOpAgentMcpIntegration";
1
4
  export * from "./baseTypes";
2
5
  export * from "./assertionTypes";
6
+ export * from "./mcpTypes";
3
7
  export * from "./collectionTypes";
4
8
  export * from "./credentialTypes";
5
9
  export * from "./emitPorts";
@@ -0,0 +1,29 @@
1
+ export type McpServerTransport = "http";
2
+ // "stdio" is a self-host-only escape hatch (CODEMATION_ALLOW_STDIO_MCP=true); not typed here for managed.
3
+
4
+ export interface McpServerDeclaration {
5
+ /** Globally unique slug, e.g. "gmail". Workflow authors reference this. */
6
+ id: string;
7
+ displayName: string;
8
+ description: string;
9
+ transport: McpServerTransport;
10
+ url: string;
11
+ /**
12
+ * Credential types accepted by this MCP server, matching CredentialRequirement.acceptedTypes.
13
+ * Absent or empty means no credential is required.
14
+ */
15
+ acceptedCredentialTypes?: ReadonlyArray<string>;
16
+ /**
17
+ * Documentation only in MVP. The bind-time validator checks
18
+ * requiredScopes ⊆ CredentialInstance.scopesGranted.
19
+ */
20
+ requiredScopes?: string[];
21
+ /** Non-secret static headers merged onto every MCP request. */
22
+ staticHeaders?: Record<string, string>;
23
+ /**
24
+ * Overrides for tool descriptions advertised by the MCP server.
25
+ * Applied by the connection pool after tools/list.
26
+ * Key: exact tool name as returned by the server.
27
+ */
28
+ toolDescriptionOverrides?: Record<string, string>;
29
+ }
@@ -88,6 +88,8 @@ export interface PersistedWorkflowSnapshotNode {
88
88
  tokenName?: string;
89
89
  configTokenName?: string;
90
90
  config: unknown;
91
+ /** Pre-computed static configuration summary; populated by WorkflowSnapshotCodec. */
92
+ inspectorSummary?: ReadonlyArray<Readonly<{ label: string; value: string }>>;
91
93
  }
92
94
 
93
95
  export interface PersistedWorkflowSnapshot {
@@ -174,6 +176,10 @@ export interface ConnectionInvocationRecord {
174
176
  readonly status: NodeExecutionStatus;
175
177
  readonly managedInput?: JsonValue;
176
178
  readonly managedOutput?: JsonValue;
179
+ /** Short human-readable description of what this invocation is doing right now (e.g. `"calling search_messages"`). Rendered as a sub-line on the canvas node card. */
180
+ readonly statusLabel?: string;
181
+ /** Stable identifier for the thing this invocation acts on (e.g. an MCP tool name like `"search_messages"`). Persists across status transitions so the inspector can show it on completed/failed entries too. Connection nodes that ARE the tool (e.g. node-backed agent tools) leave this unset — the parent node id already identifies the subject. */
182
+ readonly subjectName?: string;
177
183
  readonly error?: NodeExecutionError;
178
184
  readonly queuedAt?: string;
179
185
  readonly startedAt?: string;
@@ -196,6 +202,8 @@ export type ConnectionInvocationAppendArgs = Readonly<{
196
202
  status: NodeExecutionStatus;
197
203
  managedInput?: JsonValue;
198
204
  managedOutput?: JsonValue;
205
+ statusLabel?: string;
206
+ subjectName?: string;
199
207
  error?: NodeExecutionError;
200
208
  queuedAt?: string;
201
209
  startedAt?: string;
@@ -128,6 +128,10 @@ export interface BinaryStorage {
128
128
  openReadStream(storageKey: string): Promise<BinaryStorageReadResult | undefined>;
129
129
  stat(storageKey: string): Promise<BinaryStorageStatResult>;
130
130
  delete(storageKey: string): Promise<void>;
131
+ /** Deletes multiple objects in bulk. Keys are batched internally. */
132
+ deleteMany(storageKeys: ReadonlyArray<string>): Promise<void>;
133
+ /** Lists all keys sharing a common prefix. Returns keys in arbitrary order. */
134
+ listByPrefix(prefix: string): Promise<ReadonlyArray<string>>;
131
135
  }
132
136
 
133
137
  export interface BinaryAttachmentCreateRequest {
@@ -102,6 +102,27 @@ export interface NodeConfigBase {
102
102
  * configs (e.g. `AssertionNodeConfig`, `StringEqualsAssertionNodeConfig`).
103
103
  */
104
104
  readonly emitsAssertions?: true;
105
+ /**
106
+ * Static configuration summary surfaced in the workflow inspector — the design-time
107
+ * "what does this node do" panel that renders before any run telemetry exists.
108
+ *
109
+ * Return 2–6 short label/value pairs derived from this config (method + url for an HTTP
110
+ * call, model + tool list for an agent, schedule + timezone for a cron trigger, etc.).
111
+ * Values are truncated by the UI; aim for one line each. Return `undefined` to opt out
112
+ * — the inspector hides the section when no rows are produced.
113
+ *
114
+ * Implement on the config class instance so the function can read sibling config fields.
115
+ * `defineNode({ inspectorSummary })` plumbs through to this.
116
+ */
117
+ inspectorSummary?(): ReadonlyArray<NodeInspectorSummaryRow> | undefined;
118
+ }
119
+
120
+ /**
121
+ * One row of a node's static configuration summary. See {@link NodeConfigBase.inspectorSummary}.
122
+ */
123
+ export interface NodeInspectorSummaryRow {
124
+ readonly label: string;
125
+ readonly value: string;
105
126
  }
106
127
 
107
128
  export declare const runnableNodeInputType: unique symbol;
package/src/contracts.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  // Pure-type-only re-exports. Use this for type-only consumers that should not drag in runtime DSL or factory code.
2
2
  // This subpath prevents unnecessary compile-graph bloat for packages that only need types like NodeId, Items, etc.
3
3
 
4
+ export type * from "./contracts/agentMcpTypes";
5
+ export * from "./contracts/AgentBindError";
6
+ export * from "./contracts/NoOpAgentMcpIntegration";
4
7
  export type * from "./contracts/baseTypes";
5
8
  export type * from "./contracts/assertionTypes";
6
9
  // assertionTypes also exports a runtime helper for deriving pass/fail from a score+threshold.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Four-concept model for credentials (see docs/design/credentials-oauth-unification.md):
3
+ * 1. CredentialType — schema for stored material (e.g. "oauth.google.gmail").
4
+ * 2. Credential slot requirement — which types a node or MCP server accepts.
5
+ * 3. CredentialInstance — a stored, usable token row in the host's credential store.
6
+ * 4. OAuthFlowExecutor (this file) — the only concept that differs between deployment
7
+ * modes. DI selects one implementation at boot; the rest of the system programs
8
+ * against this interface alone.
9
+ */
10
+
11
+ export interface OAuthFlowStartArgs {
12
+ readonly typeId: string;
13
+ readonly scopes: ReadonlyArray<string>;
14
+ readonly redirectUri: string;
15
+ readonly instanceId?: string;
16
+ }
17
+
18
+ export interface OAuthFlowStartResult {
19
+ readonly consentUrl: string;
20
+ readonly stateToken: string;
21
+ }
22
+
23
+ export interface OAuthFlowCallbackArgs {
24
+ readonly stateToken: string;
25
+ readonly code: string;
26
+ }
27
+
28
+ export interface OAuthMaterial {
29
+ readonly accessToken: string;
30
+ readonly refreshToken?: string;
31
+ readonly expiresAt?: string;
32
+ readonly grantedScopes: ReadonlyArray<string>;
33
+ }
34
+
35
+ export interface OAuthFlowExecutor {
36
+ start(args: OAuthFlowStartArgs): Promise<OAuthFlowStartResult>;
37
+ /**
38
+ * Returns the instanceId associated with a pending stateToken without consuming it.
39
+ * Used by callback routes to identify the target instance before calling completeCallback.
40
+ * Returns undefined when the stateToken is unknown or already consumed.
41
+ */
42
+ lookupInstanceId(stateToken: string): string | undefined;
43
+ completeCallback(args: OAuthFlowCallbackArgs): Promise<OAuthMaterial>;
44
+ refresh(args: { typeId: string; instanceId: string; material: OAuthMaterial }): Promise<OAuthMaterial>;
45
+ }
@@ -1,6 +1,7 @@
1
1
  import type { TypeToken } from "./index";
2
2
  import type { RunEventBus } from "../events/runEvents";
3
3
  import type { EngineExecutionLimitsPolicy } from "../policies/executionLimits/EngineExecutionLimitsPolicy";
4
+ import type { AgentMcpIntegration } from "../contracts/agentMcpTypes";
4
5
  import type {
5
6
  ActivationIdFactory,
6
7
  BinaryStorage,
@@ -57,4 +58,10 @@ export const CoreTokens = {
57
58
  WorkflowActivationPolicy: Symbol.for(
58
59
  "codemation.core.WorkflowActivationPolicy",
59
60
  ) as TypeToken<WorkflowActivationPolicy>,
61
+ /**
62
+ * Optional. When registered, AIAgentNode uses it to resolve mcpServers bindings,
63
+ * validate scopes, open pool connections, and prepare the MCP ToolSet map.
64
+ * Not registered in the default core bootstrap — the host provides the implementation.
65
+ */
66
+ AgentMcpIntegration: Symbol.for("codemation.core.AgentMcpIntegration") as TypeToken<AgentMcpIntegration>,
60
67
  } as const;
@@ -7,6 +7,9 @@ import type { AsyncSleeper } from "./asyncSleeper.types";
7
7
 
8
8
  export type { AsyncSleeper } from "./asyncSleeper.types";
9
9
 
10
+ /** Maximum permitted retry attempts — workflow-declared values above this are clamped. */
11
+ const HARD_MAX_RETRY_ATTEMPTS = 10;
12
+
10
13
  type NormalizedPolicy =
11
14
  | { readonly kind: "none"; readonly maxAttempts: 1 }
12
15
  | { readonly kind: "fixed"; readonly maxAttempts: number; readonly delayMs: number }
@@ -22,8 +25,13 @@ type NormalizedPolicy =
22
25
  export class InProcessRetryRunner {
23
26
  constructor(private readonly sleeper: AsyncSleeper) {}
24
27
 
25
- async run<T>(policy: RetryPolicySpec | undefined, work: () => Promise<T>): Promise<T> {
26
- const spec = InProcessRetryRunner.normalizePolicy(policy);
28
+ async run<T>(
29
+ policy: RetryPolicySpec | undefined,
30
+ work: () => Promise<T>,
31
+ shouldRetry?: (error: unknown) => boolean,
32
+ warn?: (message: string) => void,
33
+ ): Promise<T> {
34
+ const spec = InProcessRetryRunner.normalizePolicy(policy, warn);
27
35
  let lastError: unknown;
28
36
  for (let attempt = 1; attempt <= spec.maxAttempts; attempt++) {
29
37
  try {
@@ -33,6 +41,9 @@ export class InProcessRetryRunner {
33
41
  if (attempt >= spec.maxAttempts) {
34
42
  break;
35
43
  }
44
+ if (shouldRetry !== undefined && !shouldRetry(error)) {
45
+ break;
46
+ }
36
47
  const delayMs = InProcessRetryRunner.delayAfterFailureMs(spec, attempt);
37
48
  await this.sleeper.sleep(delayMs);
38
49
  }
@@ -58,7 +69,10 @@ export class InProcessRetryRunner {
58
69
  return Math.max(0, Math.floor(ms));
59
70
  }
60
71
 
61
- private static normalizePolicy(policy: RetryPolicySpec | undefined): NormalizedPolicy {
72
+ private static normalizePolicy(
73
+ policy: RetryPolicySpec | undefined,
74
+ warn?: (message: string) => void,
75
+ ): NormalizedPolicy {
62
76
  if (policy === undefined) {
63
77
  return { kind: "none", maxAttempts: 1 };
64
78
  }
@@ -71,15 +85,17 @@ export class InProcessRetryRunner {
71
85
  }
72
86
  if (kind === "fixed") {
73
87
  const p = policy as FixedRetryPolicySpec;
74
- const maxAttempts = InProcessRetryRunner.assertPositiveInt(p.maxAttempts, "fixed.maxAttempts");
88
+ const raw = InProcessRetryRunner.assertPositiveInt(p.maxAttempts, "fixed.maxAttempts");
89
+ const maxAttempts = InProcessRetryRunner.clampMaxAttempts(raw, warn);
75
90
  const delayMs = InProcessRetryRunner.assertNonNegativeFinite(p.delayMs, "fixed.delayMs");
76
91
  return { kind: "fixed", maxAttempts, delayMs };
77
92
  }
78
93
  if (kind === "exponential") {
79
94
  const p = policy as ExponentialRetryPolicySpec;
95
+ const raw = InProcessRetryRunner.assertPositiveInt(p.maxAttempts, "exponential.maxAttempts");
80
96
  return {
81
97
  kind: "exponential",
82
- maxAttempts: InProcessRetryRunner.assertPositiveInt(p.maxAttempts, "exponential.maxAttempts"),
98
+ maxAttempts: InProcessRetryRunner.clampMaxAttempts(raw, warn),
83
99
  initialDelayMs: InProcessRetryRunner.assertNonNegativeFinite(p.initialDelayMs, "exponential.initialDelayMs"),
84
100
  multiplier: InProcessRetryRunner.assertMultiplier(p.multiplier),
85
101
  maxDelayMs:
@@ -92,6 +108,16 @@ export class InProcessRetryRunner {
92
108
  return { kind: "none", maxAttempts: 1 };
93
109
  }
94
110
 
111
+ private static clampMaxAttempts(requested: number, warn?: (message: string) => void): number {
112
+ if (requested > HARD_MAX_RETRY_ATTEMPTS) {
113
+ warn?.(
114
+ `Retry policy maxAttempts (${requested}) exceeds hard ceiling (${HARD_MAX_RETRY_ATTEMPTS}); clamping to ${HARD_MAX_RETRY_ATTEMPTS}.`,
115
+ );
116
+ return HARD_MAX_RETRY_ATTEMPTS;
117
+ }
118
+ return requested;
119
+ }
120
+
95
121
  private static assertPositiveInt(value: unknown, label: string): number {
96
122
  if (typeof value !== "number" || !Number.isFinite(value) || value < 1 || !Number.isInteger(value)) {
97
123
  throw new Error(`Retry policy ${label} must be a positive integer`);
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { isPortsEmission, isUnbrandedPortsEmissionShape } from "../contracts/emitPorts";
3
+ import { CredentialUnboundError } from "../contracts/credentialTypes";
3
4
 
4
5
  import type {
5
6
  Item,
@@ -38,14 +39,33 @@ export class NodeExecutor {
38
39
  }
39
40
 
40
41
  async execute(request: NodeActivationRequest): Promise<NodeOutputs> {
42
+ await this.assertRequiredCredentialsBound(request);
41
43
  const policy = request.ctx.config.retryPolicy;
42
- return await this.retryRunner.run(policy, async () => {
43
- const nodeInstance = this.nodeInstanceFactory.createByType(request.ctx.config.type);
44
- if (request.kind === "multi") {
45
- return await this.executeMultiInputActivation(request, nodeInstance);
46
- }
47
- return await this.executeSingleInputNode(request, nodeInstance);
48
- });
44
+ return await this.retryRunner.run(
45
+ policy,
46
+ async () => {
47
+ const nodeInstance = this.nodeInstanceFactory.createByType(request.ctx.config.type);
48
+ if (request.kind === "multi") {
49
+ return await this.executeMultiInputActivation(request, nodeInstance);
50
+ }
51
+ return await this.executeSingleInputNode(request, nodeInstance);
52
+ },
53
+ (error) => !this.isCredentialError(error),
54
+ );
55
+ }
56
+
57
+ private async assertRequiredCredentialsBound(request: NodeActivationRequest): Promise<void> {
58
+ if (!request.ctx.getCredential) return;
59
+ for (const req of request.ctx.config.getCredentialRequirements?.() ?? []) {
60
+ if (req.optional) continue;
61
+ await request.ctx.getCredential(req.slotKey);
62
+ }
63
+ }
64
+
65
+ private isCredentialError(e: unknown): boolean {
66
+ if (e instanceof CredentialUnboundError) return true;
67
+ const cause = e instanceof Error ? (e as { cause?: unknown }).cause : undefined;
68
+ return cause instanceof CredentialUnboundError;
49
69
  }
50
70
 
51
71
  private async executeMultiInputActivation(
@@ -156,6 +156,8 @@ export class NodeRunStateWriter implements NodeExecutionStatePublisher {
156
156
  status: args.status,
157
157
  managedInput: args.managedInput,
158
158
  managedOutput: args.managedOutput,
159
+ statusLabel: args.statusLabel,
160
+ subjectName: args.subjectName,
159
161
  error: args.error,
160
162
  queuedAt: args.queuedAt,
161
163
  startedAt: args.startedAt,
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export type {
6
6
  AgentConnectionCredentialSource,
7
7
  AgentConnectionNodeDescriptor,
8
8
  AgentConnectionNodeRole,
9
+ McpServerResolver,
9
10
  } from "./ai/AgentConnectionNodeCollector";
10
11
  export * from "./workflow";
11
12
  export * from "./di";
@@ -34,3 +35,12 @@ export type {
34
35
  PollingRunCycleResult,
35
36
  PollingTriggerStartArgs,
36
37
  } from "./triggers/polling";
38
+ export { WorkflowEdgePortValidator } from "./validation/WorkflowEdgePortValidator";
39
+ export type { WorkflowEdgePortError, WorkflowEdgePortValidationResult } from "./validation/WorkflowEdgePortError.types";
40
+ export type {
41
+ OAuthFlowStartArgs,
42
+ OAuthFlowStartResult,
43
+ OAuthFlowCallbackArgs,
44
+ OAuthMaterial,
45
+ OAuthFlowExecutor,
46
+ } from "./credentials/OAuthFlowExecutor.types";
@@ -161,7 +161,9 @@ export class RunContinuationService {
161
161
 
162
162
  const completedActivations = (state.engineCounters?.completedNodeActivations ?? 0) + 1;
163
163
  const engineCounters = { completedNodeActivations: completedActivations };
164
- const maxNodeActivations = state.executionOptions?.maxNodeActivations ?? Number.MAX_SAFE_INTEGER;
164
+ const maxNodeActivations =
165
+ state.executionOptions?.maxNodeActivations ??
166
+ this.executionLimitsPolicy.createRootExecutionOptions().maxNodeActivations!;
165
167
 
166
168
  if (this.semantics.isStopConditionSatisfied(state.control?.stopCondition, args.nodeId)) {
167
169
  const completedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
@@ -566,7 +568,9 @@ export class RunContinuationService {
566
568
 
567
569
  const completedActivations = (args.state.engineCounters?.completedNodeActivations ?? 0) + 1;
568
570
  const engineCounters = { completedNodeActivations: completedActivations };
569
- const maxNodeActivations = args.state.executionOptions?.maxNodeActivations ?? Number.MAX_SAFE_INTEGER;
571
+ const maxNodeActivations =
572
+ args.state.executionOptions?.maxNodeActivations ??
573
+ this.executionLimitsPolicy.createRootExecutionOptions().maxNodeActivations!;
570
574
 
571
575
  if (this.semantics.isStopConditionSatisfied(args.state.control?.stopCondition, args.args.nodeId)) {
572
576
  const completedState = this.persistedRunStateTerminalBuilder.mergeTerminal({
@@ -47,6 +47,16 @@ export class InMemoryBinaryStorage implements BinaryStorage {
47
47
  async delete(storageKey: string): Promise<void> {
48
48
  this.values.delete(storageKey);
49
49
  }
50
+
51
+ async deleteMany(storageKeys: ReadonlyArray<string>): Promise<void> {
52
+ for (const key of storageKeys) {
53
+ this.values.delete(key);
54
+ }
55
+ }
56
+
57
+ async listByPrefix(prefix: string): Promise<ReadonlyArray<string>> {
58
+ return Array.from(this.values.keys()).filter((key) => key.startsWith(prefix));
59
+ }
50
60
  }
51
61
 
52
62
  export { BinaryBodyBufferReader } from "./BinaryBodyBufferReader";