openclaw-workflowskill 0.2.1 → 0.3.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/README.md +61 -77
- package/index.ts +79 -12
- package/lib/adapters.ts +6 -103
- package/lib/storage.ts +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/tools/run.ts +2 -6
- package/tools/validate.ts +1 -1
package/README.md
CHANGED
|
@@ -13,38 +13,12 @@ Author, validate, run, and review WorkflowSkill workflows — without leaving th
|
|
|
13
13
|
2. The agent writes, validates, and test-runs the workflow in chat
|
|
14
14
|
3. Schedule it with cron — runs autonomously, no agent session needed
|
|
15
15
|
|
|
16
|
-
## What it looks like
|
|
17
|
-
|
|
18
|
-
> **You:** I want to check Hacker News for AI stories every morning and email me a summary.
|
|
19
|
-
>
|
|
20
|
-
> **Agent:** I'll author a WorkflowSkill for that. *(invokes `/workflowskill-author`, writes a SKILL.md, runs `workflowskill_validate`)*
|
|
21
|
-
>
|
|
22
|
-
> Validated — 3 steps: `fetch`, `filter`, `email`. Running a test now... *(invokes `workflowskill_run`)*
|
|
23
|
-
>
|
|
24
|
-
> Run complete: 4 AI stories found, summary drafted. Ready to schedule — want me to set up a daily cron at 8 AM?
|
|
25
|
-
|
|
26
|
-
## Workflow Lifecycle
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
describe workflow in natural language
|
|
30
|
-
↓
|
|
31
|
-
/workflowskill-author (agent writes YAML)
|
|
32
|
-
↓
|
|
33
|
-
workflowskill_validate (catch errors early)
|
|
34
|
-
↓
|
|
35
|
-
workflowskill_run (test run, review RunLog)
|
|
36
|
-
↓
|
|
37
|
-
workflowskill_runs (diagnose failures, iterate)
|
|
38
|
-
↓
|
|
39
|
-
cron (schedule for automated execution)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
16
|
## Repositories
|
|
43
17
|
|
|
44
|
-
| Repo
|
|
45
|
-
|
|
46
|
-
| [workflowskill](https://github.com/matthew-h-cromer/workflowskill) | Specification and reference runtime
|
|
47
|
-
| **openclaw-workflowskill** (this repo)
|
|
18
|
+
| Repo | Description |
|
|
19
|
+
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
20
|
+
| [workflowskill](https://github.com/matthew-h-cromer/workflowskill) | Specification and reference runtime |
|
|
21
|
+
| **openclaw-workflowskill** (this repo) | OpenClaw plugin — author, validate, run, and review workflows from the agent |
|
|
48
22
|
|
|
49
23
|
## Quick Start
|
|
50
24
|
|
|
@@ -56,17 +30,13 @@ Requires [OpenClaw](https://openclaw.ai).
|
|
|
56
30
|
openclaw plugins install openclaw-workflowskill
|
|
57
31
|
```
|
|
58
32
|
|
|
59
|
-
### 2.
|
|
60
|
-
|
|
61
|
-
The plugin reads your Anthropic API key from OpenClaw's credential store — no `.env` file needed. Make sure an Anthropic auth profile is configured in OpenClaw (`~/.openclaw/agents/main/agent/auth-profiles.json`).
|
|
62
|
-
|
|
63
|
-
### 3. Restart the gateway
|
|
33
|
+
### 2. Restart the gateway
|
|
64
34
|
|
|
65
35
|
```bash
|
|
66
36
|
openclaw gateway restart
|
|
67
37
|
```
|
|
68
38
|
|
|
69
|
-
###
|
|
39
|
+
### 3. Verify
|
|
70
40
|
|
|
71
41
|
```bash
|
|
72
42
|
openclaw plugins list
|
|
@@ -76,16 +46,46 @@ openclaw skills list
|
|
|
76
46
|
# → workflowskill-author (user-invocable)
|
|
77
47
|
```
|
|
78
48
|
|
|
49
|
+
> **Note:** `workflowskill_llm` uses your Anthropic credentials from the main agent — no separate API key configuration needed.
|
|
50
|
+
|
|
51
|
+
### 4. Create a workflow
|
|
52
|
+
|
|
53
|
+
Just tell the agent what you want to automate:
|
|
54
|
+
|
|
55
|
+
> **You:** I want to check Hacker News for AI stories every morning and email me a summary.
|
|
56
|
+
>
|
|
57
|
+
> **Agent:** I'll author a WorkflowSkill for that. _(invokes `/workflowskill-author`, writes a SKILL.md, runs `workflowskill_validate`)_
|
|
58
|
+
>
|
|
59
|
+
> Validated — 3 steps: `fetch`, `filter`, `email`. Running a test now... _(invokes `workflowskill_run`)_
|
|
60
|
+
>
|
|
61
|
+
> Run complete: 4 AI stories found, summary drafted. Ready to schedule — want me to set up a daily cron at 8 AM?
|
|
62
|
+
|
|
63
|
+
### 5. Schedule it
|
|
64
|
+
|
|
65
|
+
Ask the agent to set up a cron job, or add one manually at `~/.openclaw/cron/jobs.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"payload": {
|
|
70
|
+
"kind": "agentTurn",
|
|
71
|
+
"message": "Run the daily-triage workflow using workflowskill_run\n\nSend results to Slack in the #general channel",
|
|
72
|
+
"model": "haiku"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Your agent will use `"model": "haiku"` for cron jobs, ensuring execution is cheap and lightweight.
|
|
78
|
+
|
|
79
79
|
## Tools
|
|
80
80
|
|
|
81
81
|
Registers four tools with the OpenClaw agent:
|
|
82
82
|
|
|
83
|
-
| Tool
|
|
84
|
-
|
|
85
|
-
| `workflowskill_validate` | Parse and validate a SKILL.md or raw YAML workflow
|
|
86
|
-
| `workflowskill_run`
|
|
87
|
-
| `workflowskill_runs`
|
|
88
|
-
| `workflowskill_llm`
|
|
83
|
+
| Tool | Description |
|
|
84
|
+
| ------------------------ | ------------------------------------------------------------- |
|
|
85
|
+
| `workflowskill_validate` | Parse and validate a SKILL.md or raw YAML workflow |
|
|
86
|
+
| `workflowskill_run` | Execute a workflow and return a compact run summary |
|
|
87
|
+
| `workflowskill_runs` | List and inspect past run logs |
|
|
88
|
+
| `workflowskill_llm` | Call Anthropic directly for inline LLM reasoning in workflows |
|
|
89
89
|
|
|
90
90
|
Also ships the `/workflowskill-author` skill — just say "I want to automate X" and the agent handles the rest: researching, writing, validating, and test-running the workflow in chat.
|
|
91
91
|
|
|
@@ -100,22 +100,6 @@ Also ships the `/workflowskill-author` skill — just say "I want to automate X"
|
|
|
100
100
|
daily-triage-2024-01-15T09-00-00.000Z.json
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
## Cron Scheduling
|
|
104
|
-
|
|
105
|
-
Schedule a workflow to run autonomously via OpenClaw's cron, at `~/.openclaw/cron/jobs.json`:
|
|
106
|
-
|
|
107
|
-
```json
|
|
108
|
-
{
|
|
109
|
-
"payload": {
|
|
110
|
-
"kind": "agentTurn",
|
|
111
|
-
"message": "Run the daily-triage workflow using workflowskill_run\n\nSend results to Slack in the #general channel",
|
|
112
|
-
"model": "haiku"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Always set `"model": "haiku"` — cron runs are lightweight orchestration and don't need a powerful model. Put delivery instructions (e.g. Slack channel) in the cron message, not in the workflow, so workflows stay reusable.
|
|
118
|
-
|
|
119
103
|
Review past runs via `workflowskill_runs`.
|
|
120
104
|
|
|
121
105
|
## Architecture
|
|
@@ -134,9 +118,9 @@ The plugin's own four tools (`workflowskill_validate`, `workflowskill_run`, `wor
|
|
|
134
118
|
|
|
135
119
|
Parse and validate a SKILL.md or raw YAML workflow.
|
|
136
120
|
|
|
137
|
-
| Param
|
|
138
|
-
|
|
139
|
-
| `content` | string | yes
|
|
121
|
+
| Param | Type | Required | Description |
|
|
122
|
+
| --------- | ------ | -------- | ---------------------------------- |
|
|
123
|
+
| `content` | string | yes | SKILL.md text or raw workflow YAML |
|
|
140
124
|
|
|
141
125
|
Returns `{ valid, errors[], name, stepCount, stepTypes[] }`.
|
|
142
126
|
|
|
@@ -144,23 +128,23 @@ Returns `{ valid, errors[], name, stepCount, stepTypes[] }`.
|
|
|
144
128
|
|
|
145
129
|
Execute a workflow and return a compact run summary. The full RunLog is persisted to `workflow-runs/` and retrievable via `workflowskill_runs` with `run_id`.
|
|
146
130
|
|
|
147
|
-
| Param
|
|
148
|
-
|
|
149
|
-
| `workflow_name` | string | no
|
|
150
|
-
| `content`
|
|
151
|
-
| `inputs`
|
|
131
|
+
| Param | Type | Required | Description |
|
|
132
|
+
| --------------- | ------ | -------- | ------------------------------------------------ |
|
|
133
|
+
| `workflow_name` | string | no\* | Name of a skill resolved from skills directories |
|
|
134
|
+
| `content` | string | no\* | Inline SKILL.md content (bypasses skill files) |
|
|
135
|
+
| `inputs` | object | no | Override workflow input defaults |
|
|
152
136
|
|
|
153
|
-
|
|
137
|
+
\*One of `workflow_name` or `content` is required.
|
|
154
138
|
|
|
155
139
|
### `workflowskill_runs`
|
|
156
140
|
|
|
157
141
|
List and inspect past run logs.
|
|
158
142
|
|
|
159
|
-
| Param
|
|
160
|
-
|
|
161
|
-
| `workflow_name` | string | no
|
|
162
|
-
| `run_id`
|
|
163
|
-
| `status`
|
|
143
|
+
| Param | Type | Required | Description |
|
|
144
|
+
| --------------- | ------ | -------- | ----------------------------------- |
|
|
145
|
+
| `workflow_name` | string | no | Filter by workflow name |
|
|
146
|
+
| `run_id` | string | no | Get full RunLog detail for one run |
|
|
147
|
+
| `status` | string | no | Filter by `"success"` or `"failed"` |
|
|
164
148
|
|
|
165
149
|
No params → 20 most recent runs (summary view).
|
|
166
150
|
|
|
@@ -168,16 +152,16 @@ No params → 20 most recent runs (summary view).
|
|
|
168
152
|
|
|
169
153
|
Call Anthropic directly and return the text response. Uses the API key from OpenClaw's credential store. Useful in workflow `tool` steps when you need inline LLM reasoning.
|
|
170
154
|
|
|
171
|
-
| Param
|
|
172
|
-
|
|
173
|
-
| `prompt` | string | yes
|
|
174
|
-
| `model`
|
|
155
|
+
| Param | Type | Required | Description |
|
|
156
|
+
| -------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
|
157
|
+
| `prompt` | string | yes | The prompt to send to the LLM |
|
|
158
|
+
| `model` | string | no | Model alias (`haiku`, `sonnet`, `opus`) or full model ID — omit to use the default |
|
|
175
159
|
|
|
176
160
|
Returns `{ text: string }`.
|
|
177
161
|
|
|
178
162
|
## Development
|
|
179
163
|
|
|
180
|
-
The plugin imports from `workflowskill
|
|
164
|
+
The plugin imports from `workflowskill`, installed from npm. No build step is required for type checking:
|
|
181
165
|
|
|
182
166
|
```bash
|
|
183
167
|
npm install
|
package/index.ts
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
// Default-exports an object with id + register(api) per the OpenClaw plugin API.
|
|
5
5
|
|
|
6
6
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
import { AUTHORING_SKILL } from 'workflowskill';
|
|
9
10
|
import { validateHandler } from './tools/validate.js';
|
|
10
11
|
import { runHandler } from './tools/run.js';
|
|
11
12
|
import { runsHandler } from './tools/runs.js';
|
|
12
|
-
import {
|
|
13
|
+
import { createToolAdapter, type GatewayConfig } from './lib/adapters.js';
|
|
13
14
|
|
|
14
15
|
// ─── OpenClaw plugin API types ─────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -80,6 +81,70 @@ function toContent(result: unknown): { content: TextContent[] } {
|
|
|
80
81
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// ─── LLM helpers ───────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
const MODEL_ALIASES: Record<string, string> = {
|
|
87
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
88
|
+
sonnet: 'claude-sonnet-4-6',
|
|
89
|
+
opus: 'claude-opus-4-6',
|
|
90
|
+
};
|
|
91
|
+
const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';
|
|
92
|
+
|
|
93
|
+
function readAnthropicApiKey(): string {
|
|
94
|
+
const profilesPath = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
95
|
+
let parsed: {
|
|
96
|
+
profiles?: Record<string, { provider?: string; key?: string }>;
|
|
97
|
+
lastGood?: Record<string, string>;
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(readFileSync(profilesPath, 'utf-8')) as typeof parsed;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`WorkflowSkill: could not read OpenClaw auth profiles from ${profilesPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const profiles = parsed.profiles ?? {};
|
|
107
|
+
const lastGoodName = parsed.lastGood?.['anthropic'];
|
|
108
|
+
const profile = lastGoodName
|
|
109
|
+
? profiles[lastGoodName]
|
|
110
|
+
: Object.values(profiles).find((p) => p.provider === 'anthropic');
|
|
111
|
+
if (!profile?.key) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`WorkflowSkill: no anthropic profile found in ${profilesPath}. Add a profile with provider "anthropic" and a key.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return profile.key;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const ANTHROPIC_TIMEOUT_MS = 60_000;
|
|
120
|
+
|
|
121
|
+
async function callAnthropic(apiKey: string, model: string, prompt: string): Promise<string> {
|
|
122
|
+
const resolvedModel = MODEL_ALIASES[model] ?? model;
|
|
123
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
'x-api-key': apiKey,
|
|
128
|
+
'anthropic-version': '2023-06-01',
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
model: resolvedModel,
|
|
132
|
+
max_tokens: 8192,
|
|
133
|
+
messages: [{ role: 'user', content: prompt }],
|
|
134
|
+
}),
|
|
135
|
+
signal: AbortSignal.timeout(ANTHROPIC_TIMEOUT_MS),
|
|
136
|
+
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
const body = await response.text().catch(() => '');
|
|
139
|
+
throw new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
140
|
+
}
|
|
141
|
+
const data = (await response.json()) as {
|
|
142
|
+
content: Array<{ type: string; text: string }>;
|
|
143
|
+
};
|
|
144
|
+
const text = data.content.find((b) => b.type === 'text')?.text ?? '';
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
|
|
83
148
|
// ─── Plugin entry point ────────────────────────────────────────────────────
|
|
84
149
|
|
|
85
150
|
export default {
|
|
@@ -104,7 +169,7 @@ export default {
|
|
|
104
169
|
writeFileSync(join(skillDir, 'SKILL.md'), AUTHORING_SKILL + '\n' + openclawContext, 'utf-8');
|
|
105
170
|
|
|
106
171
|
const gatewayConfig = buildGatewayConfig(api.config);
|
|
107
|
-
const
|
|
172
|
+
const toolAdapter = createToolAdapter(gatewayConfig);
|
|
108
173
|
const { registerTool } = api;
|
|
109
174
|
|
|
110
175
|
// ── workflowskill_validate ────────────────────────────────────────────
|
|
@@ -125,7 +190,7 @@ export default {
|
|
|
125
190
|
required: ['content'],
|
|
126
191
|
},
|
|
127
192
|
execute: async (_id, params) => {
|
|
128
|
-
return toContent(
|
|
193
|
+
return toContent(validateHandler(params as { content: string }, toolAdapter));
|
|
129
194
|
},
|
|
130
195
|
});
|
|
131
196
|
|
|
@@ -159,7 +224,7 @@ export default {
|
|
|
159
224
|
await runHandler(
|
|
160
225
|
params as { workflow_name?: string; content?: string; inputs?: Record<string, unknown> },
|
|
161
226
|
workspace,
|
|
162
|
-
|
|
227
|
+
toolAdapter,
|
|
163
228
|
),
|
|
164
229
|
);
|
|
165
230
|
},
|
|
@@ -203,14 +268,14 @@ export default {
|
|
|
203
268
|
},
|
|
204
269
|
});
|
|
205
270
|
|
|
206
|
-
// ── workflowskill_llm
|
|
271
|
+
// ── workflowskill_llm ─────────────────────────────────────────────────
|
|
207
272
|
registerTool({
|
|
208
273
|
name: 'workflowskill_llm',
|
|
209
274
|
description:
|
|
210
|
-
'Call Anthropic
|
|
211
|
-
'Uses the API key from OpenClaw\'s credential store
|
|
275
|
+
'Call the Anthropic LLM and return { text }. ' +
|
|
276
|
+
'Uses the API key from OpenClaw\'s credential store. ' +
|
|
212
277
|
'Use in workflow tool steps when you need LLM reasoning inline. ' +
|
|
213
|
-
'model is optional (haiku / sonnet / opus or full model ID); omit for
|
|
278
|
+
'model is optional (haiku / sonnet / opus or full model ID); omit for haiku.',
|
|
214
279
|
parameters: {
|
|
215
280
|
type: 'object',
|
|
216
281
|
properties: {
|
|
@@ -220,16 +285,18 @@ export default {
|
|
|
220
285
|
},
|
|
221
286
|
model: {
|
|
222
287
|
type: 'string',
|
|
223
|
-
description: 'Model alias or ID. Optional
|
|
288
|
+
description: 'Model alias (haiku/sonnet/opus) or full model ID. Optional.',
|
|
224
289
|
},
|
|
225
290
|
},
|
|
226
291
|
required: ['prompt'],
|
|
227
292
|
},
|
|
228
293
|
execute: async (_id, params) => {
|
|
229
|
-
const { prompt, model } = params as { prompt: string; model?: string };
|
|
230
|
-
const
|
|
231
|
-
|
|
294
|
+
const { prompt, model = DEFAULT_MODEL } = params as { prompt: string; model?: string };
|
|
295
|
+
const apiKey = readAnthropicApiKey();
|
|
296
|
+
const text = await callAnthropic(apiKey, model, prompt);
|
|
297
|
+
return toContent({ text });
|
|
232
298
|
},
|
|
233
299
|
});
|
|
300
|
+
|
|
234
301
|
},
|
|
235
302
|
};
|
package/lib/adapters.ts
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
// adapters.ts — host-delegating
|
|
1
|
+
// adapters.ts — host-delegating tool adapter for the OpenClaw plugin.
|
|
2
2
|
//
|
|
3
3
|
// Tool steps delegate to the Gateway HTTP API via HostToolAdapter (POST /tools/invoke).
|
|
4
|
-
// LLM steps use AnthropicLLMAdapter with the API key read directly from
|
|
5
|
-
// OpenClaw's credential store at ~/.openclaw/agents/main/agent/auth-profiles.json.
|
|
6
4
|
|
|
7
|
-
import {
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import type { LLMAdapter, ToolAdapter, ToolDescriptor, ToolResult } from 'workflowskill';
|
|
11
|
-
import { AnthropicLLMAdapter, BuiltinToolAdapter } from 'workflowskill';
|
|
12
|
-
|
|
13
|
-
// Tools served locally by BuiltinToolAdapter (web.scrape, etc.)
|
|
14
|
-
// These bypass the gateway and run in-process.
|
|
15
|
-
const BUILTIN_TOOL_NAMES = new Set(['web.scrape']);
|
|
16
|
-
|
|
17
|
-
// Lazy singleton — BuiltinToolAdapter.create() is async so we initialize on first use.
|
|
18
|
-
let _builtinToolsPromise: Promise<BuiltinToolAdapter> | null = null;
|
|
19
|
-
function getBuiltinTools(): Promise<BuiltinToolAdapter> {
|
|
20
|
-
if (!_builtinToolsPromise) {
|
|
21
|
-
_builtinToolsPromise = BuiltinToolAdapter.create();
|
|
22
|
-
}
|
|
23
|
-
return _builtinToolsPromise as Promise<BuiltinToolAdapter>;
|
|
24
|
-
}
|
|
5
|
+
import type { ToolAdapter, ToolDescriptor, ToolResult } from 'workflowskill';
|
|
25
6
|
|
|
26
7
|
export interface GatewayConfig {
|
|
27
8
|
baseUrl: string;
|
|
@@ -29,11 +10,6 @@ export interface GatewayConfig {
|
|
|
29
10
|
timeoutMs?: number;
|
|
30
11
|
}
|
|
31
12
|
|
|
32
|
-
export interface AdapterSet {
|
|
33
|
-
toolAdapter: ToolAdapter;
|
|
34
|
-
llmAdapter: LLMAdapter;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
13
|
// Tools this plugin registers — must not be forwarded to the gateway to prevent infinite recursion.
|
|
38
14
|
const SELF_REFERENCING_TOOLS = new Set([
|
|
39
15
|
'workflowskill_validate',
|
|
@@ -116,84 +92,11 @@ export class HostToolAdapter implements ToolAdapter {
|
|
|
116
92
|
}
|
|
117
93
|
|
|
118
94
|
/**
|
|
119
|
-
*
|
|
120
|
-
* Throws a clear error if the file is missing or has no anthropic profile.
|
|
121
|
-
*/
|
|
122
|
-
function readAnthropicApiKey(): string {
|
|
123
|
-
const profilesPath = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
124
|
-
let parsed: {
|
|
125
|
-
profiles?: Record<string, { provider?: string; key?: string }>;
|
|
126
|
-
lastGood?: Record<string, string>;
|
|
127
|
-
};
|
|
128
|
-
try {
|
|
129
|
-
parsed = JSON.parse(readFileSync(profilesPath, 'utf-8')) as typeof parsed;
|
|
130
|
-
} catch (err) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`WorkflowSkill: could not read OpenClaw auth profiles from ${profilesPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
const profiles = parsed.profiles ?? {};
|
|
136
|
-
// Prefer the profile OpenClaw last used successfully for anthropic.
|
|
137
|
-
const lastGoodName = parsed.lastGood?.['anthropic'];
|
|
138
|
-
const profile = lastGoodName
|
|
139
|
-
? profiles[lastGoodName]
|
|
140
|
-
: Object.values(profiles).find((p) => p.provider === 'anthropic');
|
|
141
|
-
if (!profile?.key) {
|
|
142
|
-
throw new Error(
|
|
143
|
-
`WorkflowSkill: no anthropic profile found in ${profilesPath}. Add a profile with provider "anthropic" and a key.`,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
return profile.key;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Create host adapters backed by the Gateway HTTP API.
|
|
95
|
+
* Create a ToolAdapter backed by the Gateway HTTP API.
|
|
151
96
|
*
|
|
152
97
|
* HostToolAdapter forwards tool steps to the gateway's POST /tools/invoke endpoint.
|
|
153
|
-
* Self-referencing tools (the plugin's own
|
|
154
|
-
* LLM steps use AnthropicLLMAdapter with the key read from OpenClaw's credential store.
|
|
98
|
+
* Self-referencing tools (the plugin's own tools) are blocked to prevent recursion.
|
|
155
99
|
*/
|
|
156
|
-
export function
|
|
157
|
-
|
|
158
|
-
const llmAdapter = new AnthropicLLMAdapter(readAnthropicApiKey());
|
|
159
|
-
|
|
160
|
-
const LLM_COMPLETE = 'workflowskill_llm';
|
|
161
|
-
const LLM_COMPLETE_DESCRIPTOR: ToolDescriptor = {
|
|
162
|
-
name: LLM_COMPLETE,
|
|
163
|
-
description: 'Call the host LLM with a prompt; returns { text }.',
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const toolAdapter: ToolAdapter = {
|
|
167
|
-
has(toolName: string): boolean {
|
|
168
|
-
if (toolName === LLM_COMPLETE) return true;
|
|
169
|
-
if (BUILTIN_TOOL_NAMES.has(toolName)) return true;
|
|
170
|
-
return hostTools.has(toolName);
|
|
171
|
-
},
|
|
172
|
-
async invoke(toolName: string, args: Record<string, unknown>): Promise<ToolResult> {
|
|
173
|
-
if (toolName === LLM_COMPLETE) {
|
|
174
|
-
const result = await llmAdapter.call(
|
|
175
|
-
args.model as string | undefined,
|
|
176
|
-
args.prompt as string,
|
|
177
|
-
);
|
|
178
|
-
return { output: { text: result.text } };
|
|
179
|
-
}
|
|
180
|
-
if (BUILTIN_TOOL_NAMES.has(toolName)) {
|
|
181
|
-
const builtinTools = await getBuiltinTools();
|
|
182
|
-
return builtinTools.invoke(toolName, args);
|
|
183
|
-
}
|
|
184
|
-
return hostTools.invoke(toolName, args);
|
|
185
|
-
},
|
|
186
|
-
list(): ToolDescriptor[] {
|
|
187
|
-
const hostToolList = hostTools.list();
|
|
188
|
-
return [
|
|
189
|
-
LLM_COMPLETE_DESCRIPTOR,
|
|
190
|
-
...hostToolList.filter((t) => t.name !== LLM_COMPLETE),
|
|
191
|
-
];
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
toolAdapter,
|
|
197
|
-
llmAdapter,
|
|
198
|
-
};
|
|
100
|
+
export function createToolAdapter(gatewayConfig: GatewayConfig): ToolAdapter {
|
|
101
|
+
return new HostToolAdapter(gatewayConfig);
|
|
199
102
|
}
|
package/lib/storage.ts
CHANGED
|
@@ -53,7 +53,7 @@ export interface RunSummaryEntry {
|
|
|
53
53
|
duration_ms: number;
|
|
54
54
|
steps_executed: number;
|
|
55
55
|
steps_skipped: number;
|
|
56
|
-
|
|
56
|
+
total_duration_ms: number;
|
|
57
57
|
error_message?: string;
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -87,7 +87,7 @@ export function listRuns(
|
|
|
87
87
|
duration_ms: log.duration_ms,
|
|
88
88
|
steps_executed: log.summary.steps_executed,
|
|
89
89
|
steps_skipped: log.summary.steps_skipped,
|
|
90
|
-
|
|
90
|
+
total_duration_ms: log.summary.total_duration_ms,
|
|
91
91
|
error_message: log.error?.message,
|
|
92
92
|
});
|
|
93
93
|
} catch {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-workflowskill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "WorkflowSkill plugin for OpenClaw — author, validate, run, and review YAML workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"workflowskill": "^0.
|
|
38
|
+
"workflowskill": "^0.3.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.3.0",
|
package/tools/run.ts
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
// Returns a compact summary to avoid blowing up the calling agent's context.
|
|
6
6
|
|
|
7
7
|
import { runWorkflowSkill } from 'workflowskill';
|
|
8
|
-
import type { RunLog, RunSummary } from 'workflowskill';
|
|
9
|
-
import type { AdapterSet } from '../lib/adapters.js';
|
|
8
|
+
import type { RunLog, RunSummary, ToolAdapter } from 'workflowskill';
|
|
10
9
|
import { resolveSkillContent, saveRunLog } from '../lib/storage.js';
|
|
11
10
|
|
|
12
11
|
export interface RunParams {
|
|
@@ -64,7 +63,7 @@ function summarizeRunLog(log: RunLog): RunSummarySuccess | RunSummaryFailed {
|
|
|
64
63
|
export async function runHandler(
|
|
65
64
|
params: RunParams,
|
|
66
65
|
workspace: string,
|
|
67
|
-
|
|
66
|
+
toolAdapter: ToolAdapter,
|
|
68
67
|
): Promise<RunSummarySuccess | RunSummaryFailed> {
|
|
69
68
|
const { workflow_name, content: inlineContent, inputs = {} } = params;
|
|
70
69
|
|
|
@@ -73,13 +72,10 @@ export async function runHandler(
|
|
|
73
72
|
content = resolveSkillContent(workspace, workflow_name);
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
const { toolAdapter, llmAdapter } = adapters;
|
|
77
|
-
|
|
78
75
|
const log: RunLog = await runWorkflowSkill({
|
|
79
76
|
content,
|
|
80
77
|
inputs,
|
|
81
78
|
toolAdapter,
|
|
82
|
-
llmAdapter,
|
|
83
79
|
workflowName: workflow_name ?? 'inline',
|
|
84
80
|
});
|
|
85
81
|
|
package/tools/validate.ts
CHANGED
|
@@ -15,6 +15,6 @@ export interface ValidateResult {
|
|
|
15
15
|
stepTypes?: string[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export
|
|
18
|
+
export function validateHandler(params: ValidateParams, toolAdapter: ToolAdapter): ValidateResult {
|
|
19
19
|
return validateWorkflowSkill({ content: params.content, toolAdapter });
|
|
20
20
|
}
|