@keystrokehq/skills 0.0.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/AGENTS-blurb.md +123 -0
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/keystroke-agent-authoring/SKILL.md +225 -0
- package/keystroke-agent-authoring/evals/evals.json +29 -0
- package/keystroke-agent-authoring/references/messaging-gateways.md +242 -0
- package/keystroke-agent-authoring/references/patterns.md +417 -0
- package/keystroke-agent-authoring/references/prebuilt-integrations.md +879 -0
- package/keystroke-agent-authoring/references/sandbox-and-mcp.md +214 -0
- package/keystroke-agent-authoring/references/source-map.md +182 -0
- package/keystroke-agent-authoring/references/testing.md +85 -0
- package/keystroke-cli-workspace/SKILL.md +93 -0
- package/keystroke-cli-workspace/evals/evals.json +23 -0
- package/keystroke-cli-workspace/references/command-map.md +50 -0
- package/keystroke-cli-workspace/references/credentials-and-connect.md +79 -0
- package/keystroke-cli-workspace/references/project-lifecycle.md +85 -0
- package/keystroke-credential-binding/SKILL.md +509 -0
- package/keystroke-credential-binding/evals/evals.json +29 -0
- package/keystroke-credential-binding/references/cli.md +85 -0
- package/keystroke-credential-binding/references/patterns.md +878 -0
- package/keystroke-credential-binding/references/source-map.md +69 -0
- package/keystroke-data-toolkit/SKILL.md +59 -0
- package/keystroke-data-toolkit/evals/evals.json +23 -0
- package/keystroke-data-toolkit/references/usage.md +79 -0
- package/keystroke-task-authoring/SKILL.md +124 -0
- package/keystroke-task-authoring/evals/evals.json +23 -0
- package/keystroke-task-authoring/references/patterns.md +132 -0
- package/keystroke-task-authoring/references/source-map.md +61 -0
- package/keystroke-trigger-authoring/SKILL.md +189 -0
- package/keystroke-trigger-authoring/evals/evals.json +29 -0
- package/keystroke-trigger-authoring/references/patterns.md +265 -0
- package/keystroke-trigger-authoring/references/source-map.md +128 -0
- package/keystroke-trigger-authoring/references/testing.md +148 -0
- package/keystroke-workflow-as-tool-debugging/SKILL.md +52 -0
- package/keystroke-workflow-as-tool-debugging/evals/evals.json +23 -0
- package/keystroke-workflow-as-tool-debugging/references/playbook.md +77 -0
- package/keystroke-workflow-authoring/SKILL.md +234 -0
- package/keystroke-workflow-authoring/evals/evals.json +29 -0
- package/keystroke-workflow-authoring/references/patterns.md +265 -0
- package/keystroke-workflow-authoring/references/prebuilt-integrations.md +811 -0
- package/keystroke-workflow-authoring/references/runtime-helpers.md +264 -0
- package/keystroke-workflow-authoring/references/source-map.md +108 -0
- package/keystroke-workflow-authoring/references/testing.md +108 -0
- package/package.json +26 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Workflow Runtime Helpers
|
|
2
|
+
|
|
3
|
+
Read this file when the user asks what they can access from `Workflow.run(...)` or `Step.run(...)`.
|
|
4
|
+
|
|
5
|
+
`Operation`, `Step`, and `Tool` are aliases for the same class. In this workflow reference, the operation runtime context is described with the workflow-oriented `Step` naming.
|
|
6
|
+
|
|
7
|
+
## Workflow runtime boundary
|
|
8
|
+
|
|
9
|
+
Treat authored workflows as TypeScript orchestration code, not as a shell runtime.
|
|
10
|
+
|
|
11
|
+
Teach these rules explicitly:
|
|
12
|
+
- a workflow cannot run bash commands as part of the workflow authoring model
|
|
13
|
+
- a step is not the place to shell out to `python`, `pnpm`, or arbitrary binaries by default
|
|
14
|
+
- a workflow is for control flow, typed inputs and outputs, waits, hooks, and calls to steps, child workflows, or agents
|
|
15
|
+
- if the task needs shell access, persistent filesystem state, or tool installation, move that work into an agent
|
|
16
|
+
- if the agent needs Python or another binary, install it in the sandbox through sandbox setup or runtime preparation
|
|
17
|
+
|
|
18
|
+
## Workflow `ctx.wait(duration)`
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
run: async (_input, ctx) => {
|
|
22
|
+
await ctx.wait('10m');
|
|
23
|
+
return { resumed: true };
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use this when the workflow should pause durably and resume later.
|
|
28
|
+
|
|
29
|
+
In authored workflow code, treat `ctx.wait(...)` as taking duration strings.
|
|
30
|
+
|
|
31
|
+
Common public examples are:
|
|
32
|
+
|
|
33
|
+
- `await ctx.wait('30s')`
|
|
34
|
+
- `await ctx.wait('10m')`
|
|
35
|
+
- `await ctx.wait('2h')`
|
|
36
|
+
- `await ctx.wait('3d')`
|
|
37
|
+
- `await ctx.wait('1w')`
|
|
38
|
+
- `await ctx.wait('1y')`
|
|
39
|
+
|
|
40
|
+
The executor normalizes waits into milliseconds internally, but the public workflow authoring surface to teach by default is the unit-based string form.
|
|
41
|
+
|
|
42
|
+
When a workflow may be used as an agent tool, prefer adding intent metadata so yield receipts are understandable:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
run: async (_input, ctx) => {
|
|
46
|
+
await ctx.wait('30m', {
|
|
47
|
+
intent: 'data-settlement',
|
|
48
|
+
reason: 'The provider needs time to settle before final reconciliation.',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return { resumed: true };
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Workflow `ctx.createHook(name)`
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
run: async (_input, ctx) => {
|
|
59
|
+
const approval = ctx.createHook('approval');
|
|
60
|
+
await approval;
|
|
61
|
+
|
|
62
|
+
return { approved: true };
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use this when the workflow needs an external approval or resume point.
|
|
67
|
+
|
|
68
|
+
The hook returned by `ctx.createHook(name)` is promise-like, so you can `await` it directly.
|
|
69
|
+
|
|
70
|
+
Think of a hook as a set of workflow suspension endpoints:
|
|
71
|
+
|
|
72
|
+
- `resumeUrl`: endpoint that resumes the workflow
|
|
73
|
+
- `cancelUrl`: endpoint that cancels the hook
|
|
74
|
+
- `approvalPageUrl`: URL for a built-in approval page or human-facing approval flow
|
|
75
|
+
- `token`: stable identifier for the hook instance
|
|
76
|
+
|
|
77
|
+
It also exposes these properties:
|
|
78
|
+
|
|
79
|
+
- `token`
|
|
80
|
+
- `resumeUrl`
|
|
81
|
+
- `cancelUrl`
|
|
82
|
+
- `approvalPageUrl`
|
|
83
|
+
|
|
84
|
+
When the hook may be mediated through an agent tool, include intent metadata:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
run: async (_input, ctx) => {
|
|
88
|
+
const approval = ctx.createHook('approval', {
|
|
89
|
+
intent: 'human-decision',
|
|
90
|
+
prompt: 'Approve the refund?',
|
|
91
|
+
reason: 'Refunds above the policy threshold require approval.',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = await approval;
|
|
95
|
+
return { result };
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
run: async (_input, ctx) => {
|
|
103
|
+
const approval = ctx.createHook('approval');
|
|
104
|
+
|
|
105
|
+
const token = await approval.token;
|
|
106
|
+
const resumeUrl = await approval.resumeUrl;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
token,
|
|
110
|
+
resumeUrl,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
You can also use the resumed payload in the workflow.
|
|
116
|
+
|
|
117
|
+
If the external caller resumes the hook with structured data, `await approval` resolves to that payload:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
run: async (_input, ctx) => {
|
|
121
|
+
const approval = ctx.createHook('approval');
|
|
122
|
+
const approvalPageUrl = await approval.approvalPageUrl;
|
|
123
|
+
|
|
124
|
+
// Send approvalPageUrl to a human or external system here.
|
|
125
|
+
|
|
126
|
+
const result = (await approval) as {
|
|
127
|
+
approved?: boolean;
|
|
128
|
+
reviewer?: string;
|
|
129
|
+
note?: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
approved: result.approved === true,
|
|
134
|
+
reviewer: result.reviewer ?? 'unknown',
|
|
135
|
+
note: result.note ?? '',
|
|
136
|
+
approvalPageUrl,
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
You can also create hooks in a loop when the workflow should wait for repeated review until a condition is met:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
run: async (_input, ctx) => {
|
|
145
|
+
let round = 1;
|
|
146
|
+
let latestNote = '';
|
|
147
|
+
|
|
148
|
+
while (true) {
|
|
149
|
+
const review = ctx.createHook(`approval-round-${round}`);
|
|
150
|
+
const result = (await review) as {
|
|
151
|
+
approved?: boolean;
|
|
152
|
+
note?: string;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
latestNote = result.note ?? '';
|
|
156
|
+
|
|
157
|
+
if (result.approved === true) {
|
|
158
|
+
return {
|
|
159
|
+
approved: true,
|
|
160
|
+
rounds: round,
|
|
161
|
+
latestNote,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
round += 1;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Workflow `ctx.workflowGlobals`
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
workflowGlobals: z.object({
|
|
174
|
+
tenantId: z.string(),
|
|
175
|
+
}),
|
|
176
|
+
run: async (_input, ctx) => {
|
|
177
|
+
return { tenantId: ctx.workflowGlobals.tenantId };
|
|
178
|
+
};
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Use this for typed workflow-wide runtime values.
|
|
182
|
+
|
|
183
|
+
## Workflow `ctx.workflowId`
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
run: async (_input, ctx) => {
|
|
187
|
+
return { workflowId: ctx.workflowId };
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use this when the workflow output or logs need the current run id.
|
|
192
|
+
|
|
193
|
+
## Workflow `ctx.hasCredentialSet(id)`
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
run: async (input, ctx) => {
|
|
197
|
+
if (ctx.hasCredentialSet('slack')) {
|
|
198
|
+
await sendSlackMessage.run({
|
|
199
|
+
channel: input.channel,
|
|
200
|
+
text: input.summary,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { ok: true };
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Use this for graceful degradation when a credential-backed branch is genuinely optional. Build analysis recognizes literal guards such as `ctx.hasCredentialSet('slack')` and treats matching credentials as conditional instead of required. Dynamic ids are not recognized.
|
|
209
|
+
|
|
210
|
+
## Operation / Step `ctx.credentials`
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
run: async (_input, ctx) => {
|
|
214
|
+
return { apiKeyUsed: ctx.credentials.crmApi.apiKey };
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Operation / Step `ctx.workflowGlobals`
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
run: async (_input, ctx) => {
|
|
222
|
+
return { tenantId: ctx.workflowGlobals.tenantId };
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Operation / Step `ctx.attempt`, `ctx.maxAttempts`, and `ctx.stepId`
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
run: async (_input, ctx) => {
|
|
230
|
+
return {
|
|
231
|
+
attempt: ctx.attempt ?? 1,
|
|
232
|
+
maxAttempts: ctx.maxAttempts ?? 1,
|
|
233
|
+
stepId: ctx.stepId ?? 'unknown',
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Use these when operation behavior depends on retry state or when you want the step id in output or logs.
|
|
239
|
+
|
|
240
|
+
## Public testing helpers
|
|
241
|
+
|
|
242
|
+
Import these from `@keystrokehq/core/vitest`:
|
|
243
|
+
|
|
244
|
+
- `keystrokeTestPlugin`
|
|
245
|
+
- `createTestRuntime`
|
|
246
|
+
- `createTestStepContext`
|
|
247
|
+
- `createMockHook`
|
|
248
|
+
|
|
249
|
+
### What they are used for
|
|
250
|
+
|
|
251
|
+
- `keystrokeTestPlugin`: Vitest plugin that wires core test setup into the test process
|
|
252
|
+
- `createTestRuntime`: build a full workflow test runtime with workflow context plus step execution metadata
|
|
253
|
+
- `createTestStepContext`: build only the operation / step context for isolated tests
|
|
254
|
+
- `createMockHook`: create an immediately resolving hook for tests that would otherwise block on `ctx.createHook(...)`
|
|
255
|
+
|
|
256
|
+
Example:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { createMockHook } from '@keystrokehq/core/vitest';
|
|
260
|
+
|
|
261
|
+
const hook = createMockHook();
|
|
262
|
+
await hook;
|
|
263
|
+
const resumeUrl = await hook.resumeUrl;
|
|
264
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Workflow Feature Map
|
|
2
|
+
|
|
3
|
+
Use only the public imports a user repo can rely on:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Operation, Step, Workflow } from '@keystrokehq/core';
|
|
7
|
+
import {
|
|
8
|
+
createMockHook,
|
|
9
|
+
createTestRuntime,
|
|
10
|
+
createTestStepContext,
|
|
11
|
+
keystrokeTestPlugin,
|
|
12
|
+
} from '@keystrokehq/core/vitest';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`Operation`, `Step`, and `Tool` are aliases for the same class. In this workflow skill, prefer `Step` for examples and explanations, but `Operation` is equally valid for shared or integration-oriented code.
|
|
16
|
+
|
|
17
|
+
## `Workflow` config fields
|
|
18
|
+
|
|
19
|
+
- `id`
|
|
20
|
+
- `name`
|
|
21
|
+
- `description`
|
|
22
|
+
- `author`
|
|
23
|
+
- `version`
|
|
24
|
+
- `tags`
|
|
25
|
+
- `input`
|
|
26
|
+
- `output`
|
|
27
|
+
- `triggers`
|
|
28
|
+
- `timeout`
|
|
29
|
+
- `retries`
|
|
30
|
+
- `workflowGlobals`
|
|
31
|
+
- `run`
|
|
32
|
+
|
|
33
|
+
## `Workflow` instance methods
|
|
34
|
+
|
|
35
|
+
- `run(input, runtimeOrContextOrOptions?)`
|
|
36
|
+
- `withTimeout(duration)`
|
|
37
|
+
- `retry(policy)`
|
|
38
|
+
- `describe()`
|
|
39
|
+
- `toManifest()`
|
|
40
|
+
|
|
41
|
+
### What they are used for
|
|
42
|
+
|
|
43
|
+
- `run(...)`: execute the workflow directly
|
|
44
|
+
- `withTimeout(...)`: create a new workflow instance with a different timeout
|
|
45
|
+
- `retry(...)`: create a new workflow instance with a different retry policy
|
|
46
|
+
- `describe()`: return a human-readable summary string
|
|
47
|
+
- `toManifest()`: return the core manifest description
|
|
48
|
+
|
|
49
|
+
Teach this distinction when it matters:
|
|
50
|
+
- `workflow.toManifest()` returns the primitive-level core manifest
|
|
51
|
+
- the build pipeline enriches that into the full built workflow manifest
|
|
52
|
+
|
|
53
|
+
## `Workflow.run` context
|
|
54
|
+
|
|
55
|
+
- `ctx.wait(duration)`
|
|
56
|
+
- `ctx.createHook(name)`
|
|
57
|
+
- `ctx.workflowGlobals`
|
|
58
|
+
- `ctx.workflowId`
|
|
59
|
+
|
|
60
|
+
## `Operation` / `Step` config fields
|
|
61
|
+
|
|
62
|
+
- `name`
|
|
63
|
+
- `description`
|
|
64
|
+
- `input`
|
|
65
|
+
- `output`
|
|
66
|
+
- `timeout`
|
|
67
|
+
- `tags`
|
|
68
|
+
- `retries`
|
|
69
|
+
- `workflowGlobals`
|
|
70
|
+
- `credentialSets`
|
|
71
|
+
- `needsApproval`
|
|
72
|
+
- `run`
|
|
73
|
+
|
|
74
|
+
## `Operation` / `Step` instance methods
|
|
75
|
+
|
|
76
|
+
- `run(input, ctx?)`
|
|
77
|
+
- `withTimeout(duration)`
|
|
78
|
+
- `retry(policy)`
|
|
79
|
+
- `configure(overrides)`
|
|
80
|
+
- `mapInput(schema, fn)`
|
|
81
|
+
- `mapOutput(schema, fn)`
|
|
82
|
+
- `describe()`
|
|
83
|
+
- `toManifest()`
|
|
84
|
+
|
|
85
|
+
### What they are used for
|
|
86
|
+
|
|
87
|
+
- `run(...)`: execute the operation directly
|
|
88
|
+
- `withTimeout(...)`: create a new operation instance with a different timeout
|
|
89
|
+
- `retry(...)`: create a new operation instance with a different retry policy
|
|
90
|
+
- `configure(...)`: create a new operation instance with updated metadata such as description, timeout, retries, tags, or approval metadata
|
|
91
|
+
- `mapInput(...)`: create a wrapper operation with a new input schema and input transformation
|
|
92
|
+
- `mapOutput(...)`: create a wrapper operation with a new output schema and output transformation
|
|
93
|
+
- `describe()`: return a human-readable summary string
|
|
94
|
+
- `toManifest()`: return the operation manifest description
|
|
95
|
+
|
|
96
|
+
## `Operation.run` / `Step.run` context
|
|
97
|
+
|
|
98
|
+
- `ctx.credentials`
|
|
99
|
+
- `ctx.workflowGlobals`
|
|
100
|
+
- `ctx.attempt`
|
|
101
|
+
- `ctx.maxAttempts`
|
|
102
|
+
- `ctx.stepId`
|
|
103
|
+
|
|
104
|
+
## Where to read next
|
|
105
|
+
|
|
106
|
+
- `patterns.md` shows workflow and operation examples
|
|
107
|
+
- `runtime-helpers.md` shows public context examples
|
|
108
|
+
- `testing.md` shows public testing examples
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Workflow Testing
|
|
2
|
+
|
|
3
|
+
Read this file when the user asks how to test a Keystroke workflow or workflow-facing operation.
|
|
4
|
+
|
|
5
|
+
Assume `helloWorkflow`, `accountSyncWorkflow`, `loadCustomer`, `paymentWebhook`, and `paymentWorkflow` are the workflow, operation, and trigger instances shown elsewhere in this skill.
|
|
6
|
+
|
|
7
|
+
## Vitest setup
|
|
8
|
+
|
|
9
|
+
`keystrokeTestPlugin()` adds the core test setup file to Vitest. Use it when you want normal `workflow.run(...)` and `step.run(...)` calls to work in tests without building custom harness code first.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { defineConfig } from 'vitest/config';
|
|
13
|
+
import { keystrokeTestPlugin } from '@keystrokehq/core/vitest';
|
|
14
|
+
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
plugins: [keystrokeTestPlugin()],
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Test a workflow directly
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { expect, test } from 'vitest';
|
|
24
|
+
|
|
25
|
+
test('returns the greeting', async () => {
|
|
26
|
+
await expect(helloWorkflow.run({ name: 'Ada' })).resolves.toEqual({
|
|
27
|
+
message: 'Hello, Ada!',
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Test a workflow with globals or hooks
|
|
33
|
+
|
|
34
|
+
`createTestRuntime()` builds both sides of the workflow test runtime:
|
|
35
|
+
|
|
36
|
+
- `runtime.context` for the workflow `ctx`
|
|
37
|
+
- `runtime.stepContext` for operation execution metadata such as credentials, retry info, and workflow globals
|
|
38
|
+
|
|
39
|
+
Use it when a workflow test needs more than plain input data.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createMockHook, createTestRuntime } from '@keystrokehq/core/vitest';
|
|
43
|
+
|
|
44
|
+
const runtime = createTestRuntime({
|
|
45
|
+
workflowGlobals: {
|
|
46
|
+
tenantId: 'tenant_123',
|
|
47
|
+
},
|
|
48
|
+
createHook: () => createMockHook(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await accountSyncWorkflow.run(
|
|
52
|
+
{ accountId: 'acct_123', waitForApproval: true },
|
|
53
|
+
runtime.context
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Test a step directly
|
|
58
|
+
|
|
59
|
+
`createTestStepContext()` is the smaller helper for operation unit tests. Use it when you want to call `step.run(...)` or `operation.run(...)` directly and provide only the runtime context values that operation reads.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createTestStepContext } from '@keystrokehq/core/vitest';
|
|
63
|
+
|
|
64
|
+
const stepContext = createTestStepContext({
|
|
65
|
+
credentials: {
|
|
66
|
+
crmApi: {
|
|
67
|
+
apiKey: 'test-key',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
workflowGlobals: {
|
|
71
|
+
tenantId: 'tenant_123',
|
|
72
|
+
},
|
|
73
|
+
attempt: 2,
|
|
74
|
+
maxAttempts: 4,
|
|
75
|
+
stepId: 'load-customer',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await loadCustomer.run({ customerId: 'cus_123' }, stepContext);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Test a trigger transform
|
|
82
|
+
|
|
83
|
+
Create a bound trigger by calling the trigger with `{ transform }`, then call `bound.transform?.(payload)` to verify the mapping from trigger payload to workflow input.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const bound = paymentWebhook({
|
|
87
|
+
transform: (payload) => ({
|
|
88
|
+
eventId: payload.id,
|
|
89
|
+
amount: payload.amount,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const input = await bound.transform?.(
|
|
94
|
+
{ id: 'evt_123', type: 'payment.completed', amount: 5000 }
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(input).toEqual({ eventId: 'evt_123', amount: 5000 });
|
|
98
|
+
await paymentWorkflow.run(input);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## What to test
|
|
102
|
+
|
|
103
|
+
- workflow happy path
|
|
104
|
+
- operation / step happy path
|
|
105
|
+
- waits and hooks when they affect behavior
|
|
106
|
+
- workflow globals validation
|
|
107
|
+
- credential-backed operation behavior
|
|
108
|
+
- bound trigger transforms when triggers are involved
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keystrokehq/skills",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"README.md",
|
|
12
|
+
"AGENTS-blurb.md",
|
|
13
|
+
"keystroke-workflow-authoring",
|
|
14
|
+
"keystroke-agent-authoring",
|
|
15
|
+
"keystroke-data-toolkit",
|
|
16
|
+
"keystroke-workflow-as-tool-debugging",
|
|
17
|
+
"keystroke-credential-binding",
|
|
18
|
+
"keystroke-trigger-authoring",
|
|
19
|
+
"keystroke-task-authoring",
|
|
20
|
+
"keystroke-cli-workspace"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "node -e \"process.exit(0)\"",
|
|
24
|
+
"lint": "biome check ."
|
|
25
|
+
}
|
|
26
|
+
}
|