lua-cli 3.43.0 → 3.45.0
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 +227 -40
- package/dist/api-exports.js +474 -39
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +1065 -343
- package/dist/index.js.map +1 -1
- package/dist/workflow-builder.d.ts +9 -2
- package/dist/workflow-builder.js +238 -4
- package/dist/workflow-builder.js.map +1 -1
- package/docs/API_REFERENCE.md +1 -1
- package/docs/api/Agents.md +23 -9
- package/docs/api/Baskets.md +11 -1
- package/docs/api/LuaJob.md +117 -87
- package/docs/api/PostProcessor.md +73 -42
- package/docs/api/User.md +104 -74
- package/docs/api/Workflows.md +12 -4
- package/docs/workflows/approvals.md +2 -0
- package/docs/workflows/workspaces-and-long-steps.md +1 -1
- package/package.json +5 -5
- package/template/examples/postprocessors/modifyResponse.ts +4 -3
- package/template/package.json +1 -1
- package/template-web-app/web/src/lua/client.ts +12 -7
package/docs/api/User.md
CHANGED
|
@@ -11,6 +11,7 @@ import { User } from 'lua-cli';
|
|
|
11
11
|
## Overview
|
|
12
12
|
|
|
13
13
|
The User API allows you to:
|
|
14
|
+
|
|
14
15
|
- Retrieve user profile data
|
|
15
16
|
- Update user information
|
|
16
17
|
- Send messages to users
|
|
@@ -24,11 +25,13 @@ The User API allows you to:
|
|
|
24
25
|
Retrieves user data by userId, email, or phone.
|
|
25
26
|
|
|
26
27
|
**Signature:**
|
|
28
|
+
|
|
27
29
|
```typescript
|
|
28
30
|
async get(identifier?: string | UserLookupOptions): Promise<UserDataInstance | null>
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
**Parameters:**
|
|
34
|
+
|
|
32
35
|
- `identifier` (optional) - One of:
|
|
33
36
|
- No parameter: Returns current user's data (in tools with conversation context)
|
|
34
37
|
- `string`: User ID to retrieve
|
|
@@ -65,6 +68,7 @@ console.log(specificUser.email);
|
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
**Use Cases:**
|
|
71
|
+
|
|
68
72
|
- Fetch data for specific users in admin tools
|
|
69
73
|
- Access user information in webhooks/jobs
|
|
70
74
|
- Multi-user data operations
|
|
@@ -75,6 +79,7 @@ console.log(specificUser.email);
|
|
|
75
79
|
Look up a user by email or phone number. This is particularly useful in webhooks where you don't have the user ID but know the user's contact information.
|
|
76
80
|
|
|
77
81
|
**Signature:**
|
|
82
|
+
|
|
78
83
|
```typescript
|
|
79
84
|
async get(options: { email?: string; phone?: string }): Promise<UserDataInstance | null>
|
|
80
85
|
```
|
|
@@ -104,6 +109,7 @@ if (!user) {
|
|
|
104
109
|
```
|
|
105
110
|
|
|
106
111
|
**Use Cases:**
|
|
112
|
+
|
|
107
113
|
- Webhooks that receive user email/phone from external systems
|
|
108
114
|
- Admin tools where operators search by contact info
|
|
109
115
|
- Integration with third-party systems that don't have Lua user IDs
|
|
@@ -113,6 +119,7 @@ if (!user) {
|
|
|
113
119
|
Gets the conversation history for the current user.
|
|
114
120
|
|
|
115
121
|
**Signature:**
|
|
122
|
+
|
|
116
123
|
```typescript
|
|
117
124
|
async getChatHistory(): Promise<ChatHistoryMessage[]>
|
|
118
125
|
```
|
|
@@ -120,6 +127,7 @@ async getChatHistory(): Promise<ChatHistoryMessage[]>
|
|
|
120
127
|
**Returns:** Array of chat messages
|
|
121
128
|
|
|
122
129
|
**Example:**
|
|
130
|
+
|
|
123
131
|
```typescript
|
|
124
132
|
const history = await User.getChatHistory();
|
|
125
133
|
|
|
@@ -155,17 +163,20 @@ user.data.email = 'new@email.com';
|
|
|
155
163
|
Starting in v3.1.0, the user object includes a read-only `_luaProfile` property that provides secure access to core user identity data.
|
|
156
164
|
|
|
157
165
|
**Properties:**
|
|
166
|
+
|
|
158
167
|
- `userId` - The unique user identifier
|
|
159
168
|
- `fullName` - User's full name
|
|
160
169
|
- `mobileNumbers` - Array of mobile numbers
|
|
161
170
|
- `emailAddresses` - Array of email addresses
|
|
162
171
|
|
|
163
172
|
**Important:**
|
|
173
|
+
|
|
164
174
|
- `_luaProfile` is **read-only** - attempts to modify it are silently ignored
|
|
165
175
|
- Uses the `_lua` prefix to avoid conflicts with custom `user.profile` fields
|
|
166
176
|
- `user.userId` is deprecated in favor of `user._luaProfile.userId`
|
|
167
177
|
|
|
168
178
|
**Example:**
|
|
179
|
+
|
|
169
180
|
```typescript
|
|
170
181
|
const user = await User.get();
|
|
171
182
|
|
|
@@ -189,11 +200,13 @@ user._luaProfile.userId = 'new-id'; // Silently ignored
|
|
|
189
200
|
Updates user data.
|
|
190
201
|
|
|
191
202
|
**Parameters:**
|
|
203
|
+
|
|
192
204
|
- `data` (object) - Data to update
|
|
193
205
|
|
|
194
206
|
**Returns:** Promise<object> - Updated user data
|
|
195
207
|
|
|
196
208
|
**Example:**
|
|
209
|
+
|
|
197
210
|
```typescript
|
|
198
211
|
const user = await User.get();
|
|
199
212
|
|
|
@@ -201,11 +214,11 @@ await user.update({
|
|
|
201
214
|
preferences: {
|
|
202
215
|
theme: 'dark',
|
|
203
216
|
language: 'en',
|
|
204
|
-
notifications: true
|
|
217
|
+
notifications: true,
|
|
205
218
|
},
|
|
206
219
|
metadata: {
|
|
207
|
-
lastActive: new Date().toISOString()
|
|
208
|
-
}
|
|
220
|
+
lastActive: new Date().toISOString(),
|
|
221
|
+
},
|
|
209
222
|
});
|
|
210
223
|
```
|
|
211
224
|
|
|
@@ -213,33 +226,41 @@ await user.update({
|
|
|
213
226
|
|
|
214
227
|
Sends messages to the user.
|
|
215
228
|
|
|
229
|
+
**Where it lands:** `user.send()` targets the user's last-interaction channel (WhatsApp, Teams, SMS and so on). For a user who only chats on webchat, it writes the message to the user's base conversation thread (the `${userId}-${agentId}` root thread) and pushes it live to the webchat widget. It does **not** target a specific workspace conversation; there is no conversation or thread option.
|
|
230
|
+
|
|
216
231
|
**Parameters:**
|
|
232
|
+
|
|
217
233
|
- `messages` (ChatMessage[]) - Array of messages to send
|
|
218
234
|
|
|
219
|
-
**Returns:** Promise<boolean> - `true` only when the platform confirmed delivery, `false` otherwise (including an unconfirmed result). It never throws, so check the result instead
|
|
235
|
+
**Returns:** Promise<boolean> - `true` only when the platform confirmed delivery, `false` otherwise (including an unconfirmed result). It never throws, so a bare `try/catch` never fires: check the result instead (in a job, log it or retry on `false`). For a message that must be seen with certainty, use `User.Inbox.push`, which is receipted.
|
|
220
236
|
|
|
221
237
|
**Example:**
|
|
238
|
+
|
|
222
239
|
```typescript
|
|
223
240
|
const user = await User.get();
|
|
224
241
|
|
|
225
242
|
// Send text message
|
|
226
|
-
await user.send([
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
await user.send([
|
|
244
|
+
{
|
|
245
|
+
type: 'text',
|
|
246
|
+
text: 'Hello! Your order has shipped.',
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
230
249
|
|
|
231
250
|
// Send image
|
|
232
|
-
await user.send([
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
await user.send([
|
|
252
|
+
{
|
|
253
|
+
type: 'image',
|
|
254
|
+
imageUrl: 'https://example.com/product.jpg',
|
|
255
|
+
altText: 'Product image',
|
|
256
|
+
},
|
|
257
|
+
]);
|
|
237
258
|
|
|
238
259
|
// Send multiple messages
|
|
239
260
|
await user.send([
|
|
240
261
|
{ type: 'text', text: 'Your order summary:' },
|
|
241
262
|
{ type: 'text', text: '- Item 1: $10' },
|
|
242
|
-
{ type: 'text', text: 'Total: $10' }
|
|
263
|
+
{ type: 'text', text: 'Total: $10' },
|
|
243
264
|
]);
|
|
244
265
|
```
|
|
245
266
|
|
|
@@ -250,6 +271,7 @@ Clears all user data.
|
|
|
250
271
|
**Returns:** `Promise<{ success: boolean }>` - `{ success: true }` once the data is cleared. Throws if the clear fails.
|
|
251
272
|
|
|
252
273
|
**Example:**
|
|
274
|
+
|
|
253
275
|
```typescript
|
|
254
276
|
const user = await User.get();
|
|
255
277
|
await user.clear();
|
|
@@ -262,6 +284,7 @@ Saves current user data state.
|
|
|
262
284
|
**Returns:** Promise<boolean>
|
|
263
285
|
|
|
264
286
|
**Example:**
|
|
287
|
+
|
|
265
288
|
```typescript
|
|
266
289
|
const user = await User.get();
|
|
267
290
|
user.data.lastLogin = new Date().toISOString();
|
|
@@ -275,12 +298,13 @@ Gets chat history for this user.
|
|
|
275
298
|
**Returns:** Promise<ChatHistoryMessage[]>
|
|
276
299
|
|
|
277
300
|
**Example:**
|
|
301
|
+
|
|
278
302
|
```typescript
|
|
279
303
|
const user = await User.get();
|
|
280
304
|
const history = await user.getChatHistory();
|
|
281
305
|
|
|
282
306
|
// Analyze conversation
|
|
283
|
-
const userMessages = history.filter(m => m.role === 'user');
|
|
307
|
+
const userMessages = history.filter((m) => m.role === 'user');
|
|
284
308
|
console.log(`User sent ${userMessages.length} messages`);
|
|
285
309
|
```
|
|
286
310
|
|
|
@@ -295,32 +319,32 @@ import { z } from 'zod';
|
|
|
295
319
|
export default class UpdatePreferencesTool implements LuaTool {
|
|
296
320
|
name = 'update_preferences';
|
|
297
321
|
description = 'Update user preferences';
|
|
298
|
-
|
|
322
|
+
|
|
299
323
|
inputSchema = z.object({
|
|
300
324
|
theme: z.enum(['light', 'dark']).optional(),
|
|
301
325
|
language: z.string().optional(),
|
|
302
|
-
notifications: z.boolean().optional()
|
|
326
|
+
notifications: z.boolean().optional(),
|
|
303
327
|
});
|
|
304
|
-
|
|
328
|
+
|
|
305
329
|
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
306
330
|
const user = await User.get();
|
|
307
|
-
|
|
331
|
+
|
|
308
332
|
// Get current preferences
|
|
309
333
|
const currentPrefs = user.data.preferences || {};
|
|
310
|
-
|
|
334
|
+
|
|
311
335
|
// Merge with new preferences
|
|
312
336
|
const newPrefs = {
|
|
313
337
|
...currentPrefs,
|
|
314
|
-
...input
|
|
338
|
+
...input,
|
|
315
339
|
};
|
|
316
|
-
|
|
340
|
+
|
|
317
341
|
// Update
|
|
318
342
|
await user.update({ preferences: newPrefs });
|
|
319
|
-
|
|
343
|
+
|
|
320
344
|
return {
|
|
321
345
|
success: true,
|
|
322
346
|
message: 'Preferences updated',
|
|
323
|
-
preferences: newPrefs
|
|
347
|
+
preferences: newPrefs,
|
|
324
348
|
};
|
|
325
349
|
}
|
|
326
350
|
}
|
|
@@ -335,36 +359,38 @@ import { z } from 'zod';
|
|
|
335
359
|
export default class NotifyUserTool implements LuaTool {
|
|
336
360
|
name = 'notify_user';
|
|
337
361
|
description = 'Send a notification to the user';
|
|
338
|
-
|
|
362
|
+
|
|
339
363
|
inputSchema = z.object({
|
|
340
364
|
title: z.string(),
|
|
341
365
|
message: z.string(),
|
|
342
|
-
urgent: z.boolean().optional()
|
|
366
|
+
urgent: z.boolean().optional(),
|
|
343
367
|
});
|
|
344
|
-
|
|
368
|
+
|
|
345
369
|
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
346
370
|
const user = await User.get();
|
|
347
|
-
|
|
371
|
+
|
|
348
372
|
const prefix = input.urgent ? '🚨 URGENT: ' : '📬 ';
|
|
349
373
|
const fullMessage = `${prefix}${input.title}\n\n${input.message}`;
|
|
350
|
-
|
|
351
|
-
await user.send([
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
374
|
+
|
|
375
|
+
await user.send([
|
|
376
|
+
{
|
|
377
|
+
type: 'text',
|
|
378
|
+
text: fullMessage,
|
|
379
|
+
},
|
|
380
|
+
]);
|
|
381
|
+
|
|
356
382
|
// Log the notification
|
|
357
383
|
await user.update({
|
|
358
384
|
lastNotification: {
|
|
359
385
|
title: input.title,
|
|
360
386
|
sentAt: new Date().toISOString(),
|
|
361
|
-
urgent: input.urgent || false
|
|
362
|
-
}
|
|
387
|
+
urgent: input.urgent || false,
|
|
388
|
+
},
|
|
363
389
|
});
|
|
364
|
-
|
|
390
|
+
|
|
365
391
|
return {
|
|
366
392
|
success: true,
|
|
367
|
-
message: 'Notification sent'
|
|
393
|
+
message: 'Notification sent',
|
|
368
394
|
};
|
|
369
395
|
}
|
|
370
396
|
}
|
|
@@ -379,35 +405,31 @@ import { z } from 'zod';
|
|
|
379
405
|
export default class ChatAnalyticsTool implements LuaTool {
|
|
380
406
|
name = 'analyze_conversation';
|
|
381
407
|
description = 'Analyze the conversation history';
|
|
382
|
-
|
|
408
|
+
|
|
383
409
|
inputSchema = z.object({});
|
|
384
|
-
|
|
410
|
+
|
|
385
411
|
async execute(input: z.infer<typeof this.inputSchema>) {
|
|
386
412
|
const history = await User.getChatHistory();
|
|
387
|
-
|
|
413
|
+
|
|
388
414
|
const stats = {
|
|
389
415
|
totalMessages: history.length,
|
|
390
|
-
userMessages: history.filter(m => m.role === 'user').length,
|
|
391
|
-
assistantMessages: history.filter(m => m.role === 'assistant').length,
|
|
416
|
+
userMessages: history.filter((m) => m.role === 'user').length,
|
|
417
|
+
assistantMessages: history.filter((m) => m.role === 'assistant').length,
|
|
392
418
|
averageLength: 0,
|
|
393
|
-
topics: [] as string[]
|
|
419
|
+
topics: [] as string[],
|
|
394
420
|
};
|
|
395
|
-
|
|
421
|
+
|
|
396
422
|
// Calculate average message length
|
|
397
|
-
const lengths = history.map(m =>
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
stats.averageLength = Math.round(
|
|
401
|
-
lengths.reduce((a, b) => a + b, 0) / lengths.length
|
|
402
|
-
);
|
|
403
|
-
|
|
423
|
+
const lengths = history.map((m) => m.content.reduce((sum, c) => sum + (c.text?.length || 0), 0));
|
|
424
|
+
stats.averageLength = Math.round(lengths.reduce((a, b) => a + b, 0) / lengths.length);
|
|
425
|
+
|
|
404
426
|
return {
|
|
405
427
|
success: true,
|
|
406
428
|
analytics: stats,
|
|
407
|
-
recentMessages: history.slice(-5).map(m => ({
|
|
429
|
+
recentMessages: history.slice(-5).map((m) => ({
|
|
408
430
|
role: m.role,
|
|
409
|
-
preview: m.content[0]?.text?.substring(0, 50) + '...'
|
|
410
|
-
}))
|
|
431
|
+
preview: m.content[0]?.text?.substring(0, 50) + '...',
|
|
432
|
+
})),
|
|
411
433
|
};
|
|
412
434
|
}
|
|
413
435
|
}
|
|
@@ -416,6 +438,7 @@ export default class ChatAnalyticsTool implements LuaTool {
|
|
|
416
438
|
## Message Types
|
|
417
439
|
|
|
418
440
|
### Text Message
|
|
441
|
+
|
|
419
442
|
```typescript
|
|
420
443
|
{
|
|
421
444
|
type: 'text',
|
|
@@ -424,6 +447,7 @@ export default class ChatAnalyticsTool implements LuaTool {
|
|
|
424
447
|
```
|
|
425
448
|
|
|
426
449
|
### Image Message
|
|
450
|
+
|
|
427
451
|
```typescript
|
|
428
452
|
{
|
|
429
453
|
type: 'image',
|
|
@@ -433,6 +457,7 @@ export default class ChatAnalyticsTool implements LuaTool {
|
|
|
433
457
|
```
|
|
434
458
|
|
|
435
459
|
### File Message
|
|
460
|
+
|
|
436
461
|
```typescript
|
|
437
462
|
{
|
|
438
463
|
type: 'file',
|
|
@@ -445,12 +470,14 @@ export default class ChatAnalyticsTool implements LuaTool {
|
|
|
445
470
|
## Best Practices
|
|
446
471
|
|
|
447
472
|
### ✅ DO
|
|
473
|
+
|
|
448
474
|
- Always handle missing user data gracefully
|
|
449
475
|
- Use `user.update()` instead of modifying and saving separately
|
|
450
476
|
- Check if data exists before accessing nested properties
|
|
451
477
|
- Send clear, actionable messages to users
|
|
452
478
|
|
|
453
479
|
### ❌ DON'T
|
|
480
|
+
|
|
454
481
|
- Don't assume user data structure
|
|
455
482
|
- Don't modify user data without validation
|
|
456
483
|
- Don't send empty or malformed messages
|
|
@@ -479,7 +506,7 @@ const user = await User.get();
|
|
|
479
506
|
await user.update({
|
|
480
507
|
name: 'John Doe',
|
|
481
508
|
email: 'john@example.com',
|
|
482
|
-
preferences: { theme: 'dark' }
|
|
509
|
+
preferences: { theme: 'dark' },
|
|
483
510
|
});
|
|
484
511
|
|
|
485
512
|
// ❌ Multiple separate updates (slower)
|
|
@@ -492,16 +519,20 @@ await user.update({ preferences: { theme: 'dark' } });
|
|
|
492
519
|
|
|
493
520
|
```typescript
|
|
494
521
|
// ✅ Clear, formatted message
|
|
495
|
-
await user.send([
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
522
|
+
await user.send([
|
|
523
|
+
{
|
|
524
|
+
type: 'text',
|
|
525
|
+
text: `Order Confirmation\n\nOrder ID: ${orderId}\nTotal: $${total}\n\nThank you for your order!`,
|
|
526
|
+
},
|
|
527
|
+
]);
|
|
499
528
|
|
|
500
529
|
// ❌ Unformatted dump
|
|
501
|
-
await user.send([
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
530
|
+
await user.send([
|
|
531
|
+
{
|
|
532
|
+
type: 'text',
|
|
533
|
+
text: JSON.stringify(orderData),
|
|
534
|
+
},
|
|
535
|
+
]);
|
|
505
536
|
```
|
|
506
537
|
|
|
507
538
|
## Webhook Example: User Lookup by Phone
|
|
@@ -514,30 +545,30 @@ import { LuaWebhook, User } from 'lua-cli';
|
|
|
514
545
|
export default new LuaWebhook({
|
|
515
546
|
name: 'update-routing-preference',
|
|
516
547
|
description: 'Update user routing preference by phone number',
|
|
517
|
-
|
|
548
|
+
|
|
518
549
|
execute: async (event) => {
|
|
519
550
|
const { phone, routingEnabled } = event.body;
|
|
520
|
-
|
|
551
|
+
|
|
521
552
|
if (!phone) {
|
|
522
553
|
return { error: 'Phone number is required' };
|
|
523
554
|
}
|
|
524
|
-
|
|
555
|
+
|
|
525
556
|
// Look up user by phone
|
|
526
557
|
const user = await User.get({ phone });
|
|
527
558
|
if (!user) {
|
|
528
559
|
return { error: 'User not found' };
|
|
529
560
|
}
|
|
530
|
-
|
|
561
|
+
|
|
531
562
|
// Update routing preference
|
|
532
563
|
user.routingEnabled = routingEnabled;
|
|
533
564
|
await user.save();
|
|
534
|
-
|
|
535
|
-
return {
|
|
536
|
-
success: true,
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
success: true,
|
|
537
568
|
userId: user._luaProfile.userId,
|
|
538
|
-
routingEnabled
|
|
569
|
+
routingEnabled,
|
|
539
570
|
};
|
|
540
|
-
}
|
|
571
|
+
},
|
|
541
572
|
});
|
|
542
573
|
```
|
|
543
574
|
|
|
@@ -564,4 +595,3 @@ interface UserLookupOptions {
|
|
|
564
595
|
|
|
565
596
|
- [User Data Tool Example](../../template/src/skills/tools/UserDataTool.ts)
|
|
566
597
|
- [Notification Patterns](../guides/NotificationPatterns.md)
|
|
567
|
-
|
package/docs/api/Workflows.md
CHANGED
|
@@ -76,7 +76,7 @@ Returns the run summary (status, counts, gate, lineage, `output` when readable).
|
|
|
76
76
|
|
|
77
77
|
### `Workflows.list(opts?)`
|
|
78
78
|
|
|
79
|
-
`opts`: `{ limit?, status?, workflow?, correlationKey?, tags?, sort? }` — `tags` are AND-ed; `sort` is one of `-createdAt | createdAt | -durationMs | durationMs`.
|
|
79
|
+
`opts`: `{ limit?, status?, workflow?, correlationKey?, tags?, sort? }` — `tags` are AND-ed; `sort` is one of `-createdAt | createdAt | -durationMs | durationMs`. `workflow` is a name or an id; an id (UUID, `wf_…`, `wfc_…`) is queried as-is. Under `lua test` a name that matches no workflow definition returns `[]`, and a failed definition lookup throws a typed `WorkflowApiError`; deployed, the bridge matches runs by recorded name or id. A `workflow` filter never widens to every run in either runtime.
|
|
80
80
|
|
|
81
81
|
### `Workflows.cancel(runId, opts?)`
|
|
82
82
|
|
|
@@ -90,6 +90,12 @@ Resumes a step suspended with `ctx.suspend(...)`. The loser of a resume race rec
|
|
|
90
90
|
|
|
91
91
|
Delivers a named signal to a run waiting on `waitForSignal(name)`. `opts.dedupeKey` makes a re-send a no-op (`{ accepted: true, duplicate: true }`). Returns `{ accepted, reason?: 'source_not_accepted' | 'duplicate' | 'parked' }`. The waiting step completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }` — read the payload as `stepResults.<id>.payload`.
|
|
92
92
|
|
|
93
|
+
### `Workflows.raiseBudget(runId, patch)`
|
|
94
|
+
|
|
95
|
+
Raises the caps of one of the calling agent's runs. `patch` is `{ maxCredits?, maxSteps?, maxJobSeconds?, maxDurationSeconds?, note? }` — at least one cap, increases only, each at or under the org cap. Returns `{ raised: true, budget, runStatus, resumed }`, or the idempotent `{ raised: false, reason: 'already_raised' }` when the same raise was already applied.
|
|
96
|
+
|
|
97
|
+
Only a run parked on its budget gate, or still queued/running, is raisable; any other state (including a terminal run, or a run past its per-run raise limit) throws `BUDGET_NOT_RAISABLE` (409). Only the run's creator/holder or an org admin may raise it (`NOT_RUN_CREATOR`, 403 — so a call from a job or workflow step with no user fails closed). A run that is not the calling agent's throws `RUN_NOT_FOUND` (404). An over-cap value throws `CAP_EXCEEDED` (400, with `cap`, `value`, `ceiling`); a malformed patch throws `VALIDATION_FAILED` (400). Behaviour is the same under `lua test` and deployed.
|
|
98
|
+
|
|
93
99
|
#### Reserved payload keys
|
|
94
100
|
|
|
95
101
|
The run ledger redacts by **key name**, not by value: a payload member whose key is one of
|
|
@@ -100,14 +106,16 @@ is persisted as `[REDACTED]` whatever it holds. A key matches when one of those
|
|
|
100
106
|
|
|
101
107
|
This is by design (a credential must never land in a run), so the placeholder is what `stepResults.<id>.payload`, the run output, `lua workflows status --steps`, exports and the chat run card show — a workflow cannot read a data field with one of these names back. Name data fields otherwise (`ticketRef`, `handle`, `code`, `verifier`). The same rule applies to every step output and suspend payload. A `waitForSignal` whose `schema` declares a property with a reserved name draws the non-blocking `signal-reserved-key` warning at push / compose (the property, the placeholder it would read as, the rename), and `lua workflows signal` warns before sending a payload that carries a reserved key (the signal is still delivered).
|
|
102
108
|
|
|
103
|
-
###
|
|
109
|
+
### Other members
|
|
104
110
|
|
|
105
|
-
`startBatch`, `signalByKey`, `
|
|
111
|
+
`startBatch`, `signalByKey`, `setGoal` and `goals.{list,get,pause,resume,close}` are part of the API surface (the member list is frozen). For `startBatch`: from an agent turn, prefer the `startWorkflowRunBatch` tool (one consent card for the whole batch, D23-r5).
|
|
106
112
|
|
|
107
113
|
## Errors
|
|
108
114
|
|
|
109
115
|
All failures throw `WorkflowApiError` with `code` (the server discriminator — `RUNS_IN_FLIGHT`, `SUSPENDED_CAP`, `WORKFLOW_NOT_FOUND`, `CONTROL_UNAVAILABLE`, …), `statusCode`, and any extra fields the server returned (for example `blockingRunId`).
|
|
110
116
|
|
|
117
|
+
- `WORKFLOW_INACTIVE` (409): the workflow was deactivated (`lua workflows deactivate`), so `Workflows.start` by name or by id starts no new run. Runs already going keep going; activate the workflow to start again.
|
|
118
|
+
|
|
111
119
|
```typescript
|
|
112
120
|
try {
|
|
113
121
|
await Workflows.start('nightly-sync', {});
|
|
@@ -130,7 +138,7 @@ lua test workflow outreach --input @leads.json \
|
|
|
130
138
|
|
|
131
139
|
- `--step-output <id>=@file|<json>` completes a step without running it (validated against its `outputSchema`) — this is how a predicate over an agent output gets both truth values under the default `--agents fake`.
|
|
132
140
|
- `--approve <id>[=@payload]` / `--deny <id>[=@reason]` pre-answer approvals; `--signal <name>=<json>` pre-supplies `waitForSignal` payloads in order.
|
|
133
|
-
- `--fixtures <dir>` replays recorded
|
|
141
|
+
- `--fixtures <dir>` replays recorded fixtures (`--record <dir>` writes each live agent reply — requires `--agents live`; tool steps never run offline, so nothing else is recorded); a missing fixture exits **5** — never a silent fake.
|
|
134
142
|
- `--from-run <runId>` seeds every completed step from a real run and restarts at the first non-completed one.
|
|
135
143
|
- `--ledger-out <file>` writes the in-memory ledger; `lua workflows replay <runId> --local` re-evaluates a real run's predicates and mappings against your compiled artifact and reports `LEDGER_DIVERGENCE`.
|
|
136
144
|
- `--park <id>` simulates a platform-fault park of a `sideEffects:'external'` step (retry / skip / complete / fail, same verbs as production); `--fast-retries` collapses retry backoff to 0; `--real-time` actually waits instead of fast-forwarding the virtual clock; `--artefacts-dir <dir>` backs `ctx.artefacts.*` on disk.
|
|
@@ -46,6 +46,8 @@ Offline, `lua workflows run --approve <id>` completes the node with `decidedBy:
|
|
|
46
46
|
|
|
47
47
|
`timeoutHours` runs on **business time** when `businessHours: { tz, calendar }` is set. `onTimeout` is a chain: each `{ escalateTo, timeoutHours }` hop re-prompts the next audience; the terminal `'deny'` (or `'cancel-run'`/`'continue'`) completes the node — e.g. `{approved:false, timedOut:true, escalations:2}`.
|
|
48
48
|
|
|
49
|
+
A numeric `timeoutHours` (on `approval` and `waitForSignal`) must be in 1..720: outside that range `lua compile` and `lua push` refuse it as `timeout-hours-out-of-range`. A `waitForSignal` signal name must match `[a-zA-Z0-9_.-]{1,64}` or it is refused as `signal-name-invalid`. The run's own wall (7 days by default) still caps the live deadline, so a window longer than the run wall is clamped at runtime (the `deadline-clamped` warning).
|
|
50
|
+
|
|
49
51
|
## Reminders
|
|
50
52
|
|
|
51
53
|
`remindAfterHours: 24` re-sends the approval push to the approvers who still have to decide, every 24 hours while the approval is pending: at most **3 reminders** per ask (an escalation hop is a new ask, so its count starts again), and never at or after the deadline. The reminder replaces the earlier notification in place (same collapse key) and reads "Reminder: …". It takes a plain number of hours in `1..720` (no binding); anything else is refused as `remind-after-invalid`, and a value at or above the first deadline pushes with the `remind-after-exceeds-timeout` warning, because it can never fire. The first deadline is the first escalation hop's `timeoutHours` when `onTimeout` starts with a hop, otherwise `timeoutHours` (168 hours when you leave it out). Reminders ride the approval **push**, so they reach the approvers an agent's approval routing names (shared-agent approvers); an approval with no routing gets the inbox row and the channel fallback but no push, and so no reminder. They are sent on the platform's sweep cadence, so a reminder can arrive a few minutes after it is due, and quiet hours and do-not-disturb drop it like any other push.
|
|
@@ -8,7 +8,7 @@ _Source of truth: workflows-spec 05 §5.17 (D19-r1/r2). This page is the develop
|
|
|
8
8
|
|
|
9
9
|
`workspace` on `createWorkflow` declares the checkout: `{ kind:'git', repo, ref, credentialsRef, sizeGb?, verify? }` or `{ kind:'empty' }`. Steps mount it with `workspace: { mount: 'rw'|'ro', isolation?: 'worktree' }` and read `ctx.workspace = { root, mount, branch, headSha, isolation, baseSha?, arm?, backend? }` — `root` is the absolute directory of the checkout (`/workspace`); `baseSha` is the commit the run's base ref resolved to at provision (`headSha` is where this step's checkout is), `arm` the worktree arm id under `isolation:'worktree'`, `backend` the volume backend — the three are present when the pod hands the run's stamps to the step and absent otherwise (an `empty` workspace, a shared mount). The pre-3.33 name `path` is served by the Job pod as a deprecated alias for one minor (it warns once per step) and then removed; new code reads `root`.
|
|
10
10
|
|
|
11
|
-
`ttlHours` is how long a released or held volume is kept before it expires: 72 h by default, 24 h for a completed `keepArtefacts` volume. The platform has a **168 h** ceiling on it, a platform setting that applies once the platform enables it: a larger value is then refused at `lua push` with `workspace-ttl-exceeds-cap` (a non-positive or non-numeric one with `workspace-ttl-invalid`) and held volumes are clamped to 168 h. Until then `lua compile` does not check it, so do not rely on a long hold.
|
|
11
|
+
`ttlHours` is how long a released or held volume is kept before it expires: 72 h by default, 24 h for a completed `keepArtefacts` volume. The platform has a **168 h** ceiling on it, a platform setting that applies once the platform enables it: a larger value is then refused at `lua push` — and likewise at compose and dynamic-definition validation — with `workspace-ttl-exceeds-cap` (a non-positive or non-numeric one with `workspace-ttl-invalid`) and held volumes are clamped to 168 h. Until then `lua compile` does not check it, so do not rely on a long hold.
|
|
12
12
|
|
|
13
13
|
`credentialsRef` is a connection id **or a key you declare under `connections: [{ key, integrationType }]`** — declare a key; it resolves against the owner agent's own connections on any agent at run time (see [Git credentials](./git-credentials.md)), so the definition never freezes a production id.
|
|
14
14
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lua-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.45.0",
|
|
4
4
|
"description": "Build, test, and deploy AI agents with custom tools, webhooks, and scheduled jobs. Features LuaAgent unified configuration, streaming chat, and batch deployment.",
|
|
5
5
|
"readmeFilename": "README.md",
|
|
6
6
|
"main": "dist/api-exports.js",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"dotenv": "^17.2.3",
|
|
88
88
|
"esbuild": "^0.28.1",
|
|
89
89
|
"inquirer": "^12.9.6",
|
|
90
|
-
"js-yaml": "^4.
|
|
90
|
+
"js-yaml": "^4.3.2",
|
|
91
91
|
"open": "^10.1.0",
|
|
92
92
|
"posthog-node": "^5.26.0",
|
|
93
93
|
"ts-evaluator": "^2.0.0",
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"stripe": "^19.2.0",
|
|
115
115
|
"ts-node": "^10.9.2",
|
|
116
116
|
"tsup": "^8.5.1",
|
|
117
|
-
"@lua/
|
|
117
|
+
"@lua/sandbox-runtime": "0.0.1",
|
|
118
118
|
"@lua/shared-sandbox": "0.0.1",
|
|
119
|
+
"@lua/shared-source-sync": "0.0.1",
|
|
119
120
|
"@lua/shared-types": "0.0.1",
|
|
120
|
-
"@lua/workflow-graph": "0.0.1"
|
|
121
|
-
"@lua/sandbox-runtime": "0.0.1"
|
|
121
|
+
"@lua/workflow-graph": "0.0.1"
|
|
122
122
|
},
|
|
123
123
|
"scripts": {
|
|
124
124
|
"clean": "rm -rf dist temp",
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { PostProcessor, UserDataInstance } from 'lua-cli';
|
|
1
|
+
import { PostProcessor, UserDataInstance, PostProcessorMessage, messageText } from 'lua-cli';
|
|
2
2
|
|
|
3
3
|
const modifyResponsePostProcessor = new PostProcessor({
|
|
4
4
|
name: 'modify-response',
|
|
5
5
|
description: 'Modifies the response to the user',
|
|
6
|
-
execute: async (user: UserDataInstance, message:
|
|
6
|
+
execute: async (user: UserDataInstance, message: PostProcessorMessage, response: string, channel: string) => {
|
|
7
|
+
// `message` is a string or the message's parts; messageText() gives its text either way.
|
|
7
8
|
console.log('Modify response post processor', user, message, response, channel);
|
|
8
9
|
console.log('User data', await user.data);
|
|
9
10
|
console.log('Message', message);
|
|
10
11
|
console.log('Response', response);
|
|
11
12
|
console.log('Channel', channel);
|
|
12
13
|
if (response.includes('test')) {
|
|
13
|
-
return { modifiedResponse: message.toUpperCase() };
|
|
14
|
+
return { modifiedResponse: messageText(message).toUpperCase() };
|
|
14
15
|
}
|
|
15
16
|
return { modifiedResponse: response };
|
|
16
17
|
},
|
package/template/package.json
CHANGED
|
@@ -244,18 +244,23 @@ class LuaAppClient {
|
|
|
244
244
|
/**
|
|
245
245
|
* One post per allowed shell origin: the browser delivers it only when the
|
|
246
246
|
* framing window really has that origin, so no other page ever receives it.
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
247
|
+
*
|
|
248
|
+
* A shell on a custom scheme (lua-desktop's `tauri://localhost`) is not a
|
|
249
|
+
* valid postMessage target origin: WebKit throws "Invalid target origin",
|
|
250
|
+
* which aborted the loop and the session ("Could not load" in lua-desktop),
|
|
251
|
+
* and skipping it would leave the desktop shell without `lua:ready`.
|
|
252
|
+
* Such an origin is posted with '*': the messages carry only {type, v}, and
|
|
253
|
+
* the CSP's frame-ancestors already limits who can frame the app. Each post
|
|
254
|
+
* is isolated so one origin can never stop the others.
|
|
251
255
|
*/
|
|
252
256
|
private post(message: { type: string; v: number }): void {
|
|
253
257
|
if (window.parent === window) return;
|
|
254
|
-
|
|
258
|
+
const targets = new Set(this.hostOrigins().map((origin) => (/^https?:\/\//i.test(origin) ? origin : '*')));
|
|
259
|
+
for (const target of targets) {
|
|
255
260
|
try {
|
|
256
|
-
window.parent.postMessage(message,
|
|
261
|
+
window.parent.postMessage(message, target);
|
|
257
262
|
} catch {
|
|
258
|
-
//
|
|
263
|
+
// An origin this browser will not target: skip it, keep the rest.
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
266
|
}
|