lua-cli 3.42.0-beta.1 → 3.42.0-beta.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/dist/api-exports.d.ts +2396 -121
- package/dist/api-exports.js +17955 -11411
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +9433 -980
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +54 -54
- package/dist/workflow-builder.d.ts +16 -2
- package/dist/workflow-builder.js +5641 -316
- package/dist/workflow-builder.js.map +1 -1
- package/docs/API_REFERENCE.md +7 -1
- package/docs/api/AI.md +3 -1
- package/docs/api/Agents.md +197 -12
- package/docs/api/Data.md +192 -61
- package/docs/api/Jobs.md +2 -0
- package/docs/api/LuaWebhook.md +128 -85
- package/docs/api/LuaWorkflow.md +15 -15
- package/docs/api/Orders.md +7 -5
- package/docs/api/Products.md +1 -1
- package/docs/api/Progress.md +57 -0
- package/docs/api/Threads.md +122 -0
- package/docs/api/User.md +2 -2
- package/docs/api/Workflows.md +22 -1
- package/docs/workflows/approvals.md +4 -0
- package/docs/workflows/coding-harness.md +27 -1
- package/docs/workflows/workspaces-and-long-steps.md +3 -1
- package/package.json +3 -3
- package/scripts/generate-catalog.lib.ts +813 -0
- package/scripts/generate-catalog.ts +94 -0
- package/template/package.json +1 -1
- package/template-web-app/web/src/lua/client.ts +11 -1
package/docs/api/LuaWebhook.md
CHANGED
|
@@ -12,6 +12,7 @@ import { z } from 'zod';
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
14
14
|
Webhooks enable:
|
|
15
|
+
|
|
15
16
|
- Receive HTTP requests from external services
|
|
16
17
|
- Validate query parameters, headers, and body
|
|
17
18
|
- Process events from third-party platforms
|
|
@@ -94,11 +95,22 @@ interface LuaWebhookEvent {
|
|
|
94
95
|
/** Request body (JSON or raw data) */
|
|
95
96
|
body?: any;
|
|
96
97
|
|
|
98
|
+
/** Exact unparsed request bytes (utf8) — for verifying vendor signatures */
|
|
99
|
+
rawBody?: string;
|
|
100
|
+
|
|
97
101
|
/** ISO timestamp when the webhook was received */
|
|
98
102
|
timestamp: string;
|
|
99
103
|
}
|
|
100
104
|
```
|
|
101
105
|
|
|
106
|
+
`rawBody` is the request exactly as it arrived on the wire, as a utf8 string.
|
|
107
|
+
Vendor HMAC schemes (Stripe `t=…,v1=…`, GitHub `sha256=…`, Slack `v0:…`) are
|
|
108
|
+
computed over those bytes, and `JSON.stringify(event.body)` does not reproduce
|
|
109
|
+
them (whitespace, key order and escaping differ), so verify against `rawBody`.
|
|
110
|
+
It is present for JSON requests (`''` for an empty body) and absent for
|
|
111
|
+
form-encoded or plain-text bodies and for webhooks fired by platform events.
|
|
112
|
+
Header keys arrive lowercased. See [Verify Signatures](#verify-signatures).
|
|
113
|
+
|
|
102
114
|
## Complete Examples
|
|
103
115
|
|
|
104
116
|
### GitHub Webhook
|
|
@@ -113,22 +125,24 @@ export const githubWebhook = new LuaWebhook({
|
|
|
113
125
|
|
|
114
126
|
headerSchema: z.object({
|
|
115
127
|
'x-github-event': z.string(),
|
|
116
|
-
'x-hub-signature': z.string()
|
|
128
|
+
'x-hub-signature': z.string(),
|
|
117
129
|
}),
|
|
118
130
|
|
|
119
131
|
bodySchema: z.object({
|
|
120
132
|
repository: z.object({
|
|
121
133
|
name: z.string(),
|
|
122
|
-
full_name: z.string()
|
|
134
|
+
full_name: z.string(),
|
|
123
135
|
}),
|
|
124
136
|
pusher: z.object({
|
|
125
137
|
name: z.string(),
|
|
126
|
-
email: z.string()
|
|
138
|
+
email: z.string(),
|
|
127
139
|
}),
|
|
128
|
-
commits: z.array(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
commits: z.array(
|
|
141
|
+
z.object({
|
|
142
|
+
message: z.string(),
|
|
143
|
+
url: z.string(),
|
|
144
|
+
})
|
|
145
|
+
),
|
|
132
146
|
}),
|
|
133
147
|
|
|
134
148
|
execute: async (event) => {
|
|
@@ -138,7 +152,7 @@ export const githubWebhook = new LuaWebhook({
|
|
|
138
152
|
if (headers['x-github-event'] !== 'push') {
|
|
139
153
|
return {
|
|
140
154
|
success: false,
|
|
141
|
-
message: 'Only push events are supported'
|
|
155
|
+
message: 'Only push events are supported',
|
|
142
156
|
};
|
|
143
157
|
}
|
|
144
158
|
|
|
@@ -149,25 +163,27 @@ export const githubWebhook = new LuaWebhook({
|
|
|
149
163
|
author: body.pusher.name,
|
|
150
164
|
message: commit.message,
|
|
151
165
|
url: commit.url,
|
|
152
|
-
timestamp: event.timestamp
|
|
166
|
+
timestamp: event.timestamp,
|
|
153
167
|
});
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
// Notify relevant user
|
|
157
171
|
const user = await User.get({ email: body.pusher.email });
|
|
158
172
|
if (user) {
|
|
159
|
-
await user.send([
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
await user.send([
|
|
174
|
+
{
|
|
175
|
+
type: 'text',
|
|
176
|
+
text: `✅ ${body.commits.length} commits pushed to ${body.repository.name}`,
|
|
177
|
+
},
|
|
178
|
+
]);
|
|
163
179
|
}
|
|
164
180
|
|
|
165
181
|
return {
|
|
166
182
|
success: true,
|
|
167
183
|
commits: body.commits.length,
|
|
168
|
-
repository: body.repository.name
|
|
184
|
+
repository: body.repository.name,
|
|
169
185
|
};
|
|
170
|
-
}
|
|
186
|
+
},
|
|
171
187
|
});
|
|
172
188
|
```
|
|
173
189
|
|
|
@@ -182,7 +198,7 @@ export const stripeWebhook = new LuaWebhook({
|
|
|
182
198
|
description: 'Handles Stripe payment events',
|
|
183
199
|
|
|
184
200
|
headerSchema: z.object({
|
|
185
|
-
'stripe-signature': z.string()
|
|
201
|
+
'stripe-signature': z.string(),
|
|
186
202
|
}),
|
|
187
203
|
|
|
188
204
|
bodySchema: z.object({
|
|
@@ -193,9 +209,9 @@ export const stripeWebhook = new LuaWebhook({
|
|
|
193
209
|
amount: z.number(),
|
|
194
210
|
currency: z.string(),
|
|
195
211
|
customer: z.string().optional(),
|
|
196
|
-
metadata: z.record(z.any()).optional()
|
|
197
|
-
})
|
|
198
|
-
})
|
|
212
|
+
metadata: z.record(z.any()).optional(),
|
|
213
|
+
}),
|
|
214
|
+
}),
|
|
199
215
|
}),
|
|
200
216
|
|
|
201
217
|
execute: async (event) => {
|
|
@@ -214,23 +230,25 @@ export const stripeWebhook = new LuaWebhook({
|
|
|
214
230
|
await order.update({
|
|
215
231
|
paymentId: payment.id,
|
|
216
232
|
paidAmount: payment.amount / 100,
|
|
217
|
-
paidAt: event.timestamp
|
|
233
|
+
paidAt: event.timestamp,
|
|
218
234
|
});
|
|
219
235
|
|
|
220
236
|
// Notify customer
|
|
221
237
|
if (payment.customer) {
|
|
222
238
|
const user = await User.get(payment.customer);
|
|
223
|
-
await user.send([
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
239
|
+
await user.send([
|
|
240
|
+
{
|
|
241
|
+
type: 'text',
|
|
242
|
+
text: `✅ Payment received! Your order #${orderId} is being processed.`,
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
227
245
|
}
|
|
228
246
|
}
|
|
229
247
|
|
|
230
248
|
return {
|
|
231
249
|
success: true,
|
|
232
250
|
event: 'payment_succeeded',
|
|
233
|
-
orderId
|
|
251
|
+
orderId,
|
|
234
252
|
};
|
|
235
253
|
}
|
|
236
254
|
|
|
@@ -241,16 +259,18 @@ export const stripeWebhook = new LuaWebhook({
|
|
|
241
259
|
// Notify customer of failure
|
|
242
260
|
if (payment.customer) {
|
|
243
261
|
const user = await User.get(payment.customer);
|
|
244
|
-
await user.send([
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
262
|
+
await user.send([
|
|
263
|
+
{
|
|
264
|
+
type: 'text',
|
|
265
|
+
text: `❌ Payment failed for order #${orderId}. Please try again.`,
|
|
266
|
+
},
|
|
267
|
+
]);
|
|
248
268
|
}
|
|
249
269
|
|
|
250
270
|
return {
|
|
251
271
|
success: true,
|
|
252
272
|
event: 'payment_failed',
|
|
253
|
-
orderId
|
|
273
|
+
orderId,
|
|
254
274
|
};
|
|
255
275
|
}
|
|
256
276
|
|
|
@@ -258,10 +278,10 @@ export const stripeWebhook = new LuaWebhook({
|
|
|
258
278
|
return {
|
|
259
279
|
success: true,
|
|
260
280
|
event: body.type,
|
|
261
|
-
message: 'Event received but not handled'
|
|
281
|
+
message: 'Event received but not handled',
|
|
262
282
|
};
|
|
263
283
|
}
|
|
264
|
-
}
|
|
284
|
+
},
|
|
265
285
|
});
|
|
266
286
|
```
|
|
267
287
|
|
|
@@ -278,12 +298,14 @@ export const slackWebhook = new LuaWebhook({
|
|
|
278
298
|
bodySchema: z.object({
|
|
279
299
|
type: z.string(),
|
|
280
300
|
challenge: z.string().optional(),
|
|
281
|
-
event: z
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
301
|
+
event: z
|
|
302
|
+
.object({
|
|
303
|
+
type: z.string(),
|
|
304
|
+
user: z.string().optional(),
|
|
305
|
+
text: z.string().optional(),
|
|
306
|
+
channel: z.string(),
|
|
307
|
+
})
|
|
308
|
+
.optional(),
|
|
287
309
|
}),
|
|
288
310
|
|
|
289
311
|
execute: async (event) => {
|
|
@@ -306,10 +328,9 @@ export const slackWebhook = new LuaWebhook({
|
|
|
306
328
|
// Process user message
|
|
307
329
|
if (slackEvent.type === 'message' && slackEvent.text) {
|
|
308
330
|
// Generate AI response
|
|
309
|
-
const response = await AI.generate(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
);
|
|
331
|
+
const response = await AI.generate('You are a helpful Slack bot assistant.', [
|
|
332
|
+
{ type: 'text', text: slackEvent.text },
|
|
333
|
+
]);
|
|
313
334
|
|
|
314
335
|
// Store interaction
|
|
315
336
|
await Data.create('slack_messages', {
|
|
@@ -317,20 +338,20 @@ export const slackWebhook = new LuaWebhook({
|
|
|
317
338
|
channel: slackEvent.channel,
|
|
318
339
|
message: slackEvent.text,
|
|
319
340
|
response,
|
|
320
|
-
timestamp: event.timestamp
|
|
341
|
+
timestamp: event.timestamp,
|
|
321
342
|
});
|
|
322
343
|
|
|
323
344
|
// TODO: Send response back to Slack using Slack API
|
|
324
345
|
|
|
325
346
|
return {
|
|
326
347
|
success: true,
|
|
327
|
-
response
|
|
348
|
+
response,
|
|
328
349
|
};
|
|
329
350
|
}
|
|
330
351
|
}
|
|
331
352
|
|
|
332
353
|
return { success: true, message: 'Event received' };
|
|
333
|
-
}
|
|
354
|
+
},
|
|
334
355
|
});
|
|
335
356
|
```
|
|
336
357
|
|
|
@@ -345,7 +366,7 @@ export const contactFormWebhook = new LuaWebhook({
|
|
|
345
366
|
description: 'Handles contact form submissions',
|
|
346
367
|
|
|
347
368
|
querySchema: z.object({
|
|
348
|
-
source: z.enum(['website', 'mobile_app']).optional()
|
|
369
|
+
source: z.enum(['website', 'mobile_app']).optional(),
|
|
349
370
|
}),
|
|
350
371
|
|
|
351
372
|
bodySchema: z.object({
|
|
@@ -354,7 +375,7 @@ export const contactFormWebhook = new LuaWebhook({
|
|
|
354
375
|
phone: z.string().optional(),
|
|
355
376
|
subject: z.string(),
|
|
356
377
|
message: z.string().min(10),
|
|
357
|
-
consent: z.boolean()
|
|
378
|
+
consent: z.boolean(),
|
|
358
379
|
}),
|
|
359
380
|
|
|
360
381
|
execute: async (event) => {
|
|
@@ -364,7 +385,7 @@ export const contactFormWebhook = new LuaWebhook({
|
|
|
364
385
|
if (!body.consent) {
|
|
365
386
|
return {
|
|
366
387
|
success: false,
|
|
367
|
-
error: 'Consent is required'
|
|
388
|
+
error: 'Consent is required',
|
|
368
389
|
};
|
|
369
390
|
}
|
|
370
391
|
|
|
@@ -377,7 +398,7 @@ export const contactFormWebhook = new LuaWebhook({
|
|
|
377
398
|
message: body.message,
|
|
378
399
|
source: query?.source || 'unknown',
|
|
379
400
|
submittedAt: event.timestamp,
|
|
380
|
-
status: 'new'
|
|
401
|
+
status: 'new',
|
|
381
402
|
});
|
|
382
403
|
|
|
383
404
|
// Create or update user
|
|
@@ -387,22 +408,24 @@ export const contactFormWebhook = new LuaWebhook({
|
|
|
387
408
|
await user.update({
|
|
388
409
|
name: body.name,
|
|
389
410
|
email: body.email,
|
|
390
|
-
phone: body.phone
|
|
411
|
+
phone: body.phone,
|
|
391
412
|
});
|
|
392
413
|
}
|
|
393
414
|
|
|
394
415
|
// Send confirmation
|
|
395
|
-
await user.send([
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
416
|
+
await user.send([
|
|
417
|
+
{
|
|
418
|
+
type: 'text',
|
|
419
|
+
text: `Thank you for contacting us, ${body.name}! We've received your message about "${body.subject}" and will get back to you soon.`,
|
|
420
|
+
},
|
|
421
|
+
]);
|
|
399
422
|
|
|
400
423
|
return {
|
|
401
424
|
success: true,
|
|
402
425
|
submissionId: entry.id,
|
|
403
|
-
message: 'Form submitted successfully'
|
|
426
|
+
message: 'Form submitted successfully',
|
|
404
427
|
};
|
|
405
|
-
}
|
|
428
|
+
},
|
|
406
429
|
});
|
|
407
430
|
```
|
|
408
431
|
|
|
@@ -418,17 +441,17 @@ export const apiWebhook = new LuaWebhook({
|
|
|
418
441
|
|
|
419
442
|
headerSchema: z.object({
|
|
420
443
|
'x-api-key': z.string(),
|
|
421
|
-
'content-type': z.literal('application/json')
|
|
444
|
+
'content-type': z.literal('application/json'),
|
|
422
445
|
}),
|
|
423
446
|
|
|
424
447
|
querySchema: z.object({
|
|
425
448
|
action: z.enum(['create', 'update', 'delete']),
|
|
426
|
-
entity: z.string()
|
|
449
|
+
entity: z.string(),
|
|
427
450
|
}),
|
|
428
451
|
|
|
429
452
|
bodySchema: z.object({
|
|
430
453
|
id: z.string(),
|
|
431
|
-
data: z.record(z.any())
|
|
454
|
+
data: z.record(z.any()),
|
|
432
455
|
}),
|
|
433
456
|
|
|
434
457
|
execute: async (event) => {
|
|
@@ -439,7 +462,7 @@ export const apiWebhook = new LuaWebhook({
|
|
|
439
462
|
if (headers['x-api-key'] !== validApiKey) {
|
|
440
463
|
return {
|
|
441
464
|
success: false,
|
|
442
|
-
error: 'Invalid API key'
|
|
465
|
+
error: 'Invalid API key',
|
|
443
466
|
};
|
|
444
467
|
}
|
|
445
468
|
|
|
@@ -449,7 +472,7 @@ export const apiWebhook = new LuaWebhook({
|
|
|
449
472
|
await Data.create(query.entity, {
|
|
450
473
|
externalId: body.id,
|
|
451
474
|
...body.data,
|
|
452
|
-
syncedAt: event.timestamp
|
|
475
|
+
syncedAt: event.timestamp,
|
|
453
476
|
});
|
|
454
477
|
break;
|
|
455
478
|
|
|
@@ -458,7 +481,7 @@ export const apiWebhook = new LuaWebhook({
|
|
|
458
481
|
if (entries.data.length > 0) {
|
|
459
482
|
await Data.update(query.entity, entries.data[0].id, {
|
|
460
483
|
...body.data,
|
|
461
|
-
syncedAt: event.timestamp
|
|
484
|
+
syncedAt: event.timestamp,
|
|
462
485
|
});
|
|
463
486
|
}
|
|
464
487
|
break;
|
|
@@ -475,9 +498,9 @@ export const apiWebhook = new LuaWebhook({
|
|
|
475
498
|
success: true,
|
|
476
499
|
action: query.action,
|
|
477
500
|
entity: query.entity,
|
|
478
|
-
id: body.id
|
|
501
|
+
id: body.id,
|
|
479
502
|
};
|
|
480
|
-
}
|
|
503
|
+
},
|
|
481
504
|
});
|
|
482
505
|
```
|
|
483
506
|
|
|
@@ -490,6 +513,7 @@ https://api.heylua.ai/webhook/{agentId}/{webhookName}
|
|
|
490
513
|
```
|
|
491
514
|
|
|
492
515
|
Example:
|
|
516
|
+
|
|
493
517
|
```
|
|
494
518
|
https://api.heylua.ai/webhook/agent_123/github-push
|
|
495
519
|
```
|
|
@@ -551,7 +575,7 @@ export const webhook = new LuaWebhook({
|
|
|
551
575
|
description: 'Webhook with error handling',
|
|
552
576
|
|
|
553
577
|
bodySchema: z.object({
|
|
554
|
-
data: z.string()
|
|
578
|
+
data: z.string(),
|
|
555
579
|
}),
|
|
556
580
|
|
|
557
581
|
execute: async (event) => {
|
|
@@ -561,7 +585,7 @@ export const webhook = new LuaWebhook({
|
|
|
561
585
|
|
|
562
586
|
return {
|
|
563
587
|
success: true,
|
|
564
|
-
result
|
|
588
|
+
result,
|
|
565
589
|
};
|
|
566
590
|
} catch (error) {
|
|
567
591
|
// Log error
|
|
@@ -569,17 +593,17 @@ export const webhook = new LuaWebhook({
|
|
|
569
593
|
webhookName: 'robust-webhook',
|
|
570
594
|
error: error.message,
|
|
571
595
|
event: event.body,
|
|
572
|
-
timestamp: event.timestamp
|
|
596
|
+
timestamp: event.timestamp,
|
|
573
597
|
});
|
|
574
598
|
|
|
575
599
|
// Return error response
|
|
576
600
|
return {
|
|
577
601
|
success: false,
|
|
578
602
|
error: error.message,
|
|
579
|
-
retry: true // Indicate that the sender should retry
|
|
603
|
+
retry: true, // Indicate that the sender should retry
|
|
580
604
|
};
|
|
581
605
|
}
|
|
582
|
-
}
|
|
606
|
+
},
|
|
583
607
|
});
|
|
584
608
|
```
|
|
585
609
|
|
|
@@ -587,32 +611,51 @@ export const webhook = new LuaWebhook({
|
|
|
587
611
|
|
|
588
612
|
### Verify Signatures
|
|
589
613
|
|
|
614
|
+
Verify a vendor's signature in `execute` against `event.rawBody` — the exact
|
|
615
|
+
bytes the vendor signed — never against a re-serialized `event.body`:
|
|
616
|
+
|
|
590
617
|
```typescript
|
|
591
618
|
import crypto from 'crypto';
|
|
619
|
+
import { LuaWebhook, env } from 'lua-cli';
|
|
592
620
|
|
|
593
|
-
function verifyGitHubSignature(
|
|
594
|
-
|
|
595
|
-
signature
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const hmac = crypto.createHmac('sha256', secret);
|
|
599
|
-
const digest = 'sha256=' + hmac.update(body).digest('hex');
|
|
600
|
-
return crypto.timingSafeEqual(
|
|
601
|
-
Buffer.from(signature),
|
|
602
|
-
Buffer.from(digest)
|
|
603
|
-
);
|
|
621
|
+
function verifyGitHubSignature(rawBody: string, signature: string, secret: string): boolean {
|
|
622
|
+
const digest = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
|
|
623
|
+
const a = Buffer.from(signature);
|
|
624
|
+
const b = Buffer.from(digest);
|
|
625
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
604
626
|
}
|
|
627
|
+
|
|
628
|
+
export const githubPush = new LuaWebhook({
|
|
629
|
+
name: 'github-push-verified',
|
|
630
|
+
description: 'GitHub push events, signature-checked',
|
|
631
|
+
execute: async (event) => {
|
|
632
|
+
const signature = event.headers?.['x-hub-signature-256'];
|
|
633
|
+
const secret = env('GITHUB_WEBHOOK_SECRET');
|
|
634
|
+
if (!event.rawBody || !signature || !secret || !verifyGitHubSignature(event.rawBody, signature, secret)) {
|
|
635
|
+
return { success: false, error: 'invalid signature' };
|
|
636
|
+
}
|
|
637
|
+
// Signature OK — event.body is the parsed form of the same bytes.
|
|
638
|
+
return { success: true, ref: event.body.ref };
|
|
639
|
+
},
|
|
640
|
+
});
|
|
605
641
|
```
|
|
606
642
|
|
|
643
|
+
`lua test webhook` has no wire bytes, so it sets `rawBody` to the compact
|
|
644
|
+
`JSON.stringify(body)` of the test input; pass an explicit one with
|
|
645
|
+
`--input '{"body": {...}, "headers": {...}, "rawBody": "<exact bytes>"}'` to
|
|
646
|
+
exercise a signature check against a captured delivery.
|
|
647
|
+
|
|
648
|
+
To have Lua reject unsigned calls before `execute` runs, use the built-in
|
|
649
|
+
`secret` instead (see [Request Verification](#request-verification)).
|
|
650
|
+
|
|
607
651
|
### API Key Validation
|
|
608
652
|
|
|
609
653
|
```typescript
|
|
610
654
|
headerSchema: z.object({
|
|
611
|
-
'x-api-key': z.string()
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
})
|
|
655
|
+
'x-api-key': z.string().refine((key) => key === process.env.WEBHOOK_API_KEY, {
|
|
656
|
+
message: 'Invalid API key',
|
|
657
|
+
}),
|
|
658
|
+
});
|
|
616
659
|
```
|
|
617
660
|
|
|
618
661
|
## Related APIs
|
package/docs/api/LuaWorkflow.md
CHANGED
|
@@ -54,21 +54,21 @@ Config: `name`, `description?`, `inputSchema`, `outputSchema?`, `budget?` (`maxC
|
|
|
54
54
|
|
|
55
55
|
**The call site places.** Every builder call — `then`, `agentStep`, `specialistStep`, `toolStep`, `map`, `approval`, `waitForSignal`, `sleep`, `sleepUntil`, `foreach`, `parallel`, `branch`, `switch`, `dowhile`, `dountil`, `workflow` — appends **exactly one** entry to the chain where it is called. Containers take `StepRef`s: an inline step object, or a **string** naming an `agentStep`/`specialistStep`/`toolStep` declared elsewhere in the same chain — a string ref inside a container means "declare here, inside me" and does not append a second top-level entry. An id declared but never placed is `WORKFLOW_UNPLACED_STEP` (warning locally, error at push); referenced but never declared is `unknown-step-ref` at `.commit()`.
|
|
56
56
|
|
|
57
|
-
| Verb
|
|
58
|
-
|
|
|
59
|
-
| `.then(step, input?)`
|
|
60
|
-
| `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, … })`
|
|
61
|
-
| `.specialistStep(id, { role, prompt, … })`
|
|
62
|
-
| `.toolStep(id, { toolId, input? })`
|
|
63
|
-
| `.map(descriptors, { id })`
|
|
64
|
-
| `.parallel([...arms], { merge? })`
|
|
65
|
-
| `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)`
|
|
66
|
-
| `.foreach(step, { items?, concurrency?, maxItems?, chunk?, rateLimit? })`
|
|
67
|
-
| `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)`
|
|
68
|
-
| `.sleep(ms)` / `.sleepUntil(template)`
|
|
69
|
-
| `.approval(id, { title, approver, timeoutHours, onTimeout, editable?, editablePaths?, itemsPath?, … })` | Human gate — see [Approvals](../workflows/approvals.md) |
|
|
70
|
-
| `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })`
|
|
71
|
-
| `.workflow(id, ref, input?, { workspace? })`
|
|
57
|
+
| Verb | Entry |
|
|
58
|
+
| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `.then(step, input?)` | Sequential step |
|
|
60
|
+
| `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, … })` | Agent turn (`prompt` is a `template(...)`) |
|
|
61
|
+
| `.specialistStep(id, { role, prompt, … })` | Ephemeral role on the owning agent (D25) |
|
|
62
|
+
| `.toolStep(id, { toolId, input? })` | One tool call — `toolId` must name a `LuaTool` the compiler can see as a tool primitive (declared in its own module and exported, or registered on the agent); a tool object defined inline in the workflow file is not detected and fails `WORKFLOW_TOOL_REF_UNRESOLVED` at compile |
|
|
63
|
+
| `.map(descriptors, { id })` | Data reshaping — `id` is **required** once the workflow has ≥ 2 maps (`map-id-required`) |
|
|
64
|
+
| `.parallel([...arms], { merge? })` | Concurrent arms; a `[map, step]` pair is a legal arm — the step runs with the map as its own `input` (the arm node carries `input`, like a `toolStep` with one). Not a `foreach` / loop body: those receive their item / the previous output (`mapping-placement` at `.commit()`) — map the items before the container instead |
|
|
65
|
+
| `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)` | Conditional (exclusive / inclusive) |
|
|
66
|
+
| `.foreach(step, { items?, concurrency?, maxItems?, chunk?, rateLimit? })` | Fan-out over an upstream array. `items` lowers to a `<bodyId>_items` mapping ahead of the entry and takes `fromInit(path)` / `fromStep(id, path)` or an `init(...)` / `step(...).path(...)` ref (LUA-864) — the node is byte-identical whichever form you write; any other descriptor (`value`, `template`, `fromRequest`, `rows`, `fromKnowledge`, a fan-in `fromStep([...])`) is `invalid-envelope` at build: `.map({ '': ... }, { id })` before the foreach instead |
|
|
67
|
+
| `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)` | Loop |
|
|
68
|
+
| `.sleep(ms)` / `.sleepUntil(template)` | Engine-side waits — `sleepUntil` lowers to an `<id>_at` mapping + `sleepUntil{dateFrom}` (D6-r1) |
|
|
69
|
+
| `.approval(id, { title, approver, timeoutHours, onTimeout, remindAfterHours?, editable?, editablePaths?, itemsPath?, … })` | Human gate — see [Approvals](../workflows/approvals.md) |
|
|
70
|
+
| `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })` | External event via `Workflows.signal` — completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }`; payload keys named like a credential (`token`, `secret`, `apiKey`, `accessToken`, …) are persisted `[REDACTED]` (the `signal-reserved-key` warning says so at push / compose) — see [Reserved payload keys](Workflows.md#reserved-payload-keys) |
|
|
71
|
+
| `.workflow(id, ref, input?, { workspace? })` | Child run the parent waits for |
|
|
72
72
|
|
|
73
73
|
### Typed predicates
|
|
74
74
|
|
package/docs/api/Orders.md
CHANGED
|
@@ -30,14 +30,16 @@ The Orders API enables:
|
|
|
30
30
|
```typescript
|
|
31
31
|
enum OrderStatus {
|
|
32
32
|
PENDING = 'pending',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
CANCELLED = 'cancelled',
|
|
37
|
-
REFUNDED = 'refunded'
|
|
33
|
+
CONFIRMED = 'confirmed',
|
|
34
|
+
FULFILLED = 'fulfilled',
|
|
35
|
+
CANCELLED = 'cancelled'
|
|
38
36
|
}
|
|
39
37
|
```
|
|
40
38
|
|
|
39
|
+
> **Note:** the platform spells the fulfilled status `fullfilled`. `OrderStatus.FULFILLED` is sent to the platform as `fullfilled` (for both `updateStatus` and `get(status)`), and `order.common.status` reads back as `fullfilled`. Any other status value is rejected with HTTP 400, and an unknown order id answers HTTP 404; both are thrown as an `Error` carrying the server message.
|
|
40
|
+
>
|
|
41
|
+
> `Orders.getById`, `updateStatus` and `updateData` take the order `id` (the 24-character hex returned by `create`/`get`), not the `orderId` display string; an id that is not 24-hex answers HTTP 404. An unexpected platform failure on `Orders.create` answers HTTP 424 and a failure on the other calls answers HTTP 500; both are thrown as an `Error` with the generic message `Order request failed`.
|
|
42
|
+
|
|
41
43
|
## OrderInstance Methods
|
|
42
44
|
|
|
43
45
|
Once you have an order instance, you can call these methods:
|
package/docs/api/Products.md
CHANGED
|
@@ -101,7 +101,7 @@ Searches products by name or description.
|
|
|
101
101
|
|
|
102
102
|
**Parameters:**
|
|
103
103
|
- `query` (string) - Search term
|
|
104
|
-
- `limit` (number, optional) - Max results (default:
|
|
104
|
+
- `limit` (number, optional) - Max results (default: 5)
|
|
105
105
|
|
|
106
106
|
**Returns:** `Promise<ProductSearchInstance>`
|
|
107
107
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Progress API
|
|
2
|
+
|
|
3
|
+
Show live progress for a long-running tool in the chat that called it.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Progress } from 'lua-cli';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## `Progress.update(input?)`
|
|
12
|
+
|
|
13
|
+
Shows or updates ONE progress card for the current tool call. The card closes on its own when the tool returns
|
|
14
|
+
(as done, or as failed when the tool throws).
|
|
15
|
+
|
|
16
|
+
**Parameters** (all optional):
|
|
17
|
+
|
|
18
|
+
- `label` (string, up to 120 characters) — the card title. When you leave it out, the previous label is kept; the
|
|
19
|
+
first call without one uses the tool's name.
|
|
20
|
+
- `fraction` (number) — progress from 0 to 1 (clamped). Leave it out for an indeterminate bar.
|
|
21
|
+
- `phase` (string, up to 200 characters) — what the tool is doing now, e.g. `"Reading sources (3/12)"`.
|
|
22
|
+
|
|
23
|
+
**Returns:** `Promise<{ emitted: boolean }>`
|
|
24
|
+
|
|
25
|
+
- `emitted: true` — the card was shown. Calls closer together than 250 ms are coalesced: the latest one is shown.
|
|
26
|
+
- `emitted: false` — there is nothing to show it on: the chat surface cannot render progress cards (for example
|
|
27
|
+
WhatsApp, or an API call without a live stream), the tool is not running in a chat turn (jobs, webhooks,
|
|
28
|
+
triggers), the feature is not enabled for your environment, or you are running offline in `lua test`.
|
|
29
|
+
|
|
30
|
+
`Progress.update` never throws. You do not need to wrap it in `try`/`catch`.
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Progress } from 'lua-cli';
|
|
36
|
+
import { LuaTool } from 'lua-cli/skill';
|
|
37
|
+
import { z } from 'zod';
|
|
38
|
+
|
|
39
|
+
export default class ImportFilesTool implements LuaTool {
|
|
40
|
+
name = 'import_files';
|
|
41
|
+
description = 'Import the given files';
|
|
42
|
+
inputSchema = z.object({ urls: z.array(z.string()) });
|
|
43
|
+
|
|
44
|
+
async execute({ urls }: z.infer<typeof this.inputSchema>) {
|
|
45
|
+
for (let i = 0; i < urls.length; i++) {
|
|
46
|
+
await Progress.update({ label: 'Importing files', fraction: i / urls.length, phase: urls[i] });
|
|
47
|
+
await importOne(urls[i]);
|
|
48
|
+
}
|
|
49
|
+
return { imported: urls.length };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Workflow runs started from a tool
|
|
55
|
+
|
|
56
|
+
When a tool starts a workflow run with `Workflows.start(...)` during a chat turn, the run's live card appears in
|
|
57
|
+
that chat (the run's origin thread), the same card a run started by the model shows. No code is needed.
|