@decocms/bindings 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@decocms/bindings",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
7
7
  "test": "bun test"
8
8
  },
9
9
  "dependencies": {
10
- "@modelcontextprotocol/sdk": "1.27.1",
11
- "@tanstack/react-router": "1.139.7",
12
- "react": "^19.2.0",
10
+ "@modelcontextprotocol/sdk": "1.29.0",
11
+ "@tanstack/react-router": "1.169.2",
12
+ "react": "^19.2.6",
13
13
  "zod": "^4.0.0",
14
14
  "zod-from-json-schema": "^0.5.2"
15
15
  },
@@ -32,7 +32,8 @@
32
32
  "./plugin-router": "./src/core/plugin-router.tsx",
33
33
  "./ai-gateway": "./src/well-known/ai-gateway.ts",
34
34
  "./server-plugin": "./src/core/server-plugin.ts",
35
- "./trigger": "./src/well-known/trigger.ts"
35
+ "./trigger": "./src/well-known/trigger.ts",
36
+ "./brand": "./src/well-known/brand.ts"
36
37
  },
37
38
  "engines": {
38
39
  "node": ">=24.0.0"
@@ -24,7 +24,6 @@ export interface ToolBinder<
24
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
25
  TInput = any,
26
26
  TReturn extends object | null | boolean = object,
27
- TStreamable extends boolean = boolean,
28
27
  > {
29
28
  /** The name of the tool (e.g., "DECO_CHAT_CHANNELS_JOIN") */
30
29
  name: TName;
@@ -33,12 +32,7 @@ export interface ToolBinder<
33
32
  inputSchema: ZodType<TInput>;
34
33
 
35
34
  /** Optional Zod schema for validating tool output */
36
- outputSchema?: TStreamable extends true ? never : ZodType<TReturn>;
37
-
38
- /**
39
- * Whether this tool is streamable.
40
- */
41
- streamable?: TStreamable;
35
+ outputSchema?: ZodType<TReturn>;
42
36
 
43
37
  /**
44
38
  * Whether this tool is optional in the binding.
@@ -103,13 +97,6 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
103
97
  forClient: (client: ServerClient): MCPClientFetchStub<TDefinition> => {
104
98
  return createMCPFetchStub<TDefinition>({
105
99
  client,
106
- streamable: binder.reduce(
107
- (acc, tool) => {
108
- acc[tool.name] = tool.streamable === true;
109
- return acc;
110
- },
111
- {} as Record<string, boolean>,
112
- ),
113
100
  });
114
101
  },
115
102
  forConnection: (
@@ -117,13 +104,6 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
117
104
  ): MCPClientFetchStub<TDefinition> => {
118
105
  return createMCPFetchStub<TDefinition>({
119
106
  connection: mcpConnection,
120
- streamable: binder.reduce(
121
- (acc, tool) => {
122
- acc[tool.name] = tool.streamable === true;
123
- return acc;
124
- },
125
- {} as Record<string, boolean>,
126
- ),
127
107
  });
128
108
  },
129
109
  };
@@ -1,7 +1,6 @@
1
1
  export { HTTPClientTransport } from "./http-client-transport";
2
2
  export {
3
3
  createMCPFetchStub,
4
- isStreamableToolBinder,
5
4
  MCPClient,
6
5
  type CreateStubAPIOptions,
7
6
  type MCPClientFetchStub,
@@ -47,11 +47,6 @@ export interface ServerClient {
47
47
  callTool: Client["callTool"];
48
48
  listTools: () => Promise<ListToolsResult>;
49
49
  };
50
- callStreamableTool?: (
51
- tool: string,
52
- args: Record<string, unknown>,
53
- signal?: AbortSignal,
54
- ) => Promise<Response>;
55
50
  }
56
51
  export const createServerClient = async (
57
52
  mcpServer: { connection: MCPConnection; name?: string },
@@ -73,36 +68,6 @@ export const createServerClient = async (
73
68
 
74
69
  return {
75
70
  client,
76
- callStreamableTool: (tool, args, signal) => {
77
- if (mcpServer.connection.type !== "HTTP") {
78
- throw new Error("HTTP connection required");
79
- }
80
-
81
- const headers = new Headers(extraHeaders);
82
-
83
- if (!headers.has("Authorization")) {
84
- headers.set("Authorization", `Bearer ${mcpServer.connection.token}`);
85
- }
86
-
87
- for (const [key, value] of Object.entries(
88
- mcpServer.connection.headers ?? {},
89
- )) {
90
- headers.set(key, value);
91
- }
92
-
93
- const url = new URL(mcpServer.connection.url);
94
- // Trim trailing slashes from pathname, ensuring it starts with '/'
95
- const trimmedPath = url.pathname.replace(/\/+$/, "") || "/";
96
- url.pathname = `${trimmedPath}/call-tool/${encodeURIComponent(tool)}`;
97
-
98
- return fetch(url.href, {
99
- method: "POST",
100
- redirect: "manual",
101
- body: JSON.stringify(args),
102
- headers,
103
- signal,
104
- });
105
- },
106
71
  };
107
72
  };
108
73
 
@@ -3,12 +3,6 @@ import { z } from "zod";
3
3
  import type { MCPConnection } from "../connection";
4
4
  import { createMCPClientProxy } from "./proxy";
5
5
  export type { ServerClient } from "./mcp-client";
6
- export const isStreamableToolBinder = (
7
- toolBinder: ToolBinder,
8
- ): toolBinder is ToolBinder<string, any, any, true> => {
9
- return toolBinder.streamable === true;
10
- };
11
-
12
6
  // Default fetcher instance with API_SERVER_URL and API_HEADERS
13
7
  export const MCPClient = new Proxy(
14
8
  {} as {
@@ -69,13 +63,13 @@ export type MCPClientFetchStub<TDefinition extends readonly ToolBinder[]> = {
69
63
  }[]
70
64
  >;
71
65
  } & {
72
- [K in TDefinition[number] as K["name"]]: K["streamable"] extends true
73
- ? K extends ToolBinder<string, infer TInput, any, true>
74
- ? (params: TInput, init?: RequestInit) => Promise<Response>
75
- : never
76
- : K extends ToolBinder<string, infer TInput, infer TReturn, any>
77
- ? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
78
- : never;
66
+ [K in TDefinition[number] as K["name"]]: K extends ToolBinder<
67
+ string,
68
+ infer TInput,
69
+ infer TReturn
70
+ >
71
+ ? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
72
+ : never;
79
73
  };
80
74
 
81
75
  export interface MCPClientRaw {
@@ -93,7 +87,6 @@ export type JSONSchemaToZodConverter = (jsonSchema: any) => z.ZodTypeAny;
93
87
 
94
88
  export interface CreateStubForClientAPIOptions {
95
89
  client: ServerClient;
96
- streamable?: Record<string, boolean>;
97
90
  debugId?: () => string;
98
91
  getErrorByStatusCode?: (
99
92
  statusCode: number,
@@ -105,7 +98,6 @@ export interface CreateStubForClientAPIOptions {
105
98
 
106
99
  export interface CreateStubForConnectionAPIOptions {
107
100
  connection: MCPConnection;
108
- streamable?: Record<string, boolean>;
109
101
  debugId?: () => string;
110
102
  createServerClient?: (
111
103
  mcpServer: { connection: MCPConnection; name?: string },
@@ -74,16 +74,7 @@ export function createMCPClientProxy<T extends Record<string, unknown>>(
74
74
  ? { "x-trace-debug-id": debugId }
75
75
  : undefined;
76
76
 
77
- const { client, callStreamableTool } = await createClient(extraHeaders);
78
-
79
- if (options?.streamable?.[String(toolName)]) {
80
- if (!callStreamableTool) {
81
- throw new Error(
82
- `Tool ${String(toolName)} requires streaming support but client doesn't provide callStreamableTool`,
83
- );
84
- }
85
- return await callStreamableTool(String(toolName), args);
86
- }
77
+ const { client } = await createClient(extraHeaders);
87
78
 
88
79
  const { structuredContent, isError, content } = await client.callTool({
89
80
  name: String(toolName),
@@ -29,6 +29,13 @@ export interface RegisterEmptyStateParams {
29
29
  component: ReactNode;
30
30
  }
31
31
 
32
+ export interface RegisterSettingsSidebarItemParams {
33
+ key: string;
34
+ icon: ReactNode;
35
+ label: string;
36
+ to: string;
37
+ }
38
+
32
39
  export interface PluginSetupContext {
33
40
  parentRoute: AnyRoute;
34
41
  routing: {
@@ -37,6 +44,9 @@ export interface PluginSetupContext {
37
44
  };
38
45
  registerRootSidebarItem: (params: RegisterRootSidebarItemParams) => void;
39
46
  registerSidebarGroup: (params: RegisterSidebarGroupParams) => void;
47
+ registerSettingsSidebarItem: (
48
+ params: RegisterSettingsSidebarItemParams,
49
+ ) => void;
40
50
  registerPluginRoutes: (route: AnyRoute[]) => void;
41
51
  }
42
52
 
@@ -16,9 +16,9 @@ import type { Hono } from "hono";
16
16
  import type { Kysely } from "kysely";
17
17
 
18
18
  /**
19
- * Subset of MeshContext exposed to server plugin tool handlers.
19
+ * Subset of StudioContext exposed to server plugin tool handlers.
20
20
  *
21
- * Plugins receive the full MeshContext at runtime but should only depend on
21
+ * Plugins receive the full StudioContext at runtime but should only depend on
22
22
  * these properties. This keeps the plugin contract stable and avoids coupling
23
23
  * plugins to Mesh internals (db, vault, tracer, etc.).
24
24
  */
package/src/index.ts CHANGED
@@ -123,3 +123,25 @@ export {
123
123
 
124
124
  // Re-export workflow binding types
125
125
  export { WORKFLOWS_COLLECTION_BINDING } from "./well-known/workflow";
126
+
127
+ // Re-export brand binding types (for reading org brand context)
128
+ export {
129
+ BrandColorsSchema,
130
+ type BrandColors,
131
+ BrandFontsSchema,
132
+ type BrandFonts,
133
+ BrandAssetsSchema,
134
+ type BrandAssets,
135
+ BrandSchema,
136
+ type Brand,
137
+ BrandGetInputSchema,
138
+ type BrandGetInput,
139
+ type BrandGetOutput,
140
+ BrandListInputSchema,
141
+ type BrandListInput,
142
+ BrandListOutputSchema,
143
+ type BrandListOutput,
144
+ BRAND_BINDING,
145
+ BrandBinding,
146
+ type BrandBindingClient,
147
+ } from "./well-known/brand";
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Brand Well-Known Binding
3
+ *
4
+ * Defines the interface for reading brand context (colors, fonts, assets,
5
+ * voice) from an organization. External MCPs that need brand awareness
6
+ * can declare a dependency on this binding and call BRAND_GET / BRAND_LIST
7
+ * to pull structured brand data on demand.
8
+ *
9
+ * This binding includes:
10
+ * - BRAND_GET: Get a brand by ID (or the org default)
11
+ * - BRAND_LIST: List all active brands (optional)
12
+ */
13
+
14
+ import { z } from "zod";
15
+ import { bindingClient, type ToolBinder } from "../core/binder";
16
+
17
+ // ============================================================================
18
+ // Brand Sub-Schemas
19
+ // ============================================================================
20
+
21
+ export const BrandColorsSchema = z.object({
22
+ primary: z.string().optional().describe("Primary brand color (hex)"),
23
+ secondary: z.string().optional().describe("Secondary brand color (hex)"),
24
+ accent: z.string().optional().describe("Accent / highlight color (hex)"),
25
+ background: z.string().optional().describe("Background color (hex)"),
26
+ foreground: z.string().optional().describe("Foreground / text color (hex)"),
27
+ });
28
+
29
+ export type BrandColors = z.infer<typeof BrandColorsSchema>;
30
+
31
+ export const BrandFontsSchema = z.object({
32
+ heading: z.string().optional().describe("Font family for headings"),
33
+ body: z.string().optional().describe("Font family for body text"),
34
+ code: z.string().optional().describe("Font family for code / monospace"),
35
+ });
36
+
37
+ export type BrandFonts = z.infer<typeof BrandFontsSchema>;
38
+
39
+ export const BrandAssetsSchema = z.object({
40
+ logo: z.string().optional().describe("Logo URL"),
41
+ favicon: z.string().optional().describe("Favicon URL"),
42
+ ogImage: z.string().optional().describe("Open Graph image URL"),
43
+ });
44
+
45
+ export type BrandAssets = z.infer<typeof BrandAssetsSchema>;
46
+
47
+ // ============================================================================
48
+ // Brand Schema
49
+ // ============================================================================
50
+
51
+ export const BrandSchema = z.object({
52
+ id: z.string(),
53
+ name: z.string().describe("Brand / company name"),
54
+ domain: z.string().optional().describe("Company domain (e.g. example.com)"),
55
+ colors: BrandColorsSchema.optional().describe("Semantic color palette"),
56
+ fonts: BrandFontsSchema.optional().describe("Font families by role"),
57
+ assets: BrandAssetsSchema.optional().describe("Visual identity assets"),
58
+ overview: z.string().optional().describe("Company overview / description"),
59
+ tagline: z.string().optional().describe("Brand tagline"),
60
+ tone: z.string().optional().describe("Tone of voice description"),
61
+ metadata: z
62
+ .record(z.string(), z.unknown())
63
+ .optional()
64
+ .describe("Extra design tokens and metadata"),
65
+ });
66
+
67
+ export type Brand = z.infer<typeof BrandSchema>;
68
+
69
+ // ============================================================================
70
+ // BRAND_GET Schemas
71
+ // ============================================================================
72
+
73
+ export const BrandGetInputSchema = z.object({
74
+ id: z
75
+ .string()
76
+ .optional()
77
+ .describe("Brand ID. Omit to get the default brand."),
78
+ });
79
+
80
+ export type BrandGetInput = z.infer<typeof BrandGetInputSchema>;
81
+
82
+ export type BrandGetOutput = z.infer<typeof BrandSchema>;
83
+
84
+ // ============================================================================
85
+ // BRAND_LIST Schemas
86
+ // ============================================================================
87
+
88
+ export const BrandListInputSchema = z.object({});
89
+
90
+ export type BrandListInput = z.infer<typeof BrandListInputSchema>;
91
+
92
+ export const BrandListOutputSchema = z.object({
93
+ items: z.array(BrandSchema),
94
+ });
95
+
96
+ export type BrandListOutput = z.infer<typeof BrandListOutputSchema>;
97
+
98
+ // ============================================================================
99
+ // Brand Binding
100
+ // ============================================================================
101
+
102
+ /**
103
+ * Brand Binding
104
+ *
105
+ * Defines the interface for reading brand context from an organization.
106
+ *
107
+ * Required tools:
108
+ * - BRAND_GET: Get a single brand by ID, or the default brand when no ID
109
+ *
110
+ * Optional tools:
111
+ * - BRAND_LIST: List all active brands
112
+ */
113
+ export const BRAND_BINDING = [
114
+ {
115
+ name: "BRAND_GET" as const,
116
+ inputSchema: BrandGetInputSchema,
117
+ outputSchema: BrandSchema,
118
+ },
119
+ {
120
+ name: "BRAND_LIST" as const,
121
+ inputSchema: BrandListInputSchema,
122
+ outputSchema: BrandListOutputSchema,
123
+ opt: true,
124
+ },
125
+ ] satisfies ToolBinder[];
126
+
127
+ /**
128
+ * Brand Binding Client
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { BrandBinding } from "@decocms/bindings/brand";
133
+ *
134
+ * const client = BrandBinding.forConnection(connection);
135
+ *
136
+ * // Get the default brand
137
+ * const brand = await client.BRAND_GET({});
138
+ *
139
+ * // Get a specific brand
140
+ * const brand = await client.BRAND_GET({ id: "acme-corp" });
141
+ *
142
+ * // List all brands
143
+ * const { items } = await client.BRAND_LIST({});
144
+ * ```
145
+ */
146
+ export const BrandBinding = bindingClient(BRAND_BINDING);
147
+
148
+ /**
149
+ * Type helper for the Brand binding client
150
+ */
151
+ export type BrandBindingClient = ReturnType<typeof BrandBinding.forConnection>;
@@ -712,7 +712,6 @@ export const LANGUAGE_MODEL_BINDING = [
712
712
  {
713
713
  name: "LLM_DO_STREAM" as const,
714
714
  inputSchema: LanguageModelInputSchema,
715
- streamable: true,
716
715
  },
717
716
  {
718
717
  name: "LLM_DO_GENERATE" as const,
@@ -21,22 +21,22 @@ export const ToolCallActionSchema = z.object({
21
21
  transformCode: z
22
22
  .string()
23
23
  .optional()
24
- .describe(`Pure TypeScript function for data transformation of the tool call result. Must be a TypeScript file that declares the Output interface and exports a default function: \`interface Output { ... } export default async function(input): Output { ... }\`
25
- The input will match with the tool call outputSchema. If transformCode is not provided, the tool call result will be used as the step output.
24
+ .describe(`Pure TypeScript function for data transformation of the tool call result. Must be a TypeScript file that declares the Output interface and exports a default function: \`interface Output { ... } export default async function(stepInput): Output { ... }\`
25
+ The stepInput will match with the tool call outputSchema. If transformCode is not provided, the tool call result will be used as the step output.
26
26
  Providing an transformCode is recommended because it both allows you to transform the data and validate it against a JSON Schema - tools are ephemeral and may return unexpected data.`),
27
27
  });
28
28
  export type ToolCallAction = z.infer<typeof ToolCallActionSchema>;
29
29
 
30
30
  export const CodeActionSchema = z.object({
31
31
  code: z.string().describe(
32
- `Pure TypeScript function for data transformation. Useful to merge data from multiple steps and transform it. Must be a TypeScript file that declares the Output interface and exports a default function: \`interface Output { ... } export default async function(input): Output { ... }\`
33
- The input is the resolved value of the references in the input field. Example:
32
+ `Pure TypeScript function for data transformation. Useful to merge data from multiple steps and transform it. Must be a TypeScript file that declares the Output interface and exports a default function: \`interface Output { ... } export default async function(stepInput): Output { ... }\`
33
+ The stepInput is the resolved value of the references in the input field. Example:
34
34
  {
35
35
  "input": {
36
36
  "name": "@Step_1.name",
37
37
  "age": "@Step_2.age"
38
38
  },
39
- "code": "export default function(input): Output { return { result: \`\${input.name} is \${input.age} years old.\` } }"
39
+ "code": "export default function(stepInput): Output { return { result: \`\${stepInput.name} is \${stepInput.age} years old.\` } }"
40
40
  }
41
41
  `,
42
42
  ),
@@ -66,19 +66,60 @@ export type StepAction = z.infer<typeof StepActionSchema>;
66
66
  /**
67
67
  * Step Config Schema - Optional configuration for retry, timeout, and looping
68
68
  */
69
+ /**
70
+ * Step Condition Schema - Structured condition for bail and future `when` support.
71
+ * Evaluates a resolved @ref against an optional operator.
72
+ * If no operator is specified, checks for truthiness.
73
+ */
74
+ export const StepConditionSchema = z.object({
75
+ ref: z
76
+ .string()
77
+ .describe(
78
+ "@ref to resolve and evaluate, e.g. '@validate.shouldStop' or '@input.skipProcessing'",
79
+ ),
80
+ eq: z
81
+ .unknown()
82
+ .optional()
83
+ .describe("Step bails if resolved value equals this"),
84
+ neq: z
85
+ .unknown()
86
+ .optional()
87
+ .describe("Step bails if resolved value does NOT equal this"),
88
+ gt: z
89
+ .number()
90
+ .optional()
91
+ .describe("Step bails if resolved value is greater than this"),
92
+ lt: z
93
+ .number()
94
+ .optional()
95
+ .describe("Step bails if resolved value is less than this"),
96
+ });
97
+ export type StepCondition = z.infer<typeof StepConditionSchema>;
98
+
69
99
  export const StepConfigSchema = z.object({
70
100
  maxAttempts: z
71
101
  .number()
102
+ .int()
103
+ .min(1)
104
+ .max(10)
72
105
  .optional()
73
106
  .describe("Max retry attempts on failure (default: 1, no retries)"),
74
107
  backoffMs: z
75
108
  .number()
109
+ .int()
110
+ .min(100)
111
+ .max(60_000)
76
112
  .optional()
77
113
  .describe("Initial delay between retries in ms (doubles each attempt)"),
78
114
  timeoutMs: z
79
115
  .number()
116
+ .int()
117
+ .min(100)
118
+ .max(86_400_000)
80
119
  .optional()
81
- .describe("Max execution time in ms before step fails (default: 30000)"),
120
+ .describe(
121
+ "Max execution time in ms before step fails (default: 30000). Upper bound 24h.",
122
+ ),
82
123
  onError: z
83
124
  .enum(["fail", "continue"])
84
125
  .optional()
@@ -102,16 +143,21 @@ export type StepConfig = z.infer<typeof StepConfigSchema>;
102
143
  * - @ctx.execution_id → current workflow execution ID
103
144
  */
104
145
 
105
- type JsonSchema = {
146
+ export type JsonSchema = {
106
147
  type?: string;
107
- properties?: Record<string, unknown>;
148
+ properties?: Record<string, JsonSchema>;
108
149
  required?: string[];
109
150
  description?: string;
110
151
  additionalProperties?: boolean | Record<string, unknown>;
111
152
  additionalItems?: boolean | Record<string, unknown>;
112
153
  items?: JsonSchema;
154
+ format?: string;
155
+ enum?: string[];
156
+ maxLength?: number;
157
+ anyOf?: JsonSchema[];
158
+ [key: string]: unknown;
113
159
  };
114
- const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() =>
160
+ export const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() =>
115
161
  z
116
162
  .object({
117
163
  type: z.string().optional(),
@@ -127,7 +173,7 @@ const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() =>
127
173
  items: JsonSchemaSchema.optional(),
128
174
  })
129
175
  .passthrough(),
130
- );
176
+ ) as unknown as z.ZodType<JsonSchema>;
131
177
 
132
178
  /**
133
179
  * Step names that are reserved by the @ref system and cannot be used as step names.
@@ -170,6 +216,12 @@ export const StepSchema = z.object({
170
216
  .describe("max parallel iterations. default is 1 (sequential)"),
171
217
  })
172
218
  .optional(),
219
+ bail: z
220
+ .union([z.literal(true), StepConditionSchema])
221
+ .optional()
222
+ .describe(
223
+ "Early return: if true, the workflow exits with this step's output on successful completion. If a condition object, the workflow exits only when the condition is met. Bail is ignored on step error.",
224
+ ),
173
225
  });
174
226
 
175
227
  export type Step = z.infer<typeof StepSchema>;
@@ -324,6 +376,10 @@ export const WorkflowSchema = BaseCollectionEntitySchema.extend({
324
376
  .optional()
325
377
  .describe("Human-readable summary of what this workflow does"),
326
378
 
379
+ input_schema: JsonSchemaSchema.optional().describe(
380
+ "Optional JSON Schema describing the expected input for this workflow.",
381
+ ),
382
+
327
383
  steps: z
328
384
  .array(StepSchema)
329
385
  .describe(
@@ -370,7 +426,7 @@ export const DEFAULT_TOOL_STEP: Omit<Step, "name"> = {
370
426
  interface Input {
371
427
 
372
428
  }
373
- export default function(input) { return input.result }`,
429
+ export default function(stepInput) { return stepInput.result }`,
374
430
  },
375
431
  input: {
376
432
  modelId: "anthropic/claude-4.5-haiku",
@@ -399,10 +455,10 @@ export const DEFAULT_CODE_STEP: Step = {
399
455
  interface Output {
400
456
  result: unknown;
401
457
  }
402
-
403
- export default async function(input: Input): Promise<Output> {
458
+
459
+ export default async function(stepInput: Input): Promise<Output> {
404
460
  return {
405
- result: input.example
461
+ result: stepInput.example
406
462
  }
407
463
  }`,
408
464
  },
@@ -477,6 +533,7 @@ export const WORKFLOW_EXECUTION_BINDING = createCollectionBindings(
477
533
  export interface DAGStep {
478
534
  name: string;
479
535
  input?: unknown;
536
+ bail?: true | StepCondition;
480
537
  }
481
538
 
482
539
  /**
@@ -491,7 +548,7 @@ export function getAllRefs(input: unknown): string[] {
491
548
 
492
549
  function traverse(value: unknown) {
493
550
  if (typeof value === "string") {
494
- const matches = value.match(/@(\w+)/g);
551
+ const matches = value.match(/@([a-zA-Z_][a-zA-Z0-9_-]*)/g);
495
552
  if (matches) {
496
553
  refs.push(...matches.map((m) => m.substring(1))); // Remove @ prefix
497
554
  }
@@ -523,13 +580,13 @@ export function getStepDependencies(
523
580
  function traverse(value: unknown) {
524
581
  if (typeof value === "string") {
525
582
  // Match @stepName or @stepName.something patterns
526
- const matches = value.match(/@(\w+)/g);
583
+ const matches = value.match(/@([a-zA-Z_][a-zA-Z0-9_-]*)/g);
527
584
  if (matches) {
528
585
  for (const match of matches) {
529
586
  const refName = match.substring(1); // Remove @
530
587
  // Only count as dependency if it references another step
531
588
  // (not "item", "index", "input" from forEach or workflow input)
532
- if (allStepNames.has(refName)) {
589
+ if (allStepNames.has(refName) && refName !== step.name) {
533
590
  deps.push(refName);
534
591
  }
535
592
  }
@@ -542,6 +599,12 @@ export function getStepDependencies(
542
599
  }
543
600
 
544
601
  traverse(step.input);
602
+
603
+ // Extract dependency from bail condition ref
604
+ if (step.bail && step.bail !== true) {
605
+ traverse(step.bail.ref);
606
+ }
607
+
545
608
  return [...new Set(deps)];
546
609
  }
547
610