openclaw-workflowskill 0.2.1 → 0.3.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/README.md +64 -76
- package/index.ts +13 -13
- package/lib/adapters.ts +6 -103
- package/lib/storage.ts +2 -2
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
- package/tools/llm.ts +81 -0
- package/tools/run.ts +2 -6
- package/tools/validate.ts +1 -1
package/README.md
CHANGED
|
@@ -13,53 +13,27 @@ 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
|
|
|
51
25
|
Requires [OpenClaw](https://openclaw.ai).
|
|
52
26
|
|
|
53
|
-
### 1.
|
|
27
|
+
### 1. Set your profile to Coding
|
|
28
|
+
|
|
29
|
+
In OpenClaw settings, change your profile from `messaging` (the default) to `coding`. The `coding` profile grants the agent filesystem access, which is required to read and write workflow files. For more granular access configuration, refer to the [OpenClaw documentation](https://docs.openclaw.ai/).
|
|
30
|
+
|
|
31
|
+
### 2. Install the plugin
|
|
54
32
|
|
|
55
33
|
```bash
|
|
56
34
|
openclaw plugins install openclaw-workflowskill
|
|
57
35
|
```
|
|
58
36
|
|
|
59
|
-
### 2. Configure Anthropic credentials
|
|
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
37
|
### 3. Restart the gateway
|
|
64
38
|
|
|
65
39
|
```bash
|
|
@@ -76,16 +50,46 @@ openclaw skills list
|
|
|
76
50
|
# → workflowskill-author (user-invocable)
|
|
77
51
|
```
|
|
78
52
|
|
|
53
|
+
> **Note:** `workflowskill_llm` uses your Anthropic credentials from the main agent — no separate API key configuration needed.
|
|
54
|
+
|
|
55
|
+
### 5. Create a workflow
|
|
56
|
+
|
|
57
|
+
Just tell the agent what you want to automate:
|
|
58
|
+
|
|
59
|
+
> **You:** I want to check Hacker News for AI stories every morning and email me a summary.
|
|
60
|
+
>
|
|
61
|
+
> **Agent:** I'll author a WorkflowSkill for that. _(invokes `/workflowskill-author`, writes a SKILL.md, runs `workflowskill_validate`)_
|
|
62
|
+
>
|
|
63
|
+
> Validated — 3 steps: `fetch`, `filter`, `email`. Running a test now... _(invokes `workflowskill_run`)_
|
|
64
|
+
>
|
|
65
|
+
> Run complete: 4 AI stories found, summary drafted. Ready to schedule — want me to set up a daily cron at 8 AM?
|
|
66
|
+
|
|
67
|
+
### 6. Schedule it
|
|
68
|
+
|
|
69
|
+
Ask the agent to set up a cron job, or add one manually at `~/.openclaw/cron/jobs.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"payload": {
|
|
74
|
+
"kind": "agentTurn",
|
|
75
|
+
"message": "Run the daily-triage workflow using workflowskill_run\n\nSend results to Slack in the #general channel",
|
|
76
|
+
"model": "haiku"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Your agent will use `"model": "haiku"` for cron jobs, ensuring execution is cheap and lightweight.
|
|
82
|
+
|
|
79
83
|
## Tools
|
|
80
84
|
|
|
81
85
|
Registers four tools with the OpenClaw agent:
|
|
82
86
|
|
|
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`
|
|
87
|
+
| Tool | Description |
|
|
88
|
+
| ------------------------ | ------------------------------------------------------------- |
|
|
89
|
+
| `workflowskill_validate` | Parse and validate a SKILL.md or raw YAML workflow |
|
|
90
|
+
| `workflowskill_run` | Execute a workflow and return a compact run summary |
|
|
91
|
+
| `workflowskill_runs` | List and inspect past run logs |
|
|
92
|
+
| `workflowskill_llm` | Call Anthropic directly for inline LLM reasoning in workflows |
|
|
89
93
|
|
|
90
94
|
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
95
|
|
|
@@ -100,22 +104,6 @@ Also ships the `/workflowskill-author` skill — just say "I want to automate X"
|
|
|
100
104
|
daily-triage-2024-01-15T09-00-00.000Z.json
|
|
101
105
|
```
|
|
102
106
|
|
|
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
107
|
Review past runs via `workflowskill_runs`.
|
|
120
108
|
|
|
121
109
|
## Architecture
|
|
@@ -134,9 +122,9 @@ The plugin's own four tools (`workflowskill_validate`, `workflowskill_run`, `wor
|
|
|
134
122
|
|
|
135
123
|
Parse and validate a SKILL.md or raw YAML workflow.
|
|
136
124
|
|
|
137
|
-
| Param
|
|
138
|
-
|
|
139
|
-
| `content` | string | yes
|
|
125
|
+
| Param | Type | Required | Description |
|
|
126
|
+
| --------- | ------ | -------- | ---------------------------------- |
|
|
127
|
+
| `content` | string | yes | SKILL.md text or raw workflow YAML |
|
|
140
128
|
|
|
141
129
|
Returns `{ valid, errors[], name, stepCount, stepTypes[] }`.
|
|
142
130
|
|
|
@@ -144,23 +132,23 @@ Returns `{ valid, errors[], name, stepCount, stepTypes[] }`.
|
|
|
144
132
|
|
|
145
133
|
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
134
|
|
|
147
|
-
| Param
|
|
148
|
-
|
|
149
|
-
| `workflow_name` | string | no
|
|
150
|
-
| `content`
|
|
151
|
-
| `inputs`
|
|
135
|
+
| Param | Type | Required | Description |
|
|
136
|
+
| --------------- | ------ | -------- | ------------------------------------------------ |
|
|
137
|
+
| `workflow_name` | string | no\* | Name of a skill resolved from skills directories |
|
|
138
|
+
| `content` | string | no\* | Inline SKILL.md content (bypasses skill files) |
|
|
139
|
+
| `inputs` | object | no | Override workflow input defaults |
|
|
152
140
|
|
|
153
|
-
|
|
141
|
+
\*One of `workflow_name` or `content` is required.
|
|
154
142
|
|
|
155
143
|
### `workflowskill_runs`
|
|
156
144
|
|
|
157
145
|
List and inspect past run logs.
|
|
158
146
|
|
|
159
|
-
| Param
|
|
160
|
-
|
|
161
|
-
| `workflow_name` | string | no
|
|
162
|
-
| `run_id`
|
|
163
|
-
| `status`
|
|
147
|
+
| Param | Type | Required | Description |
|
|
148
|
+
| --------------- | ------ | -------- | ----------------------------------- |
|
|
149
|
+
| `workflow_name` | string | no | Filter by workflow name |
|
|
150
|
+
| `run_id` | string | no | Get full RunLog detail for one run |
|
|
151
|
+
| `status` | string | no | Filter by `"success"` or `"failed"` |
|
|
164
152
|
|
|
165
153
|
No params → 20 most recent runs (summary view).
|
|
166
154
|
|
|
@@ -168,16 +156,16 @@ No params → 20 most recent runs (summary view).
|
|
|
168
156
|
|
|
169
157
|
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
158
|
|
|
171
|
-
| Param
|
|
172
|
-
|
|
173
|
-
| `prompt` | string | yes
|
|
174
|
-
| `model`
|
|
159
|
+
| Param | Type | Required | Description |
|
|
160
|
+
| -------- | ------ | -------- | ---------------------------------------------------------------------------------- |
|
|
161
|
+
| `prompt` | string | yes | The prompt to send to the LLM |
|
|
162
|
+
| `model` | string | no | Model alias (`haiku`, `sonnet`, `opus`) or full model ID — omit to use the default |
|
|
175
163
|
|
|
176
164
|
Returns `{ text: string }`.
|
|
177
165
|
|
|
178
166
|
## Development
|
|
179
167
|
|
|
180
|
-
The plugin imports from `workflowskill
|
|
168
|
+
The plugin imports from `workflowskill`, installed from npm. No build step is required for type checking:
|
|
181
169
|
|
|
182
170
|
```bash
|
|
183
171
|
npm install
|
package/index.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { AUTHORING_SKILL } from 'workflowskill';
|
|
|
9
9
|
import { validateHandler } from './tools/validate.js';
|
|
10
10
|
import { runHandler } from './tools/run.js';
|
|
11
11
|
import { runsHandler } from './tools/runs.js';
|
|
12
|
-
import {
|
|
12
|
+
import { llmHandler } from './tools/llm.js';
|
|
13
|
+
import { createToolAdapter, type GatewayConfig } from './lib/adapters.js';
|
|
13
14
|
|
|
14
15
|
// ─── OpenClaw plugin API types ─────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -83,7 +84,7 @@ function toContent(result: unknown): { content: TextContent[] } {
|
|
|
83
84
|
// ─── Plugin entry point ────────────────────────────────────────────────────
|
|
84
85
|
|
|
85
86
|
export default {
|
|
86
|
-
id: 'workflowskill',
|
|
87
|
+
id: 'openclaw-workflowskill',
|
|
87
88
|
|
|
88
89
|
register(api: PluginApi): void {
|
|
89
90
|
const workspace = api?.config?.agents?.defaults?.workspace;
|
|
@@ -104,7 +105,7 @@ export default {
|
|
|
104
105
|
writeFileSync(join(skillDir, 'SKILL.md'), AUTHORING_SKILL + '\n' + openclawContext, 'utf-8');
|
|
105
106
|
|
|
106
107
|
const gatewayConfig = buildGatewayConfig(api.config);
|
|
107
|
-
const
|
|
108
|
+
const toolAdapter = createToolAdapter(gatewayConfig);
|
|
108
109
|
const { registerTool } = api;
|
|
109
110
|
|
|
110
111
|
// ── workflowskill_validate ────────────────────────────────────────────
|
|
@@ -125,7 +126,7 @@ export default {
|
|
|
125
126
|
required: ['content'],
|
|
126
127
|
},
|
|
127
128
|
execute: async (_id, params) => {
|
|
128
|
-
return toContent(
|
|
129
|
+
return toContent(validateHandler(params as { content: string }, toolAdapter));
|
|
129
130
|
},
|
|
130
131
|
});
|
|
131
132
|
|
|
@@ -159,7 +160,7 @@ export default {
|
|
|
159
160
|
await runHandler(
|
|
160
161
|
params as { workflow_name?: string; content?: string; inputs?: Record<string, unknown> },
|
|
161
162
|
workspace,
|
|
162
|
-
|
|
163
|
+
toolAdapter,
|
|
163
164
|
),
|
|
164
165
|
);
|
|
165
166
|
},
|
|
@@ -203,14 +204,14 @@ export default {
|
|
|
203
204
|
},
|
|
204
205
|
});
|
|
205
206
|
|
|
206
|
-
// ── workflowskill_llm
|
|
207
|
+
// ── workflowskill_llm ─────────────────────────────────────────────────
|
|
207
208
|
registerTool({
|
|
208
209
|
name: 'workflowskill_llm',
|
|
209
210
|
description:
|
|
210
|
-
'Call Anthropic
|
|
211
|
-
'Uses the API key from OpenClaw\'s credential store
|
|
211
|
+
'Call the Anthropic LLM and return { text }. ' +
|
|
212
|
+
'Uses the API key from OpenClaw\'s credential store. ' +
|
|
212
213
|
'Use in workflow tool steps when you need LLM reasoning inline. ' +
|
|
213
|
-
'model is optional (haiku / sonnet / opus or full model ID); omit for
|
|
214
|
+
'model is optional (haiku / sonnet / opus or full model ID); omit for haiku.',
|
|
214
215
|
parameters: {
|
|
215
216
|
type: 'object',
|
|
216
217
|
properties: {
|
|
@@ -220,16 +221,15 @@ export default {
|
|
|
220
221
|
},
|
|
221
222
|
model: {
|
|
222
223
|
type: 'string',
|
|
223
|
-
description: 'Model alias or ID. Optional
|
|
224
|
+
description: 'Model alias (haiku/sonnet/opus) or full model ID. Optional.',
|
|
224
225
|
},
|
|
225
226
|
},
|
|
226
227
|
required: ['prompt'],
|
|
227
228
|
},
|
|
228
229
|
execute: async (_id, params) => {
|
|
229
|
-
|
|
230
|
-
const result = await adapters.llmAdapter.call(model, prompt);
|
|
231
|
-
return toContent({ text: result.text });
|
|
230
|
+
return toContent(await llmHandler(params as { prompt: string; model?: string }));
|
|
232
231
|
},
|
|
233
232
|
});
|
|
233
|
+
|
|
234
234
|
},
|
|
235
235
|
};
|
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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "workflowskill",
|
|
2
|
+
"id": "openclaw-workflowskill",
|
|
3
3
|
"name": "WorkflowSkill",
|
|
4
4
|
"description": "Author, validate, run, and review WorkflowSkill YAML workflows",
|
|
5
|
-
"version": "0.1
|
|
5
|
+
"version": "0.3.1",
|
|
6
6
|
"skills": ["skills/workflowskill-author"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-workflowskill",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
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/llm.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// workflowskill_llm — call Anthropic LLM using OpenClaw's credential store.
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export interface LlmParams {
|
|
8
|
+
prompt: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LlmResult {
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MODEL_ALIASES: Record<string, string> = {
|
|
17
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
18
|
+
sonnet: 'claude-sonnet-4-6',
|
|
19
|
+
opus: 'claude-opus-4-6',
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';
|
|
22
|
+
const ANTHROPIC_TIMEOUT_MS = 60_000;
|
|
23
|
+
|
|
24
|
+
function readAnthropicApiKey(): string {
|
|
25
|
+
const profilesPath = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
26
|
+
let parsed: {
|
|
27
|
+
profiles?: Record<string, { provider?: string; key?: string }>;
|
|
28
|
+
lastGood?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(readFileSync(profilesPath, 'utf-8')) as typeof parsed;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`WorkflowSkill: could not read OpenClaw auth profiles from ${profilesPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const profiles = parsed.profiles ?? {};
|
|
38
|
+
const lastGoodName = parsed.lastGood?.['anthropic'];
|
|
39
|
+
const profile = lastGoodName
|
|
40
|
+
? profiles[lastGoodName]
|
|
41
|
+
: Object.values(profiles).find((p) => p.provider === 'anthropic');
|
|
42
|
+
if (!profile?.key) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`WorkflowSkill: no anthropic profile found in ${profilesPath}. Add a profile with provider "anthropic" and a key.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return profile.key;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function callAnthropic(apiKey: string, model: string, prompt: string): Promise<string> {
|
|
51
|
+
const resolvedModel = MODEL_ALIASES[model] ?? model;
|
|
52
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'x-api-key': apiKey,
|
|
57
|
+
'anthropic-version': '2023-06-01',
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
model: resolvedModel,
|
|
61
|
+
max_tokens: 8192,
|
|
62
|
+
messages: [{ role: 'user', content: prompt }],
|
|
63
|
+
}),
|
|
64
|
+
signal: AbortSignal.timeout(ANTHROPIC_TIMEOUT_MS),
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const body = await response.text().catch(() => '');
|
|
68
|
+
throw new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
69
|
+
}
|
|
70
|
+
const data = (await response.json()) as {
|
|
71
|
+
content: Array<{ type: string; text: string }>;
|
|
72
|
+
};
|
|
73
|
+
return data.content.find((b) => b.type === 'text')?.text ?? '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function llmHandler(params: LlmParams): Promise<LlmResult> {
|
|
77
|
+
const { prompt, model = DEFAULT_MODEL } = params;
|
|
78
|
+
const apiKey = readAnthropicApiKey();
|
|
79
|
+
const text = await callAnthropic(apiKey, model, prompt);
|
|
80
|
+
return { text };
|
|
81
|
+
}
|
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
|
}
|