@keystrokehq/skills 0.0.3 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +24 -15
- package/package.json +3 -10
- package/{AGENTS-blurb.md → src/_AGENTS.md} +1 -1
- package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/SKILL.md +1 -1
- package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/source-map.md +1 -1
- package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/testing.md +1 -1
- package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/SKILL.md +1 -1
- package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/references/command-map.md +3 -3
- package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/references/project-lifecycle.md +1 -1
- package/{keystroke-credential-binding → src/keystroke-credential-binding}/SKILL.md +4 -3
- package/{keystroke-credential-binding → src/keystroke-credential-binding}/references/patterns.md +14 -15
- package/{keystroke-credential-binding → src/keystroke-credential-binding}/references/source-map.md +2 -1
- package/{keystroke-task-authoring → src/keystroke-task-authoring}/SKILL.md +4 -6
- package/{keystroke-task-authoring → src/keystroke-task-authoring}/references/patterns.md +7 -7
- package/{keystroke-task-authoring → src/keystroke-task-authoring}/references/source-map.md +4 -5
- package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/SKILL.md +26 -20
- package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/patterns.md +62 -66
- package/src/keystroke-trigger-authoring/references/source-map.md +166 -0
- package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/testing.md +30 -27
- package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/SKILL.md +4 -3
- package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/patterns.md +6 -6
- package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/prebuilt-integrations.md +1 -1
- package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/runtime-helpers.md +2 -2
- package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/source-map.md +1 -1
- package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/testing.md +2 -2
- package/keystroke-agent-authoring/evals/evals.json +0 -29
- package/keystroke-cli-workspace/evals/evals.json +0 -23
- package/keystroke-credential-binding/evals/evals.json +0 -29
- package/keystroke-data-toolkit/evals/evals.json +0 -23
- package/keystroke-task-authoring/evals/evals.json +0 -23
- package/keystroke-trigger-authoring/evals/evals.json +0 -29
- package/keystroke-trigger-authoring/references/source-map.md +0 -128
- package/keystroke-workflow-as-tool-debugging/evals/evals.json +0 -23
- package/keystroke-workflow-authoring/evals/evals.json +0 -29
- /package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/messaging-gateways.md +0 -0
- /package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/patterns.md +0 -0
- /package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/prebuilt-integrations.md +0 -0
- /package/{keystroke-agent-authoring → src/keystroke-agent-authoring}/references/sandbox-and-mcp.md +0 -0
- /package/{keystroke-cli-workspace → src/keystroke-cli-workspace}/references/credentials-and-connect.md +0 -0
- /package/{keystroke-credential-binding → src/keystroke-credential-binding}/references/cli.md +0 -0
- /package/{keystroke-data-toolkit → src/keystroke-data-toolkit}/SKILL.md +0 -0
- /package/{keystroke-data-toolkit → src/keystroke-data-toolkit}/references/usage.md +0 -0
- /package/{keystroke-workflow-as-tool-debugging → src/keystroke-workflow-as-tool-debugging}/SKILL.md +0 -0
- /package/{keystroke-workflow-as-tool-debugging → src/keystroke-workflow-as-tool-debugging}/references/playbook.md +0 -0
package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/patterns.md
RENAMED
|
@@ -13,11 +13,9 @@ File layout reminder:
|
|
|
13
13
|
## Shared advanced fields
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const executionIdentityPolicy: ExecutionIdentityPolicy = {
|
|
16
|
+
const executionIdentityPolicy = {
|
|
19
17
|
subjectMode: 'requiredWhenUserProvidedCredential',
|
|
20
|
-
};
|
|
18
|
+
} as const;
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
Use `executionIdentityPolicy` when the trigger should require a subject only in specific credential cases. Use `modeDefault` when the trigger should default to a specific trigger mode such as `'subscribable'`.
|
|
@@ -29,7 +27,7 @@ import { cronTrigger } from '@keystrokehq/core';
|
|
|
29
27
|
import { z } from 'zod';
|
|
30
28
|
|
|
31
29
|
export const nightlyDigestTrigger = cronTrigger({
|
|
32
|
-
|
|
30
|
+
id: 'nightly-digest-trigger',
|
|
33
31
|
description: 'Runs every night.',
|
|
34
32
|
enabled: true,
|
|
35
33
|
modeDefault: 'subscribable',
|
|
@@ -67,37 +65,49 @@ import { webhookTrigger } from '@keystrokehq/core';
|
|
|
67
65
|
import { z } from 'zod';
|
|
68
66
|
|
|
69
67
|
export const paymentWebhook = webhookTrigger({
|
|
70
|
-
|
|
68
|
+
id: 'payment-webhook',
|
|
71
69
|
description: 'Handles payment events.',
|
|
72
70
|
enabled: true,
|
|
73
71
|
modeDefault: 'subscribable',
|
|
74
72
|
source: {
|
|
75
73
|
type: 'custom',
|
|
76
|
-
method: 'POST',
|
|
77
|
-
path: '/payments',
|
|
78
|
-
verify: async (request) => {
|
|
79
|
-
if (!request.headers['x-signature']) {
|
|
80
|
-
throw new Error('Missing signature header');
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
response: {
|
|
84
|
-
successStatus: 202,
|
|
85
|
-
successBody: {
|
|
86
|
-
accepted: true,
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
74
|
},
|
|
90
75
|
payload: z.object({
|
|
91
76
|
id: z.string(),
|
|
92
77
|
type: z.string(),
|
|
93
78
|
amount: z.number(),
|
|
94
79
|
}),
|
|
95
|
-
filter: (
|
|
96
|
-
|
|
80
|
+
filter: z.object({
|
|
81
|
+
type: z.literal('payment.completed'),
|
|
82
|
+
}),
|
|
83
|
+
idempotencyKey: {
|
|
84
|
+
from: 'payload',
|
|
85
|
+
path: 'id',
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use `source: { type: 'custom' }` when the platform should create a Keystroke-owned webhook surface. The platform owns HTTP authentication and response handling for custom webhooks; do not add `method`, `path`, `verify`, or `response`.
|
|
91
|
+
|
|
92
|
+
Use `source: { type: 'app', appRef }` when a Keystroke-managed provider app fans events out centrally:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
export const slackMessageWebhook = webhookTrigger({
|
|
96
|
+
id: 'slack-message',
|
|
97
|
+
description: 'Receives Slack message events from the platform Slack app.',
|
|
98
|
+
source: {
|
|
99
|
+
type: 'app',
|
|
100
|
+
appRef: 'slack',
|
|
101
|
+
},
|
|
102
|
+
payload: z.object({
|
|
103
|
+
type: z.literal('message'),
|
|
104
|
+
channel: z.string(),
|
|
105
|
+
text: z.string(),
|
|
106
|
+
}),
|
|
97
107
|
});
|
|
98
108
|
```
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
To parse a webhook body in tests or local checks, use `trigger.payload.parse(JSON.parse(request.rawBody))`.
|
|
101
111
|
|
|
102
112
|
## Binding a webhook trigger to a workflow with `transform`
|
|
103
113
|
|
|
@@ -128,7 +138,7 @@ import { pollingTrigger } from '@keystrokehq/core';
|
|
|
128
138
|
import { z } from 'zod';
|
|
129
139
|
|
|
130
140
|
export const orderPolling = pollingTrigger({
|
|
131
|
-
|
|
141
|
+
id: 'order-polling',
|
|
132
142
|
description: 'Polls for the newest order.',
|
|
133
143
|
enabled: true,
|
|
134
144
|
schedule: '*/15 * * * *',
|
|
@@ -144,12 +154,14 @@ export const orderPolling = pollingTrigger({
|
|
|
144
154
|
status: 'created',
|
|
145
155
|
};
|
|
146
156
|
},
|
|
147
|
-
filter: (
|
|
148
|
-
|
|
157
|
+
filter: z.object({
|
|
158
|
+
status: z.literal('created'),
|
|
159
|
+
}),
|
|
149
160
|
});
|
|
150
161
|
```
|
|
151
162
|
|
|
152
163
|
Use this when the workflow should be entered from periodic remote-state checks.
|
|
164
|
+
Polling filters are pure Zod schemas. Polling triggers do not support idempotency config.
|
|
153
165
|
|
|
154
166
|
## `pollingTrigger.parseResponse(...)`
|
|
155
167
|
|
|
@@ -162,23 +174,6 @@ const parsedResponse = orderPolling.parseResponse({
|
|
|
162
174
|
|
|
163
175
|
Use `parseResponse(...)` when you want the trigger's response schema validation without running polling logic.
|
|
164
176
|
|
|
165
|
-
## `providerTrigger`
|
|
166
|
-
|
|
167
|
-
```ts
|
|
168
|
-
import { providerTrigger } from '@keystrokehq/core';
|
|
169
|
-
|
|
170
|
-
export const githubIssueTrigger = providerTrigger({
|
|
171
|
-
name: 'GitHub Issue Trigger',
|
|
172
|
-
description: 'Receives GitHub provider events for issue activity.',
|
|
173
|
-
provider: 'github',
|
|
174
|
-
eventTypes: ['issues.opened', 'issues.edited'],
|
|
175
|
-
filter: async (event) => event.type === 'issues.opened',
|
|
176
|
-
idempotencyKey: async (event) => event.id,
|
|
177
|
-
});
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Use a provider trigger when the user is authoring around normalized provider events instead of a raw webhook or polling loop.
|
|
181
|
-
|
|
182
177
|
## Bare trigger in workflow (no transform)
|
|
183
178
|
|
|
184
179
|
```ts
|
|
@@ -202,12 +197,16 @@ triggers: [
|
|
|
202
197
|
|
|
203
198
|
Call the trigger as a function with `{ transform }` when the trigger payload and workflow input are different shapes. This returns a `BoundTrigger`.
|
|
204
199
|
|
|
205
|
-
##
|
|
200
|
+
## Narrow a trigger for per-workflow filtering
|
|
206
201
|
|
|
207
202
|
```ts
|
|
203
|
+
const largePayments = paymentWebhook.narrow({
|
|
204
|
+
id: 'large-payments',
|
|
205
|
+
filter: z.object({ amount: z.number().gt(100) }),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
208
|
triggers: [
|
|
209
|
-
|
|
210
|
-
filter: (payload) => payload.amount > 100,
|
|
209
|
+
largePayments({
|
|
211
210
|
transform: (payload) => ({
|
|
212
211
|
eventId: payload.id,
|
|
213
212
|
amount: payload.amount,
|
|
@@ -216,7 +215,7 @@ triggers: [
|
|
|
216
215
|
]
|
|
217
216
|
```
|
|
218
217
|
|
|
219
|
-
|
|
218
|
+
Filtering and idempotency live on the trigger or its `.narrow()` derivatives — never on the attachment. Bindings accept only `transform`. Use `.narrow()` to produce a filtered child trigger and attach that to the workflow.
|
|
220
219
|
|
|
221
220
|
## Task trigger note
|
|
222
221
|
|
|
@@ -227,36 +226,33 @@ Use the task skill when the user needs prompt templating, task lifecycle, or foc
|
|
|
227
226
|
## Trigger credentials
|
|
228
227
|
|
|
229
228
|
```ts
|
|
230
|
-
import { CredentialSet,
|
|
229
|
+
import { CredentialSet, pollingTrigger } from '@keystrokehq/core';
|
|
231
230
|
import { z } from 'zod';
|
|
232
231
|
|
|
233
|
-
const
|
|
234
|
-
id: '
|
|
232
|
+
const crmCredentials = new CredentialSet({
|
|
233
|
+
id: 'crmApi',
|
|
235
234
|
auth: z.object({
|
|
236
|
-
|
|
235
|
+
apiKey: z.string(),
|
|
237
236
|
}),
|
|
238
237
|
});
|
|
239
238
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
description: '
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
credentialSets: [signingCredentials],
|
|
248
|
-
verify: async (_request, ctx) => {
|
|
249
|
-
if (!ctx.credentials.webhookSigning.secret) {
|
|
250
|
-
throw new Error('Missing signing secret');
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
payload: z.object({
|
|
255
|
-
id: z.string(),
|
|
239
|
+
const recentCustomers = pollingTrigger({
|
|
240
|
+
id: 'recent-customers',
|
|
241
|
+
description: 'Polls for recently updated customers.',
|
|
242
|
+
credentialSets: [crmCredentials],
|
|
243
|
+
schedule: '5m',
|
|
244
|
+
response: z.object({
|
|
245
|
+
customers: z.array(z.object({ id: z.string() })),
|
|
256
246
|
}),
|
|
247
|
+
poll: async (ctx) => {
|
|
248
|
+
const apiKey = ctx.credentials.crmApi.apiKey;
|
|
249
|
+
return { customers: [{ id: `customer-for-${apiKey}` }] };
|
|
250
|
+
},
|
|
257
251
|
});
|
|
258
252
|
```
|
|
259
253
|
|
|
254
|
+
Polling triggers can attach credential sets and read credentials in `poll(ctx)`. Webhook triggers do not attach per-trigger credential sets in the current custom/app source model.
|
|
255
|
+
|
|
260
256
|
## `describe()` and `toManifest()`
|
|
261
257
|
|
|
262
258
|
```ts
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Trigger Feature Map
|
|
2
|
+
|
|
3
|
+
Use only the public imports a user repo can rely on:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import {
|
|
7
|
+
cronTrigger,
|
|
8
|
+
pollingTrigger,
|
|
9
|
+
webhookTrigger,
|
|
10
|
+
} from '@keystrokehq/core';
|
|
11
|
+
import type {
|
|
12
|
+
BoundTrigger,
|
|
13
|
+
CallableTrigger,
|
|
14
|
+
IdempotencyKeyConfig,
|
|
15
|
+
TriggerBindOptions,
|
|
16
|
+
TriggerManifest,
|
|
17
|
+
WebhookRequest,
|
|
18
|
+
} from '@keystrokehq/core/trigger';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When a trigger explanation also needs to talk about workflow steps or agent tools, use the terminology from the other Keystroke skills:
|
|
22
|
+
|
|
23
|
+
- `Step`, `Tool`, and `Operation` are aliases for the same `Operation` class
|
|
24
|
+
- use the workflow skill for workflow-side operation guidance
|
|
25
|
+
- use the agent skill for agent-side tool guidance
|
|
26
|
+
|
|
27
|
+
## Common trigger fields
|
|
28
|
+
|
|
29
|
+
- `id`
|
|
30
|
+
- `description`
|
|
31
|
+
- `enabled`
|
|
32
|
+
- `credentialSets`
|
|
33
|
+
- `executionIdentityPolicy`
|
|
34
|
+
- `modeDefault`
|
|
35
|
+
|
|
36
|
+
## Common trigger methods
|
|
37
|
+
|
|
38
|
+
- `describe()`
|
|
39
|
+
- `toManifest()`
|
|
40
|
+
|
|
41
|
+
## `cronTrigger` fields
|
|
42
|
+
|
|
43
|
+
- `input`
|
|
44
|
+
- `payload`
|
|
45
|
+
- `schedule`
|
|
46
|
+
- `timezone`
|
|
47
|
+
|
|
48
|
+
## `cronTrigger` instance properties
|
|
49
|
+
|
|
50
|
+
- `.id`
|
|
51
|
+
- `.payload`
|
|
52
|
+
- `.schedule`
|
|
53
|
+
- `.timezone`
|
|
54
|
+
- `.toManifest()`
|
|
55
|
+
- `.describe()`
|
|
56
|
+
|
|
57
|
+
## `webhookTrigger` fields
|
|
58
|
+
|
|
59
|
+
- `id`
|
|
60
|
+
- `description`
|
|
61
|
+
- `source`
|
|
62
|
+
- `payload`
|
|
63
|
+
- `filter`
|
|
64
|
+
- `idempotencyKey`
|
|
65
|
+
|
|
66
|
+
### `webhookTrigger.source`
|
|
67
|
+
|
|
68
|
+
- `{ type: 'custom' }` — Keystroke owns the HTTP surface and authenticates with a Keystroke-issued secret
|
|
69
|
+
- `{ type: 'app', appRef: string }` — a Keystroke-managed provider app fans out events centrally
|
|
70
|
+
|
|
71
|
+
Do not teach `method`, `path`, `verify`, or `response` on webhook triggers. Those are not part of the current public config.
|
|
72
|
+
|
|
73
|
+
## `webhookTrigger` instance properties and methods
|
|
74
|
+
|
|
75
|
+
- `.id`
|
|
76
|
+
- `.source`
|
|
77
|
+
- `.payload`
|
|
78
|
+
- `.payload.parse(data)` — validate parsed body against the payload schema
|
|
79
|
+
- `.filter` — pure Zod schema metadata, not a callback
|
|
80
|
+
- `.idempotencyKey` — declarative `IdempotencyKeyConfig`, not a callback
|
|
81
|
+
- `.toManifest()`
|
|
82
|
+
- `.describe()`
|
|
83
|
+
|
|
84
|
+
## `pollingTrigger` fields
|
|
85
|
+
|
|
86
|
+
- `id`
|
|
87
|
+
- `description`
|
|
88
|
+
- `schedule`
|
|
89
|
+
- `response`
|
|
90
|
+
- `poll`
|
|
91
|
+
- `filter`
|
|
92
|
+
|
|
93
|
+
## `pollingTrigger` instance properties and methods
|
|
94
|
+
|
|
95
|
+
- `.id`
|
|
96
|
+
- `.poll(ctx)`
|
|
97
|
+
- `.parseResponse(response)`
|
|
98
|
+
- `.filter` — pure Zod schema metadata, not a callback
|
|
99
|
+
|
|
100
|
+
Polling triggers do not support idempotency config.
|
|
101
|
+
|
|
102
|
+
## Filter schemas
|
|
103
|
+
|
|
104
|
+
Webhook and polling triggers accept `filter` as a pure Zod schema. The runtime converts it to JSON Schema for server-side evaluation before a VM hop.
|
|
105
|
+
|
|
106
|
+
Allowed filter schemas are structural schemas such as:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
z.object({
|
|
110
|
+
type: z.literal('payment.completed'),
|
|
111
|
+
amount: z.number().min(100),
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Do not use effectful Zod features in filters:
|
|
116
|
+
|
|
117
|
+
- `.refine()`
|
|
118
|
+
- `.superRefine()`
|
|
119
|
+
- `.transform()`
|
|
120
|
+
- `.preprocess()`
|
|
121
|
+
- `.pipe()`
|
|
122
|
+
- `z.custom()`
|
|
123
|
+
|
|
124
|
+
For conditional logic that cannot be expressed as a pure schema, do the check at the start of the workflow body or use an integration `mapPayload` helper.
|
|
125
|
+
|
|
126
|
+
## Webhook idempotency config
|
|
127
|
+
|
|
128
|
+
Webhook triggers accept declarative `IdempotencyKeyConfig`:
|
|
129
|
+
|
|
130
|
+
- `{ from: 'payload', path: 'eventId' }`
|
|
131
|
+
- `{ from: 'payload', strategy: 'hash' }`
|
|
132
|
+
- `{ from: 'header', name: 'x-event-id' }`
|
|
133
|
+
- `{ from: 'header', strategy: 'hash' }`
|
|
134
|
+
|
|
135
|
+
There is no function-style idempotency callback.
|
|
136
|
+
|
|
137
|
+
## Calling a trigger (creating a bound trigger)
|
|
138
|
+
|
|
139
|
+
All factory-created triggers are callable. Calling one returns a `BoundTrigger`:
|
|
140
|
+
|
|
141
|
+
- `trigger()` — bare binding (no transform)
|
|
142
|
+
- `trigger({ transform })` — bound with payload-to-input mapping
|
|
143
|
+
|
|
144
|
+
Bindings carry only `transform`. To filter events for a specific workflow, derive a child with `trigger.narrow({ id, filter })` and attach that. Binding-level `filter` and `idempotencyKey` are intentionally not part of the API.
|
|
145
|
+
|
|
146
|
+
## `BoundTrigger`
|
|
147
|
+
|
|
148
|
+
- `.isBoundTrigger` — always `true`
|
|
149
|
+
- `.trigger` — reference to the underlying trigger instance
|
|
150
|
+
- `.transform?` — payload-to-workflow-input mapping function
|
|
151
|
+
|
|
152
|
+
## Workflow `triggers` array
|
|
153
|
+
|
|
154
|
+
`Workflow({ triggers: [...] })` accepts trigger entries:
|
|
155
|
+
- a bare `CallableTrigger` (trigger payload passes through as workflow input)
|
|
156
|
+
- a `BoundTrigger` (returned by calling the trigger with options)
|
|
157
|
+
|
|
158
|
+
## Task note
|
|
159
|
+
|
|
160
|
+
- task triggers are declared inline in `Task.triggers`
|
|
161
|
+
- messaging gateways belong to agent authoring, not trigger authoring
|
|
162
|
+
|
|
163
|
+
## Where to read next
|
|
164
|
+
|
|
165
|
+
- `patterns.md` for trigger field examples
|
|
166
|
+
- `testing.md` for trigger metadata, polling, and bound trigger tests
|
package/{keystroke-trigger-authoring → src/keystroke-trigger-authoring}/references/testing.md
RENAMED
|
@@ -10,14 +10,14 @@ Assume `paymentWebhook`, `orderPolling`, and `paymentWorkflow` are the public tr
|
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import { defineConfig } from 'vitest/config';
|
|
13
|
-
import { keystrokeTestPlugin } from '@keystrokehq/testing
|
|
13
|
+
import { keystrokeTestPlugin } from '@keystrokehq/testing';
|
|
14
14
|
|
|
15
15
|
export default defineConfig({
|
|
16
16
|
plugins: [keystrokeTestPlugin()],
|
|
17
17
|
});
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
## Test webhook
|
|
20
|
+
## Test webhook parsing and declarative metadata
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
23
|
const request = {
|
|
@@ -34,28 +34,36 @@ const request = {
|
|
|
34
34
|
path: '/payments',
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const payload = paymentWebhook.payload.parse(JSON.parse(request.rawBody));
|
|
38
|
+
|
|
39
|
+
expect(payload).toEqual({
|
|
40
|
+
id: 'evt_123',
|
|
41
|
+
type: 'payment.completed',
|
|
42
|
+
amount: 5000,
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
+
const runtime = paymentWebhook.toManifest().runtime;
|
|
46
|
+
expect(runtime.filterSchema).toBeDefined();
|
|
47
|
+
expect(runtime.idempotencyConfig).toEqual({
|
|
48
|
+
from: 'payload',
|
|
49
|
+
path: 'id',
|
|
50
|
+
});
|
|
45
51
|
```
|
|
46
52
|
|
|
47
53
|
This isolates the webhook-only concerns:
|
|
48
54
|
|
|
49
|
-
- authenticity checks in `verify`
|
|
50
55
|
- parsing via `trigger.payload.parse(JSON.parse(request.rawBody))`
|
|
51
|
-
- event gating in `
|
|
56
|
+
- declarative event gating metadata in `runtime.filterSchema`
|
|
57
|
+
- declarative dedupe metadata in `runtime.idempotencyConfig`
|
|
58
|
+
|
|
59
|
+
Custom webhook HTTP authentication and responses are platform-owned in the current surface. Do not test `verify`, `method`, `path`, or `response` callbacks on authored webhook triggers.
|
|
52
60
|
|
|
53
61
|
## Test polling behavior
|
|
54
62
|
|
|
55
63
|
```ts
|
|
56
64
|
const response = await orderPolling.poll({
|
|
57
65
|
credentials: {},
|
|
58
|
-
|
|
66
|
+
triggerId: orderPolling.id,
|
|
59
67
|
triggerType: 'polling',
|
|
60
68
|
lastPolledAt: new Date().toISOString(),
|
|
61
69
|
lastResponse: {
|
|
@@ -65,6 +73,7 @@ const response = await orderPolling.poll({
|
|
|
65
73
|
});
|
|
66
74
|
|
|
67
75
|
const payload = orderPolling.parseResponse(response);
|
|
76
|
+
expect(orderPolling.toManifest().runtime.filterSchema).toBeDefined();
|
|
68
77
|
```
|
|
69
78
|
|
|
70
79
|
This isolates the polling-only concerns:
|
|
@@ -72,6 +81,7 @@ This isolates the polling-only concerns:
|
|
|
72
81
|
- what `poll(...)` returns
|
|
73
82
|
- how prior state affects the next poll
|
|
74
83
|
- whether the response matches the trigger schema
|
|
84
|
+
- declarative filter metadata when the polling trigger has a filter schema
|
|
75
85
|
|
|
76
86
|
## Test bound trigger transform
|
|
77
87
|
|
|
@@ -110,26 +120,19 @@ const workflowInput = bound.transform?.(
|
|
|
110
120
|
);
|
|
111
121
|
```
|
|
112
122
|
|
|
113
|
-
Pass the optional second argument when the transform depends on webhook request data
|
|
123
|
+
Pass the optional second argument when the transform depends on webhook request data such as headers or query params. `transform` does not receive credentials. Filters and idempotency are declarative metadata, not callbacks.
|
|
114
124
|
|
|
115
125
|
## Full webhook-to-workflow test
|
|
116
126
|
|
|
117
127
|
```ts
|
|
118
128
|
it('maps a valid webhook event into workflow input', async () => {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
await paymentWebhook.verify?.(request, {
|
|
125
|
-
credentials: {},
|
|
126
|
-
triggerName: paymentWebhook.name,
|
|
127
|
-
triggerType: 'webhook',
|
|
129
|
+
const payload = paymentWebhook.payload.parse({
|
|
130
|
+
id: 'evt_123',
|
|
131
|
+
type: 'payment.completed',
|
|
132
|
+
amount: 5000,
|
|
128
133
|
});
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
const passed = paymentWebhook.filter?.(payload, request);
|
|
132
|
-
expect(passed).toBe(true);
|
|
135
|
+
expect(paymentWebhook.toManifest().runtime.filterSchema).toBeDefined();
|
|
133
136
|
|
|
134
137
|
const bound = paymentWebhook({
|
|
135
138
|
transform: (p) => ({ eventId: p.id, amount: p.amount }),
|
|
@@ -141,8 +144,8 @@ it('maps a valid webhook event into workflow input', async () => {
|
|
|
141
144
|
|
|
142
145
|
## What to validate
|
|
143
146
|
|
|
144
|
-
- webhook
|
|
145
|
-
- filter
|
|
146
|
-
- idempotency
|
|
147
|
+
- webhook payload parsing
|
|
148
|
+
- declarative filter metadata
|
|
149
|
+
- webhook idempotency config when defined
|
|
147
150
|
- polling response shape
|
|
148
151
|
- transform correctness from trigger payload to workflow input
|
|
@@ -93,6 +93,7 @@ Teach this mental model clearly:
|
|
|
93
93
|
- `Workflow.run(...)` coordinates steps, child workflows, waits, hooks, and agents
|
|
94
94
|
- `Step.run(...)` does low-level operational work and returns typed output
|
|
95
95
|
- triggers are listed in `Workflow({ triggers: [...] })`; call a trigger with `{ transform }` to bind payload mapping
|
|
96
|
+
- trigger filters are declarative Zod schemas on the trigger or on `.narrow({ id, filter })`, not workflow attachment callbacks
|
|
96
97
|
- tasks are different: use `Task` when the job is “trigger -> prompt -> agent run”
|
|
97
98
|
|
|
98
99
|
`Step`, `Tool`, and `Operation` are the same class. In this skill, default to `Step` because that matches workflow author language.
|
|
@@ -123,7 +124,7 @@ Teach these rules:
|
|
|
123
124
|
- waits and hooks
|
|
124
125
|
5. Put operational work in steps, shared operations, or agents.
|
|
125
126
|
6. Keep each exported primitive in its own typed file.
|
|
126
|
-
7. Finish with tests using `@keystrokehq/testing
|
|
127
|
+
7. Finish with tests using `@keystrokehq/testing`.
|
|
127
128
|
|
|
128
129
|
## Workflow rules
|
|
129
130
|
|
|
@@ -136,7 +137,7 @@ Teach these rules:
|
|
|
136
137
|
- Use `workflowGlobals` for typed workflow-wide runtime values.
|
|
137
138
|
- Use `CredentialSet` for secrets and integration auth.
|
|
138
139
|
- Do not use `process.env` in authored workflow or step code.
|
|
139
|
-
- Follow Zod v4 syntax in examples and authored code. See `../../../.agents/
|
|
140
|
+
- Follow Zod v4 syntax in examples and authored code. See `../../../.agents/skills/zod-4/SKILL.md` for deep schema guidance.
|
|
140
141
|
- Workflows can be registered as agent tools. Sync workflows return inline results; suspending workflows yield and resume later.
|
|
141
142
|
- Add `largeResultMode: 'ref'` when a workflow tool may return large data; agents inspect refs with `describe_ref`, `read_ref`, and `slice_ref`.
|
|
142
143
|
- Add `midSessionSnapshot: true` only for workflow tools that have measured benefit from preserving mid-tool-call reasoning state. The default turn-boundary yield path is simpler and should remain the default.
|
|
@@ -202,7 +203,7 @@ keystroke workflows run <authoredWorkflowId> --input '{}' --wait
|
|
|
202
203
|
keystroke workflows run <authoredWorkflowId> --input '{}' --follow
|
|
203
204
|
```
|
|
204
205
|
|
|
205
|
-
`workflows run` executes the current deployed snapshot. It is different from `workflows
|
|
206
|
+
`workflows run` executes the current deployed snapshot. It is different from `workflows test`, which builds local code and runs a temporary artifact.
|
|
206
207
|
|
|
207
208
|
To explicitly start a workflow via API:
|
|
208
209
|
- **Endpoint**: `POST /api/v1/workflows/execute`
|
package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/patterns.md
RENAMED
|
@@ -48,21 +48,21 @@ import { z } from 'zod';
|
|
|
48
48
|
const PayloadSchema = z.object({ accountId: z.string() });
|
|
49
49
|
|
|
50
50
|
const accountCron = cronTrigger({
|
|
51
|
-
|
|
51
|
+
id: 'nightly-account-sync',
|
|
52
|
+
description: 'Runs the account sync every night.',
|
|
52
53
|
schedule: '0 0 * * *',
|
|
53
54
|
input: PayloadSchema,
|
|
54
55
|
payload: { accountId: 'default' },
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
const accountWebhook = webhookTrigger({
|
|
58
|
-
|
|
59
|
+
id: 'account-webhook',
|
|
60
|
+
description: 'Receives account sync webhook events.',
|
|
59
61
|
source: {
|
|
60
62
|
type: 'custom',
|
|
61
|
-
method: 'POST',
|
|
62
|
-
path: '/accounts',
|
|
63
63
|
},
|
|
64
64
|
payload: z.object({ id: z.string(), action: z.string() }),
|
|
65
|
-
filter: (
|
|
65
|
+
filter: z.object({ action: z.literal('sync') }),
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
export const accountSyncWorkflow = new Workflow({
|
|
@@ -80,7 +80,7 @@ export const accountSyncWorkflow = new Workflow({
|
|
|
80
80
|
});
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Bare triggers (like `accountCron` above) pass their payload directly as workflow input. Call the trigger with `{ transform }` when the payload shape differs from the workflow input.
|
|
83
|
+
Bare triggers (like `accountCron` above) pass their payload directly as workflow input. Call the trigger with `{ transform }` when the payload shape differs from the workflow input. Put filtering on the trigger as a pure Zod schema, or use `.narrow({ id, filter })` for workflow-specific filtering.
|
|
84
84
|
|
|
85
85
|
## Workflow metadata fields
|
|
86
86
|
|
|
@@ -249,7 +249,7 @@ Common operation groups:
|
|
|
249
249
|
- search: `searchCode`, `searchIssues`, `searchRepositories`
|
|
250
250
|
- users and orgs: `getAuthenticatedUser`, `getUser`, `listOrganizations`, `listOrgMembers`
|
|
251
251
|
|
|
252
|
-
Messaging-specific credential
|
|
252
|
+
Messaging-specific credential helpers also exist in this package, but workflow trigger and conversation entry guidance belongs in the trigger and agent skills.
|
|
253
253
|
|
|
254
254
|
## `@keystrokehq/google`
|
|
255
255
|
|
|
@@ -239,7 +239,7 @@ Use these when operation behavior depends on retry state or when you want the st
|
|
|
239
239
|
|
|
240
240
|
## Public testing helpers
|
|
241
241
|
|
|
242
|
-
Import these from `@keystrokehq/testing
|
|
242
|
+
Import these from `@keystrokehq/testing`:
|
|
243
243
|
|
|
244
244
|
- `keystrokeTestPlugin`
|
|
245
245
|
- `createTestRuntime`
|
|
@@ -256,7 +256,7 @@ Import these from `@keystrokehq/testing/vitest`:
|
|
|
256
256
|
Example:
|
|
257
257
|
|
|
258
258
|
```ts
|
|
259
|
-
import { createMockHook } from '@keystrokehq/testing
|
|
259
|
+
import { createMockHook } from '@keystrokehq/testing';
|
|
260
260
|
|
|
261
261
|
const hook = createMockHook();
|
|
262
262
|
await hook;
|
package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/source-map.md
RENAMED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createTestRuntime,
|
|
10
10
|
createTestStepContext,
|
|
11
11
|
keystrokeTestPlugin,
|
|
12
|
-
} from '@keystrokehq/testing
|
|
12
|
+
} from '@keystrokehq/testing';
|
|
13
13
|
```
|
|
14
14
|
|
|
15
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.
|
package/{keystroke-workflow-authoring → src/keystroke-workflow-authoring}/references/testing.md
RENAMED
|
@@ -10,7 +10,7 @@ Assume `helloWorkflow`, `accountSyncWorkflow`, `loadCustomer`, `paymentWebhook`,
|
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import { defineConfig } from 'vitest/config';
|
|
13
|
-
import { keystrokeTestPlugin } from '@keystrokehq/testing
|
|
13
|
+
import { keystrokeTestPlugin } from '@keystrokehq/testing';
|
|
14
14
|
|
|
15
15
|
export default defineConfig({
|
|
16
16
|
plugins: [keystrokeTestPlugin()],
|
|
@@ -40,7 +40,7 @@ Use it when a workflow test needs more than plain input data.
|
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
import { createTestRuntime } from '@keystrokehq/testing';
|
|
43
|
-
import { createMockHook } from '@keystrokehq/testing
|
|
43
|
+
import { createMockHook } from '@keystrokehq/testing';
|
|
44
44
|
|
|
45
45
|
const runtime = createTestRuntime({
|
|
46
46
|
workflowGlobals: {
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"skill_name": "keystroke-agent-authoring",
|
|
3
|
-
"evals": [
|
|
4
|
-
{
|
|
5
|
-
"id": 1,
|
|
6
|
-
"prompt": "I need a Keystroke agent that can look up Slack users and DM one of them. What fields should I set up, and where do tools and credentials go?",
|
|
7
|
-
"expected_output": "Explains the canonical Keystroke agent path, the important agent fields, tool configuration, and the credential relationship between the agent and its tools.",
|
|
8
|
-
"files": []
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"id": 2,
|
|
12
|
-
"prompt": "Should this be a Step or an agent? The task is to inspect some repo files, decide what changed, and then call a couple of tools depending on what it finds.",
|
|
13
|
-
"expected_output": "Explains when an agent is appropriate, when a normal step would be better, and how workflow orchestration relates to agent scope.",
|
|
14
|
-
"files": []
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"id": 3,
|
|
18
|
-
"prompt": "Show me how to build a sandboxed Keystroke coding agent that can work in a checked-out repo and maybe use MCP later if needed.",
|
|
19
|
-
"expected_output": "Uses the public sandbox and MCP patterns, points to MCP references when needed, and keeps workflow orchestration separate.",
|
|
20
|
-
"files": []
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"id": 4,
|
|
24
|
-
"prompt": "I want my Keystroke agent to answer messages from GitHub conversations. Should I use a trigger or something else?",
|
|
25
|
-
"expected_output": "Explains that conversational entry belongs on Agent.messaging with a MessagingGateway, not on workflow triggers, and keeps the distinction between tasks, triggers, and conversations clear.",
|
|
26
|
-
"files": []
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
}
|