@jiggai/recipes 0.4.34 → 0.4.36
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/docs/ARCHITECTURE.md +66 -1
- package/docs/COMMANDS.md +12 -0
- package/docs/MEDIA_DRIVERS.md +175 -0
- package/docs/MEDIA_GENERATION.md +553 -0
- package/docs/TEMPLATE_VARIABLES.md +196 -0
- package/docs/WORKFLOW_APPROVALS.md +334 -0
- package/docs/WORKFLOW_NODES.md +147 -0
- package/docs/WORKFLOW_RUNS_FILE_FIRST.md +2 -0
- package/index.ts +9 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/handlers/media-drivers.ts +49 -0
- package/src/lib/workflows/media-drivers/generic.driver.ts +128 -0
- package/src/lib/workflows/media-drivers/index.ts +22 -0
- package/src/lib/workflows/media-drivers/kling-video.driver.ts +110 -0
- package/src/lib/workflows/media-drivers/luma-video.driver.ts +59 -0
- package/src/lib/workflows/media-drivers/nano-banana-pro.driver.ts +70 -0
- package/src/lib/workflows/media-drivers/openai-image-gen.driver.ts +60 -0
- package/src/lib/workflows/media-drivers/registry.ts +96 -0
- package/src/lib/workflows/media-drivers/runway-video.driver.ts +59 -0
- package/src/lib/workflows/media-drivers/types.ts +50 -0
- package/src/lib/workflows/media-drivers/utils.ts +149 -0
- package/src/lib/workflows/workflow-worker.ts +119 -170
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Template Variables
|
|
2
|
+
|
|
3
|
+
ClawRecipes supports powerful template variable substitution for workflow prompts, file paths, and content. Variables use double-brace syntax: `{{variable.name}}`.
|
|
4
|
+
|
|
5
|
+
## Global Variables
|
|
6
|
+
|
|
7
|
+
These variables are available in all nodes:
|
|
8
|
+
|
|
9
|
+
- `{{date}}` — Current ISO timestamp (e.g., `2025-04-04T03:53:00.000Z`)
|
|
10
|
+
- `{{run.id}}` — Unique run ID (timestamp-based, e.g., `2025-04-04T03-53-00-123Z`)
|
|
11
|
+
- `{{run.timestamp}}` — Alias for `{{run.id}}`
|
|
12
|
+
- `{{workflow.id}}` — Workflow ID from the JSON file
|
|
13
|
+
- `{{workflow.name}}` — Workflow display name (fallback to ID or filename)
|
|
14
|
+
- `{{node.id}}` — Current node ID (available in media nodes)
|
|
15
|
+
|
|
16
|
+
## Upstream Node Variables
|
|
17
|
+
|
|
18
|
+
Access outputs from previous workflow nodes using the pattern `{{nodeId.fieldName}}`:
|
|
19
|
+
|
|
20
|
+
### Basic Access Patterns
|
|
21
|
+
|
|
22
|
+
- `{{nodeId.output}}` — Full JSON output envelope from the node
|
|
23
|
+
- `{{nodeId.text}}` — The `text` field from the node output (most common)
|
|
24
|
+
- `{{nodeId.fieldName}}` — Any specific field from the node output JSON
|
|
25
|
+
|
|
26
|
+
### Common Pitfall: output vs text
|
|
27
|
+
|
|
28
|
+
**Wrong:** `{{brand_review.output}}` returns the full envelope:
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"runId": "2025-04-04T03-53-00-123Z",
|
|
32
|
+
"teamId": "marketing-team",
|
|
33
|
+
"nodeId": "brand_review",
|
|
34
|
+
"kind": "llm",
|
|
35
|
+
"text": "Here is my review...",
|
|
36
|
+
"completedAt": "2025-04-04T03:53:15.000Z"
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Right:** `{{brand_review.text}}` returns just the payload:
|
|
41
|
+
```
|
|
42
|
+
Here is my review...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Nested JSON Extraction
|
|
46
|
+
|
|
47
|
+
When a node's `text` field contains JSON, ClawRecipes automatically extracts nested fields:
|
|
48
|
+
|
|
49
|
+
### Example Node Output
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"text": "{\"title\": \"Product Launch\", \"tags\": [\"marketing\"], \"approved\": true}"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Available Template Variables
|
|
57
|
+
- `{{nodeId.text}}` — Raw JSON string
|
|
58
|
+
- `{{nodeId.title}}` — Extracted: "Product Launch"
|
|
59
|
+
- `{{nodeId.approved_json}}` — For non-string values: "true"
|
|
60
|
+
|
|
61
|
+
### Deeply Nested Extraction
|
|
62
|
+
If the JSON contains nested objects, fields are flattened:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"text": "{\"meta\": {\"author\": \"John\", \"priority\": 1}}"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Available as:
|
|
71
|
+
- `{{nodeId.meta_json}}` — Full meta object as JSON string
|
|
72
|
+
- `{{nodeId.author}}` — "John" (if meta.author is a string)
|
|
73
|
+
|
|
74
|
+
## LLM Node Structured Output
|
|
75
|
+
|
|
76
|
+
LLM nodes with `outputFields` configuration produce predictable JSON structures:
|
|
77
|
+
|
|
78
|
+
### Configuration
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"config": {
|
|
82
|
+
"outputFields": [
|
|
83
|
+
{"name": "title", "type": "text"},
|
|
84
|
+
{"name": "tags", "type": "list"},
|
|
85
|
+
{"name": "metadata", "type": "json"}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Template Access
|
|
92
|
+
```
|
|
93
|
+
Title: {{nodeId.title}}
|
|
94
|
+
Tags: {{nodeId.tags}}
|
|
95
|
+
Metadata: {{nodeId.metadata_json}}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Usage Examples
|
|
99
|
+
|
|
100
|
+
### LLM Prompt Template
|
|
101
|
+
```
|
|
102
|
+
Review the content from the previous step:
|
|
103
|
+
|
|
104
|
+
{{brand_review.text}}
|
|
105
|
+
|
|
106
|
+
Based on this review, create a social media post with:
|
|
107
|
+
- Title: engaging and on-brand
|
|
108
|
+
- Content: max 280 characters
|
|
109
|
+
- Hashtags: 3-5 relevant tags
|
|
110
|
+
|
|
111
|
+
Current date: {{date}}
|
|
112
|
+
Workflow: {{workflow.name}}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### File Path Templates
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"tool": "fs.write",
|
|
119
|
+
"args": {
|
|
120
|
+
"path": "content/{{run.id}}/{{brand_review.title}}.md",
|
|
121
|
+
"content": "# {{brand_review.title}}\n\n{{brand_review.text}}"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Media Node Prompts
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"config": {
|
|
130
|
+
"promptTemplate": "Create an image for: {{content_draft.title}}\n\nStyle: {{brand_review.style}}\nMood: professional and engaging"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Implementation Details
|
|
136
|
+
|
|
137
|
+
Template substitution happens in the workflow worker during node execution. The code path is:
|
|
138
|
+
|
|
139
|
+
- **Primary**: `src/lib/workflows/workflow-worker.ts` — `buildTemplateVars()` function
|
|
140
|
+
- **Utility**: `src/lib/workflows/workflow-utils.ts` — `templateReplace()` function
|
|
141
|
+
|
|
142
|
+
### Variable Resolution Process
|
|
143
|
+
|
|
144
|
+
1. **Global vars** are built from run metadata and timestamps
|
|
145
|
+
2. **Node outputs** are loaded from each completed node's output file
|
|
146
|
+
3. **JSON parsing** attempts to extract fields from the `text` field
|
|
147
|
+
4. **Nested extraction** flattens nested objects with `_json` suffixes for non-strings
|
|
148
|
+
5. **Template replacement** applies all variables using simple string substitution
|
|
149
|
+
|
|
150
|
+
### Performance Notes
|
|
151
|
+
|
|
152
|
+
- Variables are rebuilt for each node execution (fresh state)
|
|
153
|
+
- Large node outputs are fully loaded into memory during template resolution
|
|
154
|
+
- JSON parsing failures are silently ignored (graceful degradation)
|
|
155
|
+
|
|
156
|
+
## Best Practices
|
|
157
|
+
|
|
158
|
+
### Variable Naming
|
|
159
|
+
- Use descriptive node IDs: `brand_review` not `step1`
|
|
160
|
+
- Design consistent output field names across nodes
|
|
161
|
+
- Use `outputFields` for structured LLM outputs
|
|
162
|
+
|
|
163
|
+
### Error Handling
|
|
164
|
+
- Always provide fallbacks: `{{nodeId.title || "Untitled"}}`
|
|
165
|
+
- Test templates with missing/malformed node outputs
|
|
166
|
+
- Use `{{nodeId.text}}` when unsure about field structure
|
|
167
|
+
|
|
168
|
+
### Memory Management
|
|
169
|
+
- Avoid extremely large text outputs in frequently-referenced nodes
|
|
170
|
+
- Consider splitting large data into separate file outputs
|
|
171
|
+
|
|
172
|
+
## Common Patterns
|
|
173
|
+
|
|
174
|
+
### Content Pipeline
|
|
175
|
+
```
|
|
176
|
+
1. research_node.text → "Market analysis shows..."
|
|
177
|
+
2. draft_node template: "Based on {{research_node.text}}, write..."
|
|
178
|
+
3. review_node template: "Review this draft: {{draft_node.text}}"
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Conditional Content
|
|
182
|
+
```
|
|
183
|
+
{% if brand_review.approved %}
|
|
184
|
+
Approved content: {{brand_review.text}}
|
|
185
|
+
{% else %}
|
|
186
|
+
Needs revision: {{brand_review.feedback}}
|
|
187
|
+
{% endif %}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Note: ClawRecipes uses simple string substitution, not template engines like Jinja2. Complex conditionals should be handled in LLM prompts.
|
|
191
|
+
|
|
192
|
+
## Related Documentation
|
|
193
|
+
|
|
194
|
+
- [WORKFLOW_NODES.md](WORKFLOW_NODES.md) — Node types and configuration
|
|
195
|
+
- [WORKFLOW_EXAMPLES.md](WORKFLOW_EXAMPLES.md) — Template usage examples
|
|
196
|
+
- [MEDIA_GENERATION.md](MEDIA_GENERATION.md) — Media node templates and variables
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# Workflow Approvals
|
|
2
|
+
|
|
3
|
+
Human approval nodes (`human_approval`) pause workflow execution for manual review. This document covers the runtime behavior, state management, and approval flow.
|
|
4
|
+
|
|
5
|
+
## Approval Flow Overview
|
|
6
|
+
|
|
7
|
+
1. **Node Execution** — Worker reaches approval node
|
|
8
|
+
2. **Pause State** — Run status changes to `awaiting_approval`
|
|
9
|
+
3. **Message Sent** — Approval request sent to configured channel
|
|
10
|
+
4. **Human Decision** — User replies with approve/decline command
|
|
11
|
+
5. **State Update** — Approval recorded to disk
|
|
12
|
+
6. **Resume** — Workflow continues or loops back for revision
|
|
13
|
+
|
|
14
|
+
## Runtime Behavior
|
|
15
|
+
|
|
16
|
+
### Node Status Transition
|
|
17
|
+
|
|
18
|
+
When a workflow worker processes a `human_approval` node:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
node.status: undefined → waiting → waiting_approval
|
|
22
|
+
run.status: running → awaiting_approval
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The worker:
|
|
26
|
+
1. Creates approval directory: `workflow-runs/{runId}/approvals/`
|
|
27
|
+
2. Generates random approval code (6-char uppercase)
|
|
28
|
+
3. Writes `approval.json` with pending status
|
|
29
|
+
4. Sends message to configured channel with approval code
|
|
30
|
+
5. Marks node as waiting in run state
|
|
31
|
+
6. Pauses execution (no further nodes enqueued)
|
|
32
|
+
|
|
33
|
+
### State Files Written
|
|
34
|
+
|
|
35
|
+
**Run Log Update** (`workflow-runs/{runId}/run.json`):
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"status": "awaiting_approval",
|
|
39
|
+
"nodeStates": {
|
|
40
|
+
"approval_node": {"status": "waiting", "ts": "2025-04-04T03:53:15.000Z"}
|
|
41
|
+
},
|
|
42
|
+
"events": [
|
|
43
|
+
{
|
|
44
|
+
"ts": "2025-04-04T03:53:15.000Z",
|
|
45
|
+
"type": "node.awaiting_approval",
|
|
46
|
+
"nodeId": "approval_node",
|
|
47
|
+
"bindingId": "telegram:account:mybot",
|
|
48
|
+
"approvalFile": "workflow-runs/2025-04-04T03-53-00-123Z/approvals/approval.json"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"nodeResults": [
|
|
52
|
+
{
|
|
53
|
+
"nodeId": "approval_node",
|
|
54
|
+
"kind": "human_approval",
|
|
55
|
+
"approvalBindingId": "telegram:account:mybot",
|
|
56
|
+
"approvalFile": "workflow-runs/2025-04-04T03-53-00-123Z/approvals/approval.json"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Approval Record** (`workflow-runs/{runId}/approvals/approval.json`):
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"runId": "2025-04-04T03-53-00-123Z",
|
|
66
|
+
"teamId": "marketing-team",
|
|
67
|
+
"workflowFile": "content-pipeline.workflow.json",
|
|
68
|
+
"nodeId": "approval_node",
|
|
69
|
+
"bindingId": "telegram:account:mybot",
|
|
70
|
+
"requestedAt": "2025-04-04T03:53:15.000Z",
|
|
71
|
+
"status": "pending",
|
|
72
|
+
"code": "XY4K91",
|
|
73
|
+
"ticket": "work/in-progress/0042-social-media-campaign.md",
|
|
74
|
+
"runLog": "workflow-runs/2025-04-04T03-53-00-123Z/run.json"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Message Template Rendering
|
|
79
|
+
|
|
80
|
+
The approval message includes template variable substitution:
|
|
81
|
+
|
|
82
|
+
**Template Variables Available:**
|
|
83
|
+
- `{{workflow.name}}` — Workflow display name
|
|
84
|
+
- `{{code}}` — Generated approval code
|
|
85
|
+
- `{{ticket}}` — Relative path to ticket file
|
|
86
|
+
- Proposed content preview (from prior node outputs)
|
|
87
|
+
|
|
88
|
+
**Default Message Template:**
|
|
89
|
+
```
|
|
90
|
+
Approval requested: {{workflow.name}}
|
|
91
|
+
Ticket: {{ticket}}
|
|
92
|
+
Code: {{code}}
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
PROPOSED POST (X)
|
|
96
|
+
---
|
|
97
|
+
{{proposed_content}}
|
|
98
|
+
|
|
99
|
+
Reply with:
|
|
100
|
+
- approve {{code}}
|
|
101
|
+
- decline {{code}} <what to change>
|
|
102
|
+
|
|
103
|
+
(You can also review in Kitchen: http://localhost:7777/teams/{{teamId}}/workflows/{{workflow.id}})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Proposed Content Logic:**
|
|
107
|
+
1. Look for `qc_brand` node output first
|
|
108
|
+
2. Fall back to most recent prior LLM node
|
|
109
|
+
3. Extract `text` field and sanitize for preview
|
|
110
|
+
4. Show "(Warning: no proposed text found)" if unavailable
|
|
111
|
+
|
|
112
|
+
## Human Response Handling
|
|
113
|
+
|
|
114
|
+
### Command Format
|
|
115
|
+
|
|
116
|
+
Users reply in the configured channel:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
approve XY4K91
|
|
120
|
+
decline XY4K91 needs better headlines
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Parsing Rules:**
|
|
124
|
+
- Case-insensitive verb matching: `approve|decline`
|
|
125
|
+
- Code must be exact (uppercase)
|
|
126
|
+
- Optional note after code for decline responses
|
|
127
|
+
- Parser checks multiple lines, prefers last match
|
|
128
|
+
|
|
129
|
+
### Auto-Processing
|
|
130
|
+
|
|
131
|
+
When a user replies with a valid approval command:
|
|
132
|
+
|
|
133
|
+
1. **Event Detection** — Plugin listener matches approval format
|
|
134
|
+
2. **Code Lookup** — Searches all team runs for matching pending approval
|
|
135
|
+
3. **Decision Recording** — Updates approval.json status and note
|
|
136
|
+
4. **Auto-Resume** — Calls resume workflow to continue execution
|
|
137
|
+
|
|
138
|
+
**Supported Channels:**
|
|
139
|
+
- Telegram (primary)
|
|
140
|
+
- Other channels with conditional processing based on metadata
|
|
141
|
+
|
|
142
|
+
## Resume and Continuation
|
|
143
|
+
|
|
144
|
+
### Approval Resume
|
|
145
|
+
|
|
146
|
+
**Approved Flows:**
|
|
147
|
+
```
|
|
148
|
+
approval.status: pending → approved
|
|
149
|
+
node.status: waiting → success
|
|
150
|
+
run.status: awaiting_approval → running
|
|
151
|
+
→ Next node enqueued
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Rejection Flows:**
|
|
155
|
+
```
|
|
156
|
+
approval.status: pending → rejected
|
|
157
|
+
node.status: waiting → error
|
|
158
|
+
run.status: awaiting_approval → needs_revision
|
|
159
|
+
→ Loop back to revision node
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Revision Logic
|
|
163
|
+
|
|
164
|
+
When an approval is declined:
|
|
165
|
+
|
|
166
|
+
1. **Find revision target** — Prefers `draft_assets` node, else closest prior LLM node
|
|
167
|
+
2. **Clear node states** — Remove status for revision node onward
|
|
168
|
+
3. **Clear locks** — Remove any stale node lock files
|
|
169
|
+
4. **Enqueue revision** — Send revision task to appropriate agent
|
|
170
|
+
5. **Include feedback** — Pass human note in task packet
|
|
171
|
+
|
|
172
|
+
**Revision Node Selection:**
|
|
173
|
+
```typescript
|
|
174
|
+
// Preferred: explicit draft_assets node
|
|
175
|
+
let reviseIdx = workflow.nodes.findIndex(n => n.id === 'draft_assets');
|
|
176
|
+
|
|
177
|
+
// Fallback: most recent LLM node before approval
|
|
178
|
+
if (reviseIdx < 0) {
|
|
179
|
+
for (let i = approvalIdx - 1; i >= 0; i--) {
|
|
180
|
+
if (workflow.nodes[i]?.kind === 'llm') {
|
|
181
|
+
reviseIdx = i;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Configuration
|
|
189
|
+
|
|
190
|
+
### Node Configuration
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"id": "final_approval",
|
|
195
|
+
"kind": "human_approval",
|
|
196
|
+
"action": {
|
|
197
|
+
"approvalBindingId": "telegram:account:mybot"
|
|
198
|
+
},
|
|
199
|
+
"config": {
|
|
200
|
+
"provider": "telegram",
|
|
201
|
+
"target": "6477250615",
|
|
202
|
+
"accountId": "mybot",
|
|
203
|
+
"agentId": "marketing-team-lead"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Configuration Hierarchy:**
|
|
209
|
+
1. `approvalBindingId` → Look up in OpenClaw config bindings
|
|
210
|
+
2. `config.target` → Direct channel target
|
|
211
|
+
3. Back-compat fallbacks for known account IDs
|
|
212
|
+
|
|
213
|
+
### Agent Assignment
|
|
214
|
+
|
|
215
|
+
Approval nodes are executed by:
|
|
216
|
+
1. **Explicit `config.agentId`** — Workflow author override
|
|
217
|
+
2. **Team lead** — `${teamId}-lead` (default orchestrator)
|
|
218
|
+
3. **Current agent** — Fallback for backwards compatibility
|
|
219
|
+
|
|
220
|
+
## Command Line Interface
|
|
221
|
+
|
|
222
|
+
### Manual Approval
|
|
223
|
+
```bash
|
|
224
|
+
openclaw recipes workflows approve \
|
|
225
|
+
--team-id marketing-team \
|
|
226
|
+
--run-id 2025-04-04T03-53-00-123Z \
|
|
227
|
+
--approved true
|
|
228
|
+
|
|
229
|
+
openclaw recipes workflows approve \
|
|
230
|
+
--team-id marketing-team \
|
|
231
|
+
--run-id 2025-04-04T03-53-00-123Z \
|
|
232
|
+
--approved false \
|
|
233
|
+
--note "Headlines need work"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Resume Workflow
|
|
237
|
+
```bash
|
|
238
|
+
openclaw recipes workflows resume \
|
|
239
|
+
--team-id marketing-team \
|
|
240
|
+
--run-id 2025-04-04T03-53-00-123Z
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Poll Approvals
|
|
244
|
+
```bash
|
|
245
|
+
# Auto-resume any decided approvals
|
|
246
|
+
openclaw recipes workflows poll-approvals \
|
|
247
|
+
--team-id marketing-team \
|
|
248
|
+
--limit 20
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Implementation Details
|
|
252
|
+
|
|
253
|
+
### Code Locations
|
|
254
|
+
|
|
255
|
+
**Primary Approval Logic:**
|
|
256
|
+
- `src/lib/workflows/workflow-approvals.ts`
|
|
257
|
+
- `approveWorkflowRun()` — Record approval decision
|
|
258
|
+
- `resumeWorkflowRun()` — Continue/revise after decision
|
|
259
|
+
- `pollWorkflowApprovals()` — Auto-resume decided approvals
|
|
260
|
+
|
|
261
|
+
**Worker Execution:**
|
|
262
|
+
- `src/lib/workflows/workflow-worker.ts`
|
|
263
|
+
- Approval node execution in `runWorkflowWorkerTick()`
|
|
264
|
+
- Message sending and state management
|
|
265
|
+
|
|
266
|
+
**Event Handling:**
|
|
267
|
+
- `index.ts` — Auto-approval reply handler in plugin registration
|
|
268
|
+
|
|
269
|
+
### File Structure
|
|
270
|
+
```
|
|
271
|
+
workspace-{teamId}/
|
|
272
|
+
└── shared-context/
|
|
273
|
+
└── workflow-runs/
|
|
274
|
+
└── {runId}/
|
|
275
|
+
├── run.json # Main run state
|
|
276
|
+
├── approvals/
|
|
277
|
+
│ └── approval.json # Approval record
|
|
278
|
+
├── locks/ # Node execution locks
|
|
279
|
+
└── node-outputs/ # Node result files
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Error Handling
|
|
283
|
+
|
|
284
|
+
### Missing Targets
|
|
285
|
+
```
|
|
286
|
+
Node final_approval missing approval target (provide config.target or binding mapping)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Solution:** Configure `config.target` or add binding in OpenClaw config.
|
|
290
|
+
|
|
291
|
+
### Code Not Found
|
|
292
|
+
```
|
|
293
|
+
[recipes] approval reply not matched: code=XY4K91
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Causes:**
|
|
297
|
+
- Code already processed/expired
|
|
298
|
+
- Wrong team workspace searched
|
|
299
|
+
- Approval file corrupted
|
|
300
|
+
|
|
301
|
+
### Resume Failures
|
|
302
|
+
```
|
|
303
|
+
Approval still pending. Update approval.json first.
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Solution:** Record decision with `workflows approve` before resuming.
|
|
307
|
+
|
|
308
|
+
## Best Practices
|
|
309
|
+
|
|
310
|
+
### Node Placement
|
|
311
|
+
- Place approval nodes after content generation but before publishing
|
|
312
|
+
- Use descriptive node IDs: `final_approval`, `brand_review`, `legal_check`
|
|
313
|
+
|
|
314
|
+
### Message Configuration
|
|
315
|
+
- Always configure approval binding in OpenClaw config for production
|
|
316
|
+
- Test approval flow with known test accounts/channels
|
|
317
|
+
- Include meaningful context in approval messages
|
|
318
|
+
|
|
319
|
+
### Error Recovery
|
|
320
|
+
- Monitor approval files for stuck `pending` status
|
|
321
|
+
- Use `poll-approvals` for automated processing
|
|
322
|
+
- Set up alerting for approval timeouts
|
|
323
|
+
|
|
324
|
+
### Workflow Design
|
|
325
|
+
- Design clear revision loops with identifiable `draft_assets` nodes
|
|
326
|
+
- Keep approval criteria specific and actionable
|
|
327
|
+
- Document approval responsibilities in team workflows
|
|
328
|
+
|
|
329
|
+
## Related Documentation
|
|
330
|
+
|
|
331
|
+
- [WORKFLOW_NODES.md](WORKFLOW_NODES.md) — All node types and configuration
|
|
332
|
+
- [TEMPLATE_VARIABLES.md](TEMPLATE_VARIABLES.md) — Message template variables
|
|
333
|
+
- [OUTBOUND_POSTING.md](OUTBOUND_POSTING.md) — Publishing after approval
|
|
334
|
+
- [COMMANDS.md](COMMANDS.md) — CLI workflow commands
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Workflow Node Reference (ClawRecipes)
|
|
2
|
+
|
|
3
|
+
This document is the **runtime** reference for workflow node configuration in ClawRecipes.
|
|
4
|
+
|
|
5
|
+
ClawKitchen (the UI) edits workflow JSON files, but the actual execution semantics live here.
|
|
6
|
+
|
|
7
|
+
If you’re new to the file format, start with:
|
|
8
|
+
- [WORKFLOW_RUNS_FILE_FIRST.md](WORKFLOW_RUNS_FILE_FIRST.md)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Mental model
|
|
13
|
+
|
|
14
|
+
A workflow run is a directory on disk containing a `run.json` (and logs/deliverables). The worker executes nodes in order, persists each node’s output, and downstream nodes reference upstream outputs via `{{ }}` template variables.
|
|
15
|
+
|
|
16
|
+
### Template variables
|
|
17
|
+
|
|
18
|
+
At runtime, ClawRecipes replaces `{{vars}}` in strings inside node configs.
|
|
19
|
+
|
|
20
|
+
There are two broad categories:
|
|
21
|
+
|
|
22
|
+
**Global vars** (always available):
|
|
23
|
+
- `{{date}}`
|
|
24
|
+
- `{{run.id}}`
|
|
25
|
+
- `{{workflow.id}}`
|
|
26
|
+
- `{{workflow.name}}`
|
|
27
|
+
- `{{node.id}}`
|
|
28
|
+
|
|
29
|
+
**Upstream node vars** (depend on prior nodes):
|
|
30
|
+
- `{{someNode.output}}` — the full stored output envelope
|
|
31
|
+
- `{{someNode.text}}` — the most common “payload string” field
|
|
32
|
+
- `{{someNode.someField}}` — a field extracted from JSON inside the node’s `text` (when applicable)
|
|
33
|
+
|
|
34
|
+
Notes:
|
|
35
|
+
- If an upstream node stores JSON inside its `text` field, the template system can extract nested fields (e.g. `{{draft_assets.video_brief}}`).
|
|
36
|
+
- If you see “garbled JSON” showing up in a media prompt, you’re usually templating the *envelope* (e.g. `{{draft_assets.output}}`) instead of the intended field (e.g. `{{draft_assets.text}}` or `{{draft_assets.video_brief}}`).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Node types
|
|
41
|
+
|
|
42
|
+
This section describes the important node types and the config fields the worker actually reads.
|
|
43
|
+
|
|
44
|
+
### LLM nodes
|
|
45
|
+
|
|
46
|
+
LLM nodes execute via the `llm-task` tool.
|
|
47
|
+
|
|
48
|
+
Common config fields (stored under `node.config`):
|
|
49
|
+
- `promptTemplate` (string): the prompt content, after template-var substitution
|
|
50
|
+
- `timeoutMs` (number, optional)
|
|
51
|
+
- `model` (string, optional): provider/model selection (node → workflow → global precedence)
|
|
52
|
+
|
|
53
|
+
#### outputFields (structured output)
|
|
54
|
+
|
|
55
|
+
If `node.config.outputFields` is present, ClawRecipes will:
|
|
56
|
+
1) Append an explicit OUTPUT FORMAT section to the prompt
|
|
57
|
+
2) Derive a JSON Schema and pass it to `llm-task`
|
|
58
|
+
|
|
59
|
+
That means the LLM output is **validated**.
|
|
60
|
+
|
|
61
|
+
Shape:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"outputFields": [
|
|
66
|
+
{"name": "summary", "type": "text"},
|
|
67
|
+
{"name": "angles", "type": "list"},
|
|
68
|
+
{"name": "metadata", "type": "json"}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Types:
|
|
74
|
+
- `text` → JSON string
|
|
75
|
+
- `list` → array of strings
|
|
76
|
+
- `json` → JSON object
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### Media nodes (image/video/audio)
|
|
81
|
+
|
|
82
|
+
Media nodes ultimately produce a file deliverable. ClawRecipes invokes a **MediaDriver** selected by `provider`.
|
|
83
|
+
|
|
84
|
+
Provider selection:
|
|
85
|
+
- In workflow JSON, providers are referenced as `skill-<slug>`
|
|
86
|
+
- At runtime the worker strips `skill-` and looks up a driver by slug
|
|
87
|
+
|
|
88
|
+
#### Common media config fields
|
|
89
|
+
|
|
90
|
+
These are passed through to drivers as `opts.config`:
|
|
91
|
+
|
|
92
|
+
- `size` (string):
|
|
93
|
+
- For images, typically pixel size like `1024x1024`, `1792x1024`, `1024x1792`
|
|
94
|
+
- Some providers interpret this differently (drivers may map pixel sizes to tiers)
|
|
95
|
+
|
|
96
|
+
- `duration` (string or number): duration in seconds, e.g. `"5s"`, `"10s"`, or `10`
|
|
97
|
+
|
|
98
|
+
- `aspect_ratio` (string): e.g. `"16:9"`, `"9:16"` (provider-specific)
|
|
99
|
+
|
|
100
|
+
- `quality` / `style`: image-provider-specific fields (ex: OpenAI)
|
|
101
|
+
|
|
102
|
+
- `addRefinement` (boolean): **opt-in** prompt refinement pass for video/audio.
|
|
103
|
+
- Default is OFF. When enabled, the worker runs an extra `llm-task` call to refine the prompt before invoking the driver.
|
|
104
|
+
|
|
105
|
+
#### Driver constraints
|
|
106
|
+
|
|
107
|
+
Drivers can optionally declare duration constraints (`minSeconds/maxSeconds/defaultSeconds`).
|
|
108
|
+
|
|
109
|
+
Kitchen surfaces these constraints as UX hints, but the runtime is the source of truth.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Tool nodes
|
|
114
|
+
|
|
115
|
+
Tool nodes call a tool by name with JSON args. Example:
|
|
116
|
+
- `fs.append`
|
|
117
|
+
- `outbound.post`
|
|
118
|
+
- `message.send`
|
|
119
|
+
|
|
120
|
+
Tool nodes support template vars inside string args.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### Human approval nodes
|
|
125
|
+
|
|
126
|
+
Human approval nodes pause execution until an approval is granted in the UI.
|
|
127
|
+
|
|
128
|
+
Common pattern:
|
|
129
|
+
- Upstream LLM generates a draft
|
|
130
|
+
- Approval node shows a `messageTemplate` (with template vars)
|
|
131
|
+
- Downstream nodes continue only after approval
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Adding new capabilities (where to change code)
|
|
136
|
+
|
|
137
|
+
- LLM output shaping/validation: `src/lib/workflows/workflow-worker.ts`
|
|
138
|
+
- Media providers: `src/lib/workflows/media-drivers/`
|
|
139
|
+
- CLI for Kitchen dropdown: `openclaw recipes workflows media-drivers` (handler: `src/handlers/media-drivers.ts`)
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Troubleshooting quick hits
|
|
144
|
+
|
|
145
|
+
- **Provider missing in dropdown**: run `openclaw recipes workflows media-drivers` and check `available` / `missingEnvVars`.
|
|
146
|
+
- **Media node ignores config**: confirm the driver reads `opts.config` (not all scripts support every knob).
|
|
147
|
+
- **Bad video quality**: ensure your prompt is a single clear scene brief; avoid multi-scene paragraphs when the provider needs a seed frame.
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
This document explains how ClawRecipes workflows work in practice.
|
|
4
4
|
|
|
5
5
|
If you want a copy-paste cookbook after reading this reference, also see:
|
|
6
|
+
|
|
7
|
+
- [WORKFLOW_NODES.md](WORKFLOW_NODES.md) — runtime node config reference (LLM/media/tool/approval)
|
|
6
8
|
- [WORKFLOW_EXAMPLES.md](WORKFLOW_EXAMPLES.md)
|
|
7
9
|
|
|
8
10
|
If you are trying to answer any of these questions, start here:
|
package/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { handleScaffold, scaffoldAgentFromRecipe } from "./src/handlers/scaffold
|
|
|
47
47
|
import { handleAddRoleToTeam } from "./src/handlers/team-add-role";
|
|
48
48
|
import { reconcileRecipeCronJobs } from "./src/handlers/cron";
|
|
49
49
|
import { handleWorkflowsApprove, handleWorkflowsPollApprovals, handleWorkflowsResume, handleWorkflowsRun, handleWorkflowsRunnerOnce, handleWorkflowsRunnerTick, handleWorkflowsWorkerTick } from "./src/handlers/workflows";
|
|
50
|
+
import { handleMediaDriversList } from "./src/handlers/media-drivers";
|
|
50
51
|
import { listRecipeFiles, loadRecipeById, workspacePath } from "./src/lib/recipes";
|
|
51
52
|
import {
|
|
52
53
|
executeWorkspaceCleanup,
|
|
@@ -728,6 +729,14 @@ workflows
|
|
|
728
729
|
console.log(JSON.stringify(res, null, 2));
|
|
729
730
|
});
|
|
730
731
|
|
|
732
|
+
workflows
|
|
733
|
+
.command("media-drivers")
|
|
734
|
+
.description("List available media generation drivers with env-var availability")
|
|
735
|
+
.action(async () => {
|
|
736
|
+
const drivers = await handleMediaDriversList();
|
|
737
|
+
console.log(JSON.stringify(drivers));
|
|
738
|
+
});
|
|
739
|
+
|
|
731
740
|
workflows
|
|
732
741
|
.command("poll-approvals")
|
|
733
742
|
.description("Auto-resume any workflow runs whose approval decision has been recorded (approved/rejected)")
|