@codemation/agent-skills 0.1.10 → 0.2.0
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 +32 -0
- package/package.json +1 -1
- package/skills/codemation-ai-agent-node/SKILL.md +128 -0
- package/skills/codemation-custom-node-development/SKILL.md +34 -27
- package/skills/codemation-custom-node-development/references/credential-aware-nodes.md +38 -0
- package/skills/codemation-custom-node-development/references/define-batch-node.md +38 -0
- package/skills/codemation-custom-node-development/references/define-node-per-item.md +61 -0
- package/skills/codemation-custom-node-development/references/node-patterns.md +141 -0
- package/skills/codemation-framework-concepts/SKILL.md +10 -1
- package/skills/codemation-mcp-capabilities/SKILL.md +85 -0
- package/skills/codemation-mcp-capabilities/references/agent-with-mcp.ts +44 -0
- package/skills/codemation-plugin-development/SKILL.md +43 -0
- package/skills/codemation-workflow-dsl/SKILL.md +206 -16
- package/skills/codemation-workflow-dsl/references/builder-patterns.md +47 -15
- package/skills/codemation-workflow-dsl/references/complete-example.md +263 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference: using an MCP server in a workflow agent node.
|
|
3
|
+
*
|
|
4
|
+
* Before writing this, call GET /api/registry/capabilities?query=<name> to confirm
|
|
5
|
+
* the server id and credential type. Then list the server id under `mcpServers`.
|
|
6
|
+
*
|
|
7
|
+
* Cron / webhook workflows use createWorkflowBuilder({id, name}).trigger(new XxxTrigger(...))
|
|
8
|
+
* and chain with .then(new SomeNodeConfig(...)). The fluent .map/.if/.agent helpers are
|
|
9
|
+
* only available via workflow("id").manualTrigger(...). See codemation-workflow-dsl skill.
|
|
10
|
+
*
|
|
11
|
+
* `mcpServers` is a plain array of server ids. Each declared server surfaces a credential
|
|
12
|
+
* slot on the materialized MCP connection node (same shape as ChatModel/Tool connection
|
|
13
|
+
* nodes). The user binds a credential instance via the canvas credential dropdown before
|
|
14
|
+
* activation — same flow as trigger credentials.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { AIAgent, CronTrigger, createWorkflowBuilder } from "@codemation/core-nodes";
|
|
18
|
+
|
|
19
|
+
// Example: cron-triggered agent that uses the Gmail MCP server.
|
|
20
|
+
// The "gmail" id comes from the registry (acceptedCredentialTypes: ["oauth.google.gmail"]).
|
|
21
|
+
// The user must have connected their Google account and bound the credential before this runs.
|
|
22
|
+
|
|
23
|
+
export const summariseEmailsWorkflow = createWorkflowBuilder({
|
|
24
|
+
id: "wf.summarise-emails",
|
|
25
|
+
name: "Summarise unread emails",
|
|
26
|
+
})
|
|
27
|
+
.trigger(new CronTrigger("Weekdays at 09:00", { schedule: "0 9 * * 1-5", timezone: "UTC" }))
|
|
28
|
+
.then(
|
|
29
|
+
new AIAgent({
|
|
30
|
+
name: "Summarise",
|
|
31
|
+
mcpServers: ["gmail"],
|
|
32
|
+
messages: [
|
|
33
|
+
{
|
|
34
|
+
role: "system",
|
|
35
|
+
content: [
|
|
36
|
+
"You are an email assistant. Read the user's unread Gmail messages from the last 24 hours.",
|
|
37
|
+
"Summarise each one in one sentence. Output as a bullet list.",
|
|
38
|
+
"Do not draft or send any replies.",
|
|
39
|
+
].join("\n"),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
.build();
|
|
@@ -45,6 +45,49 @@ Do not use this skill for ordinary consumer workflow-only changes unless the wor
|
|
|
45
45
|
|
|
46
46
|
Import **`WorkflowTestKit`** from **`@codemation/core/testing`**. Use **`registerDefinedNodes([...])`** for `defineNode` packages, then **`runNode({ node: yourNode.create(...), items })`** or **`run({ workflow, items })`** for fuller graphs. Prefer this for fast node tests; use **`codemation dev:plugin`** when you need the UI and persistence.
|
|
47
47
|
|
|
48
|
+
## Declaring MCP servers from a plugin (`mcpServers?`)
|
|
49
|
+
|
|
50
|
+
A plugin can declare MCP servers that the framework merges into its in-memory catalog at startup. Use this for providers that need non-standard auth, custom adapter logic, or are shipping alongside custom nodes. For standard SaaS providers (OAuth via broker, plain bearer/API key), prefer the control-plane registry instead — no plugin code required.
|
|
51
|
+
|
|
52
|
+
### When to use plugin-declared MCP servers
|
|
53
|
+
|
|
54
|
+
- The provider's MCP server has non-standard auth the generic credential types cannot express.
|
|
55
|
+
- The plugin already ships custom nodes for the same provider and wants to co-locate the MCP declaration.
|
|
56
|
+
- Self-hosted deployments where no control-plane registry is available.
|
|
57
|
+
|
|
58
|
+
### Required fields
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { definePlugin } from "@codemation/host/authoring";
|
|
62
|
+
import type { McpServerDeclaration } from "@codemation/core";
|
|
63
|
+
|
|
64
|
+
const myServer: McpServerDeclaration = {
|
|
65
|
+
id: "my-service", // globally unique slug: /^[a-z0-9-]+$/
|
|
66
|
+
displayName: "My Service",
|
|
67
|
+
description: "Provides MCP tools for My Service.",
|
|
68
|
+
transport: "http",
|
|
69
|
+
url: "https://mcp.my-service.com",
|
|
70
|
+
// Credential types this server accepts. Users bind a credential instance
|
|
71
|
+
// per slot via the UI. Omit (or set to []) for servers requiring no auth.
|
|
72
|
+
acceptedCredentialTypes: ["my-service.bearer-token"],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default definePlugin({
|
|
76
|
+
mcpServers: [myServer],
|
|
77
|
+
// credentials, nodes, register, etc.
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Merge precedence
|
|
82
|
+
|
|
83
|
+
The framework merges from three sources in this order (last-write-wins on `id` collisions):
|
|
84
|
+
|
|
85
|
+
1. **Plugin** (lowest) — code in `codemation.plugin.ts`
|
|
86
|
+
2. **`codemation.config.ts`** — dev/self-host declarations
|
|
87
|
+
3. **Control-plane registry** (highest) — managed-mode fast lane; shadows plugin declarations to fix descriptions without a plugin release
|
|
88
|
+
|
|
89
|
+
A warning is logged when a higher-priority source shadows a plugin declaration. This is intentional.
|
|
90
|
+
|
|
48
91
|
## Read next when needed
|
|
49
92
|
|
|
50
93
|
- Read `references/plugin-structure.md` for package layout and node-versus-credential guidance.
|
|
@@ -1,31 +1,132 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codemation-workflow-dsl
|
|
3
|
-
description: Guides Codemation workflow authoring
|
|
4
|
-
compatibility: Designed for Codemation apps and plugins that author workflows
|
|
3
|
+
description: Guides Codemation workflow authoring. Use when creating or updating workflow definitions in `src/workflows` — manual-trigger flows via `workflow("...").manualTrigger(...)`, or cron/webhook/other triggers via `createWorkflowBuilder({id, name}).trigger(...)`.
|
|
4
|
+
compatibility: Designed for Codemation apps and plugins that author workflows.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Codemation Workflow DSL
|
|
8
8
|
|
|
9
9
|
## Use this skill when
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Authoring or reviewing workflow definitions under `src/workflows/`.
|
|
12
12
|
|
|
13
13
|
Do not use this skill for CLI-only troubleshooting or deep host architecture questions unless they directly affect workflow authoring.
|
|
14
14
|
|
|
15
|
+
## Discovering nodes and patterns
|
|
16
|
+
|
|
17
|
+
**Always call `find_examples` first** when you need to learn how to use a node or build a workflow pattern.
|
|
18
|
+
|
|
19
|
+
### Why examples are the canonical reference
|
|
20
|
+
|
|
21
|
+
Examples in the catalog typecheck, lint, and are verified by CI. They show the exact import paths, constructor signatures, and DSL shape that work in a real project — more efficiently than reading schema definitions or grepping framework source.
|
|
22
|
+
|
|
23
|
+
### When to call `find_examples` first
|
|
24
|
+
|
|
25
|
+
- Before writing any workflow that uses an unfamiliar node.
|
|
26
|
+
- When you need a pattern (polling, branching, sub-workflow, agent with tools, etc.) and aren't sure of the exact API.
|
|
27
|
+
- As your first step — before `read_skill`, before `search_capabilities`, before reading any file.
|
|
28
|
+
|
|
29
|
+
### Query patterns
|
|
30
|
+
|
|
31
|
+
Call `find_examples` in two ways:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// By node name:
|
|
35
|
+
find_examples({ query: "HttpRequest" });
|
|
36
|
+
find_examples({ query: "AIAgent" });
|
|
37
|
+
find_examples({ query: "CronTrigger" });
|
|
38
|
+
|
|
39
|
+
// By use case / intent:
|
|
40
|
+
find_examples({ query: "poll API and write to database" });
|
|
41
|
+
find_examples({ query: "AIAgent multi-step pipeline" });
|
|
42
|
+
find_examples({ query: "gmail trigger classify email" });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Mix both: `find_examples({ query: "AIAgent gmail classify" })` works too.
|
|
46
|
+
|
|
47
|
+
### Install state in results
|
|
48
|
+
|
|
49
|
+
Every search result includes `installed: boolean` and `requiresInstall: string[]`. Use these to plan installs (`install_package`) before adapting an example. If `installed` is `false` or `requiresInstall` is non-empty, call `install_package` for each missing package before writing any workflow code that imports them.
|
|
50
|
+
|
|
51
|
+
### When find_examples returns zero hits
|
|
52
|
+
|
|
53
|
+
Stop. Do not improvise from memory. Do one of:
|
|
54
|
+
|
|
55
|
+
1. **Ask the user**: "I don't have an example for `<query>`. Would you like me to adapt the closest match (`<nearest>`) or should a proper example be added first?"
|
|
56
|
+
2. **Adapt the closest near-miss** — only with the user's explicit confirmation that the approach is reasonable.
|
|
57
|
+
|
|
58
|
+
Do not attempt to infer node behavior by grepping framework source code (e.g. `node_modules/@codemation/*`). Examples convey the same information more efficiently and are authoritative.
|
|
59
|
+
|
|
60
|
+
## When no example matches — the self-solving fallback chain
|
|
61
|
+
|
|
62
|
+
If `find_examples` returns no good match for your query, **do not ask the user**. The user is non-technical and can't help you pick between framework primitives. Solve it using this fixed chain:
|
|
63
|
+
|
|
64
|
+
### Tier 1 — Retry with intent variations
|
|
65
|
+
|
|
66
|
+
Re-query with the underlying intent: a different verb, a more generic term, the closest standard pattern. Example: no hit for `"google sheets append row"` → retry `"http POST bearer credential"` or `"REST API call with credential"`.
|
|
67
|
+
|
|
68
|
+
### Tier 2 — Custom REST node (preferred for HTTP APIs)
|
|
69
|
+
|
|
70
|
+
If the task is "call an external HTTP API," use `defineRestNode`. Always works.
|
|
71
|
+
|
|
72
|
+
`find_examples({ query: "defineRestNode" })` → returns the canonical templates:
|
|
73
|
+
|
|
74
|
+
- `custom-rest-node-simple.example.ts` — basic shape
|
|
75
|
+
- `custom-rest-node-with-credential.example.ts` — with bearer/OAuth credential slot
|
|
76
|
+
|
|
77
|
+
Adapt these to the specific endpoint + payload shape needed.
|
|
78
|
+
|
|
79
|
+
### Tier 3 — Raw HttpRequest (inline, one-off)
|
|
80
|
+
|
|
81
|
+
If the call is one-shot inline in a workflow and you don't need to define a reusable node, use the `HttpRequest` config class.
|
|
82
|
+
|
|
83
|
+
`find_examples({ query: "HttpRequest" })` → `node-httprequest.example.ts`
|
|
84
|
+
|
|
85
|
+
### Tier 4 — defineNode (non-HTTP custom logic)
|
|
86
|
+
|
|
87
|
+
If the task isn't an HTTP call (data transformation, business logic, anything stateful), use `defineNode`.
|
|
88
|
+
|
|
89
|
+
`find_examples({ query: "defineNode template" })` → `custom-node-template.example.ts`
|
|
90
|
+
|
|
91
|
+
### What NOT to do
|
|
92
|
+
|
|
93
|
+
- Do NOT ask the user "should I use HttpRequest or defineRestNode?" — they can't help; pick using the chain.
|
|
94
|
+
- Do NOT grep `node_modules/@codemation/*` for node implementations — the templates above are the canonical reference.
|
|
95
|
+
- Do NOT invent a custom solution outside this chain.
|
|
96
|
+
|
|
97
|
+
### Surfacing what you did
|
|
98
|
+
|
|
99
|
+
After building, your final message to the concierge should state the technique used, e.g.:
|
|
100
|
+
|
|
101
|
+
> "Built using `defineRestNode` for the Google Sheets append call (no first-class Sheets node yet)."
|
|
102
|
+
|
|
103
|
+
This is informational, not a request for approval.
|
|
104
|
+
|
|
105
|
+
## There are TWO authoring APIs — pick by trigger type
|
|
106
|
+
|
|
107
|
+
| Trigger | API to use | Import | Available chain helpers |
|
|
108
|
+
| ----------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
109
|
+
| **Manual** (one-shot, optionally seeded with default items) | `workflow("id").manualTrigger(...)` | `import { workflow } from "@codemation/host"` | Full fluent sugar: `.map`, `.if`, `.switch`, `.split`, `.agent`, `.node`, `.then`, `.build` |
|
|
110
|
+
| **Cron, Webhook, Test, or any non-manual trigger** | `createWorkflowBuilder({ id, name }).trigger(new XxxTrigger(...))` | `import { createWorkflowBuilder, CronTrigger, WebhookTrigger } from "@codemation/core-nodes"` | Low-level `.then(new SomeNodeConfig(...))` only — **no** `.map`/`.if`/`.agent`/`.node` sugar |
|
|
111
|
+
|
|
112
|
+
**Why two APIs?** `workflow("...")` returns a `WorkflowAuthoringBuilder` that _only_ exposes `.name()` and `.manualTrigger(...)`. Once you call `.manualTrigger(...)`, you get a `WorkflowChain` that has all the fluent helpers. For any other trigger, you must use the lower-level `createWorkflowBuilder({id, name}).trigger(new Trigger(...))` path — the result is a `ChainCursor` whose only chain method is `.then(new NodeConfig(...))`. You compose by passing node config classes directly: `new Callback(...)`, `new HttpRequest(...)`, `new AIAgent(...)`, `new If(...)`, `new Split(...)`, etc.
|
|
113
|
+
|
|
114
|
+
If you find yourself wanting `.map` or `.if` on a cron workflow, you have two options: (a) accept the verbose `.then(new Callback(...))` style, or (b) wrap the cron-trigger cursor explicitly: `new WorkflowChain(builder.trigger(new CronTrigger(...)))` — but this is rare in practice; production cron workflows use plain `.then(new ConfigClass(...))`.
|
|
115
|
+
|
|
15
116
|
## Core mental model
|
|
16
117
|
|
|
17
118
|
1. A workflow definition describes how items move from a trigger through downstream steps.
|
|
18
|
-
2.
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
21
|
-
5. Fluent callback helpers follow the runtime item contract: `.map(...)`, `.if(...)`, and `.switch({ resolveCaseKey })` receive `(item, ctx)`, so row fields live under `item.json` and earlier completed outputs are available through `ctx.data`.
|
|
119
|
+
2. Activations are **batch-shaped** (`Items`); many steps use **per-item** execution (`execute`, including helper **`defineNode`**) with optional **`inputSchema`** and **`itemExpr`** on config fields. Batch reshape steps (split/filter/aggregate, **`defineBatchNode`**) work on the whole batch.
|
|
120
|
+
3. Fluent callback helpers (manual-trigger only) follow the runtime item contract: `.map(...)`, `.if(...)`, and `.switch({ resolveCaseKey })` receive `(item, ctx)`. Row fields live under `item.json`; earlier completed outputs are available through `ctx.data`.
|
|
121
|
+
4. Finish every workflow definition with `.build()`.
|
|
22
122
|
|
|
23
123
|
## Authoring rules
|
|
24
124
|
|
|
25
|
-
1.
|
|
125
|
+
1. **Pick the API by trigger type** (see table above). Don't try to call `.trigger(...)` on the `workflow(...)` builder — it doesn't exist there.
|
|
26
126
|
2. Keep workflow files focused on orchestration and named steps.
|
|
27
127
|
3. Use custom nodes when a callback grows into reusable product logic.
|
|
28
128
|
4. Distinguish **batch activations** from **per-item node bodies**: custom nodes from **`defineNode`** implement **`execute`** per item unless you chose **`defineBatchNode`** for batch **`run`**.
|
|
129
|
+
5. **Collection nodes (`collectionInsertNode`, `collectionGetNode`, `collectionListNode`, etc.) use `.then(node.create(...))` instead of `.node(label, node, opts)`.** TypeScript's inference can't bridge the recursive `ParamDeep` constraint when the node config contains `z.record(...)` fields. See `node-collection-crud.example.ts` for the canonical pattern.
|
|
29
130
|
|
|
30
131
|
## Node ids and stability
|
|
31
132
|
|
|
@@ -42,19 +143,60 @@ For nodes that hold credential bindings, the binding is keyed by `(workflowId, n
|
|
|
42
143
|
})
|
|
43
144
|
```
|
|
44
145
|
|
|
146
|
+
### Collision gotcha — set explicit ids on every node
|
|
147
|
+
|
|
148
|
+
Auto-derived ids can also **collide** when a trigger and a downstream node share a label. Example:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// ❌ Auto-derived ids collide: both slugify to "classify-feedback"
|
|
152
|
+
workflow("wf.feedback")
|
|
153
|
+
.manualTrigger("Classify feedback", {
|
|
154
|
+
/* ... */
|
|
155
|
+
})
|
|
156
|
+
.agent("Classify feedback", {
|
|
157
|
+
/* ... */
|
|
158
|
+
})
|
|
159
|
+
.build(); // throws WorkflowDefinitionError: duplicate nodeId "classify-feedback"
|
|
160
|
+
|
|
161
|
+
// ✅ Explicit id on the AIAgent disambiguates
|
|
162
|
+
workflow("wf.feedback")
|
|
163
|
+
.manualTrigger("Classify feedback", {
|
|
164
|
+
/* ... */
|
|
165
|
+
})
|
|
166
|
+
.agent("Classify feedback", { id: "classify-feedback-agent" /* ... */ })
|
|
167
|
+
.build();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Recommendation: always set an explicit `id:` on every node.** It's a few extra characters that buys you:
|
|
171
|
+
|
|
172
|
+
1. Stable credential bindings across label renames (above)
|
|
173
|
+
2. No collision build errors when refactoring labels
|
|
174
|
+
3. Stable references for any downstream code that addresses nodes by id (e.g. pinned-output state, test-suite assertions, audit-log entries)
|
|
175
|
+
|
|
176
|
+
The slug-derived default exists for quick prototyping; production workflows should declare ids.
|
|
177
|
+
|
|
45
178
|
## Typical flow
|
|
46
179
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
180
|
+
**Manual trigger (fluent):**
|
|
181
|
+
|
|
182
|
+
1. `workflow("wf.example.id")`.
|
|
183
|
+
2. `.name("Display name")` (optional — defaults to the id).
|
|
184
|
+
3. `.manualTrigger("Start", { /* default item json */ })`.
|
|
185
|
+
4. Chain transformations: `.map(...)`, `.if(...)`, `.switch(...)`, `.split(...)`, `.agent(...)`, `.node(...)`, `.then(...)`.
|
|
186
|
+
5. `.build()`.
|
|
187
|
+
|
|
188
|
+
**Cron / webhook (low-level):**
|
|
189
|
+
|
|
190
|
+
1. `createWorkflowBuilder({ id: "wf.example.id", name: "Display name" })`.
|
|
191
|
+
2. `.trigger(new CronTrigger("Label", { schedule, timezone }))` or `.trigger(new WebhookTrigger("Label", { endpointKey, methods }))`.
|
|
192
|
+
3. Chain with `.then(new SomeNodeConfig(...))` repeatedly. Common configs: `Callback`, `HttpRequest`, `AIAgent`, `If`, `Split`, `Merge`, `SubWorkflow`.
|
|
193
|
+
4. `.build()`.
|
|
52
194
|
|
|
53
195
|
## Built-in triggers
|
|
54
196
|
|
|
55
|
-
- **`ManualTrigger`** — one-shot manual run, optionally seeded with default items. Use
|
|
56
|
-
- **`WebhookTrigger`** — fires on an incoming HTTP request. Construct with `new WebhookTrigger(name, { endpointKey, methods })
|
|
57
|
-
- **`CronTrigger`** — fires on a cron schedule. Construct with `new CronTrigger(name, { schedule, timezone? })
|
|
197
|
+
- **`ManualTrigger`** — one-shot manual run, optionally seeded with default items. Use the fluent shortcut: `workflow("id").manualTrigger(name, items?)`. The shortcut internally wires up `createWorkflowBuilder(...).trigger(new ManualTrigger(...))` and wraps the result in `WorkflowChain` so you get the full fluent sugar.
|
|
198
|
+
- **`WebhookTrigger`** — fires on an incoming HTTP request. Construct with `new WebhookTrigger(name, { endpointKey, methods })`. Attach via `createWorkflowBuilder({id, name}).trigger(new WebhookTrigger(...))`.
|
|
199
|
+
- **`CronTrigger`** — fires on a cron schedule. Construct with `new CronTrigger(name, { schedule, timezone? })`. Attach via `createWorkflowBuilder({id, name}).trigger(new CronTrigger(...))`. The expression is validated at workflow build time. Each tick emits one item: `{ firedAt: string, scheduledFor: string }` (both ISO-8601). Defaults to UTC — always supply `timezone` for DST-sensitive schedules.
|
|
58
200
|
|
|
59
201
|
## Agent tools (callable helpers)
|
|
60
202
|
|
|
@@ -82,7 +224,55 @@ Authors invoke a TestSuiteRun from the canvas **Tests tab** or via `POST /api/wo
|
|
|
82
224
|
|
|
83
225
|
Custom nodes can also read `ctx.testContext?.{testSuiteRunId, testCaseIndex}` directly — useful for synthetic outputs in test mode without `IsTestRun` branching.
|
|
84
226
|
|
|
227
|
+
## Binary slots across SubWorkflow boundaries
|
|
228
|
+
|
|
229
|
+
`item.binary` (the map of named `BinaryAttachment` records) is carried transparently through SubWorkflow boundaries in both directions:
|
|
230
|
+
|
|
231
|
+
- **Parent → child**: binary slots attached before the SubWorkflow node are visible inside the child run. `ctx.binary.openReadStream(attachment)` works in the child because both runs share the same `BinaryStorage`.
|
|
232
|
+
- **Child → parent**: slots attached inside the child are returned with the item and visible in the parent's continuation nodes.
|
|
233
|
+
|
|
234
|
+
This requires no special configuration in production — the shared `BinaryStorage` DI singleton is what makes cross-run byte reads possible.
|
|
235
|
+
|
|
236
|
+
### SubWorkflow + binary example (manual trigger)
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { workflow } from "@codemation/host";
|
|
240
|
+
import { Callback, SubWorkflow } from "@codemation/core-nodes";
|
|
241
|
+
|
|
242
|
+
// Manual-trigger flow — uses the fluent `.map`/`.then` sugar.
|
|
243
|
+
export default workflow("wf.parent")
|
|
244
|
+
.manualTrigger<{ url: string }>("Start", { url: "" })
|
|
245
|
+
// Attach a binary slot before the sub-workflow:
|
|
246
|
+
.map(async (item, ctx) => {
|
|
247
|
+
const att = await ctx.binary.attach({
|
|
248
|
+
name: "doc",
|
|
249
|
+
body: Buffer.from("..."),
|
|
250
|
+
mimeType: "application/pdf",
|
|
251
|
+
filename: "doc.pdf",
|
|
252
|
+
});
|
|
253
|
+
return ctx.binary.withAttachment(item, "doc", att);
|
|
254
|
+
})
|
|
255
|
+
// Sub-workflow receives item with binary["doc"] populated:
|
|
256
|
+
.then(new SubWorkflow("ParseDoc", { workflowId: "wf.child" }))
|
|
257
|
+
// Continuation: both parent "doc" slot and any child-added slots are visible here.
|
|
258
|
+
.map((item) => item)
|
|
259
|
+
.build();
|
|
260
|
+
```
|
|
261
|
+
|
|
85
262
|
## Read next when needed
|
|
86
263
|
|
|
87
264
|
- Read `references/builder-patterns.md` for item-flow rules and fluent authoring patterns.
|
|
88
265
|
- Read `references/workflow-testing.md` for TestTrigger / IsTestRun / Assertion authoring with full examples.
|
|
266
|
+
- Read `references/complete-example.md` for a single dense end-to-end workflow example that exercises most authoring features (CronTrigger, map, if, agent, callableTool, itemExpr, ctx.data, ctx.binary, node with explicit id, build).
|
|
267
|
+
|
|
268
|
+
## Verifying your workflow
|
|
269
|
+
|
|
270
|
+
After writing or modifying a workflow file, call `verify_workflow({ path })` instead of running `pnpm typecheck` yourself. The tool runs typecheck + lint + DSL build + structure dump in one round-trip and returns a structured envelope:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
verify_workflow({ path: "src/workflows/my-workflow.ts" });
|
|
274
|
+
// → { ok: true, data: { typecheck: "ok", lint: "ok", build: "ok", structure: { id, name, trigger, nodes, edges, activation } } }
|
|
275
|
+
// → { ok: false, error: "...", data: { typecheck: {...}, lint: {...}, build: {...}, structure: null }, hint: "..." }
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
A failed `ok: false` result includes a `hint` field that points at the specific fix needed. Fix the reported errors and call `verify_workflow` again — do not report done until `ok: true`.
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
Load this when you need item-flow rules, the two-API decision, and fluent authoring patterns.
|
|
2
|
+
|
|
1
3
|
# Builder Patterns
|
|
2
4
|
|
|
3
|
-
##
|
|
5
|
+
## Manual-trigger workflow (fluent — full sugar available)
|
|
4
6
|
|
|
5
7
|
```ts
|
|
8
|
+
import { workflow } from "@codemation/host";
|
|
9
|
+
|
|
6
10
|
export default workflow("wf.example.id")
|
|
7
11
|
.name("Example")
|
|
8
|
-
.manualTrigger("Start", {
|
|
9
|
-
step: "start",
|
|
10
|
-
})
|
|
12
|
+
.manualTrigger("Start", { step: "start" })
|
|
11
13
|
.map("Transform", (item, _ctx) => ({
|
|
12
14
|
...item.json,
|
|
13
15
|
transformed: true,
|
|
@@ -15,27 +17,57 @@ export default workflow("wf.example.id")
|
|
|
15
17
|
.build();
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
The `.map`, `.if`, `.switch`, `.split`, `.agent`, `.node`, `.then` helpers are available because `manualTrigger(...)` returns a `WorkflowChain`.
|
|
21
|
+
|
|
22
|
+
## Cron-triggered workflow (low-level — `.then(new NodeConfig(...))` only)
|
|
19
23
|
|
|
20
24
|
```ts
|
|
21
|
-
import { CronTrigger } from "@codemation/core-nodes";
|
|
25
|
+
import { Callback, CronTrigger, createWorkflowBuilder } from "@codemation/core-nodes";
|
|
22
26
|
|
|
23
|
-
export default
|
|
24
|
-
.
|
|
27
|
+
export default createWorkflowBuilder({
|
|
28
|
+
id: "wf.nightly.id",
|
|
29
|
+
name: "Nightly job",
|
|
30
|
+
})
|
|
25
31
|
.trigger(new CronTrigger("Nightly", { schedule: "0 3 * * *", timezone: "Europe/Amsterdam" }))
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
.then(
|
|
33
|
+
new Callback("Process tick", (items, _ctx) => {
|
|
34
|
+
// Callback receives the whole batch (Items), not a single item.
|
|
35
|
+
// For a cron trigger the batch is always one item: { firedAt, scheduledFor }.
|
|
36
|
+
return items.map((item) => ({ firedAt: (item.json as { firedAt: string }).firedAt }));
|
|
37
|
+
}),
|
|
38
|
+
)
|
|
29
39
|
.build();
|
|
30
40
|
```
|
|
31
41
|
|
|
32
42
|
The cron expression is validated at workflow build time. Each tick emits one item with `{ firedAt, scheduledFor }` ISO-8601 strings. Always supply `timezone` for DST-sensitive schedules — defaults to UTC.
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
**Note:** non-manual triggers do NOT give you `.map(...)` / `.if(...)` / `.agent(...)` sugar. Compose with `.then(new Callback(...))`, `.then(new If(...))`, `.then(new AIAgent({...}))`, etc.
|
|
45
|
+
|
|
46
|
+
## Webhook-triggered workflow
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { WebhookTrigger, createWorkflowBuilder, Callback } from "@codemation/core-nodes";
|
|
50
|
+
|
|
51
|
+
export default createWorkflowBuilder({
|
|
52
|
+
id: "wf.webhook.example",
|
|
53
|
+
name: "Webhook example",
|
|
54
|
+
})
|
|
55
|
+
.trigger(new WebhookTrigger("Incoming", { endpointKey: "inbound", methods: ["POST"] }))
|
|
56
|
+
.then(new Callback("Handle payload", (items) => items.map((it) => ({ received: it.json }))))
|
|
57
|
+
.build();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Decision rule
|
|
61
|
+
|
|
62
|
+
- **Manual one-shot trigger?** Use `workflow("id").manualTrigger(...)` — short, fluent, full sugar.
|
|
63
|
+
- **Anything else?** Use `createWorkflowBuilder({ id, name }).trigger(new Trigger(...))` — verbose, node-config style.
|
|
64
|
+
|
|
65
|
+
## Imports cheat sheet
|
|
35
66
|
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
67
|
+
- `workflow` → `@codemation/host` (re-exports from `@codemation/core-nodes`)
|
|
68
|
+
- `createWorkflowBuilder`, `CronTrigger`, `WebhookTrigger`, `Callback`, `HttpRequest`, `AIAgent`, `If`, `Split`, `Merge`, `SubWorkflow` → `@codemation/core-nodes`
|
|
69
|
+
- `callableTool`, `itemExpr` → `@codemation/core`
|
|
70
|
+
- Workflow file location: `src/workflows/`. Export the built definition as the default export.
|
|
39
71
|
|
|
40
72
|
## Item rules
|
|
41
73
|
|