@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.
- package/CHANGELOG.md +183 -0
- package/dist/CostCatalogContract-DZgcUBE4.d.cts +19 -0
- package/dist/{EngineRuntimeRegistration.types-ClLuY1FG.d.ts → EngineRuntimeRegistration.types-BQbS9_gs.d.ts} +2 -2
- package/dist/{EngineRuntimeRegistration.types-BryWi2mA.d.cts → EngineRuntimeRegistration.types-Cggm5GVY.d.cts} +8 -2
- package/dist/{InMemoryRunDataFactory-DeXNJt1O.d.cts → InMemoryRunDataFactory-C7YItvHG.d.cts} +9 -19
- package/dist/{InMemoryRunEventBusRegistry-sM4z4n_i.js → InMemoryRunEventBusRegistry-Bwunvt1T.js} +1 -1
- package/dist/{InMemoryRunEventBusRegistry-sM4z4n_i.js.map → InMemoryRunEventBusRegistry-Bwunvt1T.js.map} +1 -1
- package/dist/{InMemoryRunEventBusRegistry-VM3OWnHo.cjs → InMemoryRunEventBusRegistry-Sa86VxuV.cjs} +1 -1
- package/dist/{InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map → InMemoryRunEventBusRegistry-Sa86VxuV.cjs.map} +1 -1
- package/dist/ItemsInputNormalizer-C_dpn76M.d.cts +407 -0
- package/dist/ItemsInputNormalizer-CwdOhSAK.cjs +43 -0
- package/dist/ItemsInputNormalizer-CwdOhSAK.cjs.map +1 -0
- package/dist/ItemsInputNormalizer-D-MH8MBs.js +36 -0
- package/dist/ItemsInputNormalizer-D-MH8MBs.js.map +1 -0
- package/dist/ItemsInputNormalizer-_Mfcd3YU.d.ts +321 -0
- package/dist/RunIntentService-BVur7x9n.d.ts +285 -0
- package/dist/RunIntentService-CEF-sFfI.d.cts +206 -0
- package/dist/{RunIntentService-BqNjrksF.d.cts → agentMcpTypes-ZiNbNsEi.d.cts} +1717 -1809
- package/dist/bootstrap/index.cjs +4 -2
- package/dist/bootstrap/index.d.cts +8 -3
- package/dist/bootstrap/index.d.ts +6 -4
- package/dist/bootstrap/index.js +4 -2
- package/dist/{bootstrap-DtjQtuvi.cjs → bootstrap-BxuTFTLB.cjs} +41 -34
- package/dist/bootstrap-BxuTFTLB.cjs.map +1 -0
- package/dist/{bootstrap-BfFKGzyj.js → bootstrap-D_Yyi0wL.js} +9 -2
- package/dist/bootstrap-D_Yyi0wL.js.map +1 -0
- package/dist/browser.cjs +16 -0
- package/dist/browser.d.cts +4 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +4 -0
- package/dist/contracts-CK0x6w_G.cjs +74 -0
- package/dist/contracts-CK0x6w_G.cjs.map +1 -0
- package/dist/contracts-DXdfTdpW.js +50 -0
- package/dist/contracts-DXdfTdpW.js.map +1 -0
- package/dist/contracts.cjs +6 -0
- package/dist/contracts.d.cts +5 -0
- package/dist/contracts.d.ts +2 -0
- package/dist/contracts.js +3 -0
- package/dist/di-0Wop7z1y.js +376 -0
- package/dist/di-0Wop7z1y.js.map +1 -0
- package/dist/di-BlEKdoZS.cjs +489 -0
- package/dist/di-BlEKdoZS.cjs.map +1 -0
- package/dist/executionPersistenceContracts-BgZMRsTa.d.cts +275 -0
- package/dist/{index-CJQtTY_M.d.ts → index-62Ba9f7D.d.ts} +114 -320
- package/dist/{RunIntentService-CI-F8qQ7.d.ts → index-zWGtEhrf.d.ts} +1811 -1914
- package/dist/index.cjs +76 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -656
- package/dist/index.d.ts +5 -3
- package/dist/index.js +52 -50
- package/dist/index.js.map +1 -1
- package/dist/params-B5SENSzZ.d.cts +44 -0
- package/dist/{runtime-_ywksLa6.cjs → runtime-DBzq5YBi.cjs} +125 -489
- package/dist/runtime-DBzq5YBi.cjs.map +1 -0
- package/dist/{runtime-DbMjpb5d.js → runtime-cxmUkk0l.js} +117 -369
- package/dist/runtime-cxmUkk0l.js.map +1 -0
- package/dist/testing.cjs +23 -21
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -3
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +5 -3
- package/dist/testing.js.map +1 -1
- package/package.json +12 -17
- package/src/ai/AgentConnectionNodeCollector.ts +47 -5
- package/src/authoring/defineNode.types.ts +21 -1
- package/src/authoring/definePollingTrigger.types.ts +20 -0
- package/src/binaries/UnavailableBinaryStorage.ts +6 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +9 -0
- package/src/browser.ts +1 -0
- package/src/contracts/AgentBindError.ts +11 -0
- package/src/contracts/CodemationTelemetryAttributeNames.ts +4 -0
- package/src/contracts/NoOpAgentMcpIntegration.ts +13 -0
- package/src/contracts/agentMcpTypes.ts +64 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/mcpTypes.ts +29 -0
- package/src/contracts/runTypes.ts +8 -0
- package/src/contracts/runtimeTypes.ts +4 -0
- package/src/contracts/workflowTypes.ts +21 -0
- package/src/contracts.ts +3 -0
- package/src/credentials/OAuthFlowExecutor.types.ts +45 -0
- package/src/di/CoreTokens.ts +7 -0
- package/src/execution/InProcessRetryRunner.ts +31 -5
- package/src/execution/NodeExecutor.ts +27 -7
- package/src/execution/NodeRunStateWriter.ts +2 -0
- package/src/index.ts +10 -0
- package/src/orchestration/RunContinuationService.ts +6 -2
- package/src/runStorage/InMemoryBinaryStorageRegistry.ts +10 -0
- package/src/scheduler/InlineDrivingScheduler.ts +26 -22
- package/src/types/index.ts +1 -0
- package/src/validation/WorkflowEdgePortError.types.ts +16 -0
- package/src/validation/WorkflowEdgePortValidator.ts +52 -0
- package/src/workflow/definition/ConnectionInvocationIdFactory.ts +4 -3
- package/src/workflow/definition/ConnectionNodeIdFactory.ts +25 -0
- package/src/workflow/definition/NodeIterationIdFactory.ts +5 -3
- package/src/workflowSnapshots/WorkflowSnapshotCodec.ts +42 -10
- package/tsdown.config.ts +1 -1
- package/dist/bootstrap-BfFKGzyj.js.map +0 -1
- package/dist/bootstrap-DtjQtuvi.cjs.map +0 -1
- package/dist/runtime-DbMjpb5d.js.map +0 -1
- 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
|
|
|
@@ -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
|
+
}
|
package/src/contracts/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/di/CoreTokens.ts
CHANGED
|
@@ -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>(
|
|
26
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 =
|
|
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 =
|
|
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";
|