lua-cli 3.15.0 → 3.15.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.
Files changed (44) hide show
  1. package/dist/cli/command-definitions.d.ts.map +1 -1
  2. package/dist/cli/command-definitions.js +46 -0
  3. package/dist/cli/command-definitions.js.map +1 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +70 -83
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/push.d.ts.map +1 -1
  8. package/dist/commands/push.js +87 -11
  9. package/dist/commands/push.js.map +1 -1
  10. package/dist/commands/pushBackup.d.ts +58 -4
  11. package/dist/commands/pushBackup.d.ts.map +1 -1
  12. package/dist/commands/pushBackup.js +202 -125
  13. package/dist/commands/pushBackup.js.map +1 -1
  14. package/dist/commands/source.d.ts +72 -0
  15. package/dist/commands/source.d.ts.map +1 -0
  16. package/dist/commands/source.js +134 -0
  17. package/dist/commands/source.js.map +1 -0
  18. package/dist/compiler/plugins/agent.plugin.d.ts.map +1 -1
  19. package/dist/compiler/plugins/agent.plugin.js +2 -0
  20. package/dist/compiler/plugins/agent.plugin.js.map +1 -1
  21. package/dist/instances/user.instance.d.ts +2 -1
  22. package/dist/instances/user.instance.d.ts.map +1 -1
  23. package/dist/instances/user.instance.js.map +1 -1
  24. package/dist/interfaces/backup.d.ts +24 -0
  25. package/dist/interfaces/backup.d.ts.map +1 -1
  26. package/dist/interfaces/chat.d.ts +1 -21
  27. package/dist/interfaces/chat.d.ts.map +1 -1
  28. package/dist/types/api-contracts.d.ts +4 -3
  29. package/dist/types/api-contracts.d.ts.map +1 -1
  30. package/dist/types/yaml.types.d.ts +6 -0
  31. package/dist/types/yaml.types.d.ts.map +1 -1
  32. package/dist/utils/build-manifest-from-disk.d.ts +17 -0
  33. package/dist/utils/build-manifest-from-disk.d.ts.map +1 -0
  34. package/dist/utils/build-manifest-from-disk.js +111 -0
  35. package/dist/utils/build-manifest-from-disk.js.map +1 -0
  36. package/node_modules/@lua/shared-types/dist/index.d.mts +78 -1
  37. package/node_modules/@lua/shared-types/dist/index.d.ts +78 -1
  38. package/node_modules/@lua/shared-types/dist/index.js +74 -2
  39. package/node_modules/@lua/shared-types/dist/index.mjs +71 -1
  40. package/node_modules/@lua/shared-types/src/agent-config.types.ts +14 -0
  41. package/node_modules/@lua/shared-types/src/chat-history.types.ts +112 -0
  42. package/node_modules/@lua/shared-types/src/index.ts +1 -0
  43. package/package.json +1 -1
  44. package/template/package.json +1 -1
@@ -0,0 +1,111 @@
1
+ /**
2
+ * buildManifestFromDisk
3
+ *
4
+ * Walk a project directory, hash every file we'd consider "source", and
5
+ * return a fresh BackupManifest. Replaces the broken
6
+ * `reconcileManifestWithDisk` (in backup-helpers.ts), which only re-hashed
7
+ * files already present in the compiled `manifest.json` — so files
8
+ * written by the Builder (or any out-of-band write that bypasses
9
+ * `lua compile`) never made it into the backup.
10
+ *
11
+ * Used by `runBackupPush({ fresh: true })` so auto-backup-push after every
12
+ * primitive push captures everything currently on disk, not just what
13
+ * the most recent compile happened to know about.
14
+ */
15
+ import { readdirSync, readFileSync, statSync } from 'fs';
16
+ import { join, sep } from 'path';
17
+ import { hashContent, shouldSkipFile, shouldSkipDirectory } from '../compiler/utils/common.js';
18
+ import { calculateProjectHash } from './backup-helpers.js';
19
+ // Reuse the compiler's `shouldSkipFile` / `shouldSkipDirectory` helpers to
20
+ // keep the fresh-from-disk file set identical to the compiled-manifest path.
21
+ // Otherwise dotfiles (`.eslintrc`, `.vscode/`, `.prettierrc`), test files
22
+ // (`*.test.ts`, `*.spec.ts`, `__tests__/`), `.d.ts`, `.map`, and lockfiles
23
+ // would land in fresh manifests but not compiled ones, producing a different
24
+ // projectHash for identical content and triggering false out-of-sync flags
25
+ // every time the user toggled between the two modes.
26
+ const MAX_FILE_BYTES = 256 * 1024;
27
+ export function buildManifestFromDisk(projectPath, agentId) {
28
+ const files = [];
29
+ walk(projectPath, '', files);
30
+ files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
31
+ // Use the SAME hash + projectHash function as the compiled-manifest path
32
+ // (`prepareFileRefs` + `calculateProjectHash` in backup-helpers.ts). Otherwise
33
+ // blob dedup breaks across paths — the same file content uploaded under a
34
+ // 16-char hash via the compiled path is invisible when looked up under a
35
+ // 64-char hash via the fresh path. Same goes for projectHash: switching
36
+ // between fresh and non-fresh modes must produce the same hash for
37
+ // identical content, otherwise `isBackupOutOfSync` flags every push.
38
+ const projectFiles = files.map((f) => ({ relativePath: f.relativePath, hash: f.hash }));
39
+ const totalSize = files.reduce((acc, f) => acc + f.size, 0);
40
+ return {
41
+ agentId,
42
+ // orgId is unknown at this layer; the caller (runBackupPush) supplies
43
+ // it when sending to the server. The disk-side manifest just needs the
44
+ // shape to satisfy the type.
45
+ orgId: '',
46
+ projectHash: calculateProjectHash(projectFiles),
47
+ fileCount: files.length,
48
+ totalSize,
49
+ createdBy: 'cli',
50
+ createdAt: new Date().toISOString(),
51
+ files,
52
+ };
53
+ }
54
+ function walk(root, relPrefix, out) {
55
+ const dir = relPrefix ? join(root, relPrefix) : root;
56
+ const entries = readdirSync(dir, { withFileTypes: true });
57
+ for (const entry of entries) {
58
+ if (entry.isDirectory()) {
59
+ // Two-pass directory skip:
60
+ // - shouldSkipDirectory covers the curated build/cache list
61
+ // (node_modules / dist / dist-v2 / .git / .lua / .temp /
62
+ // coverage / .next / .turbo)
63
+ // - shouldSkipFile also rejects `__tests__/`, dotted dirs,
64
+ // and any path containing `node_modules` / `__tests__` —
65
+ // applying it to the directory NAME catches those branches
66
+ // and keeps the compiled path's exclusion set in lockstep.
67
+ if (shouldSkipDirectory(entry.name))
68
+ continue;
69
+ if (shouldSkipFile(entry.name))
70
+ continue;
71
+ walk(root, relPrefix ? `${relPrefix}${sep}${entry.name}` : entry.name, out);
72
+ continue;
73
+ }
74
+ if (!entry.isFile())
75
+ continue;
76
+ // shouldSkipFile covers dotfiles, test files, .d.ts/.map, lockfiles,
77
+ // and node_modules paths — keeping the fresh manifest file set
78
+ // identical to the compiled path's so projectHash matches across modes.
79
+ if (shouldSkipFile(entry.name))
80
+ continue;
81
+ const rel = (relPrefix ? `${relPrefix}${sep}${entry.name}` : entry.name).split(sep).join('/');
82
+ const abs = join(root, rel);
83
+ const stat = statSync(abs);
84
+ if (stat.size > MAX_FILE_BYTES)
85
+ continue;
86
+ let content;
87
+ try {
88
+ content = readFileSync(abs);
89
+ }
90
+ catch {
91
+ continue;
92
+ }
93
+ const hash = hashContent(content.toString('utf-8'));
94
+ out.push({
95
+ relativePath: rel,
96
+ hash,
97
+ size: stat.size,
98
+ type: classifyFile(rel),
99
+ });
100
+ }
101
+ }
102
+ function classifyFile(relPath) {
103
+ if (relPath.endsWith('.ts') || relPath.endsWith('.tsx') || relPath.endsWith('.js') || relPath.endsWith('.jsx')) {
104
+ return 'source';
105
+ }
106
+ if (relPath.endsWith('.yaml') || relPath.endsWith('.yml') || relPath.endsWith('.json') || relPath.endsWith('.toml')) {
107
+ return 'config';
108
+ }
109
+ return 'other';
110
+ }
111
+ //# sourceMappingURL=build-manifest-from-disk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-manifest-from-disk.js","sourceRoot":"","sources":["../../src/utils/build-manifest-from-disk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC/F,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,2EAA2E;AAC3E,6EAA6E;AAC7E,0EAA0E;AAC1E,2EAA2E;AAC3E,6EAA6E;AAC7E,2EAA2E;AAC3E,qDAAqD;AACrD,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC;AAElC,MAAM,UAAU,qBAAqB,CAAC,WAAmB,EAAE,OAAe;IACxE,MAAM,KAAK,GAAoB,EAAE,CAAC;IAClC,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAE7B,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnE,yEAAyE;IACzE,+EAA+E;IAC/E,0EAA0E;IAC1E,yEAAyE;IACzE,wEAAwE;IACxE,mEAAmE;IACnE,qEAAqE;IACrE,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACxF,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAE5D,OAAO;QACL,OAAO;QACP,sEAAsE;QACtE,uEAAuE;QACvE,6BAA6B;QAC7B,KAAK,EAAE,EAAE;QACT,WAAW,EAAE,oBAAoB,CAAC,YAAmB,CAAC;QACtD,SAAS,EAAE,KAAK,CAAC,MAAM;QACvB,SAAS;QACT,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,KAAK;KACN,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,SAAiB,EAAE,GAAoB;IACjE,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,2BAA2B;YAC3B,8DAA8D;YAC9D,6DAA6D;YAC7D,kCAAkC;YAClC,6DAA6D;YAC7D,6DAA6D;YAC7D,+DAA+D;YAC/D,+DAA+D;YAC/D,IAAI,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC9C,IAAI,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACzC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC5E,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,SAAS;QAC9B,qEAAqE;QACrE,+DAA+D;QAC/D,wEAAwE;QACxE,IAAI,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QACzC,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9F,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC5B,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,IAAI,CAAC,IAAI,GAAG,cAAc;YAAE,SAAS;QACzC,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC;YACP,YAAY,EAAE,GAAG;YACjB,IAAI;YACJ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,YAAY,CAAC,GAAG,CAAC;SACxB,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,OAAe;IACnC,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/G,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACpH,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -216,6 +216,69 @@ interface AgentInvocationOutput {
216
216
  threadId?: string;
217
217
  }
218
218
 
219
+ /**
220
+ * Wire-format types for `GET /chat/history/:agentId` and the VM-sandbox
221
+ * `User.getChatHistory()` API. Both paths return the same shape.
222
+ *
223
+ * Shared between lua-core (HTTP controller + VM devtools) and lua-cli
224
+ * (HTTP client typing for `lua test`). Also shared with lua-api's
225
+ * `thread.service.ts` (admin org-thread listing) so the parser never
226
+ * diverges across services.
227
+ */
228
+ /**
229
+ * One content part of a chat-history message.
230
+ *
231
+ * `text` parts carry plain text. Media parts (`image` / `video` / `audio`
232
+ * / `file`) carry a URL or base64 payload in `data` (or `image` / `video`
233
+ * for legacy reasons) plus a `mediaType` MIME string.
234
+ */
235
+ interface ChatHistoryContent {
236
+ type: 'text' | 'image' | 'video' | 'audio' | 'file';
237
+ text?: string;
238
+ image?: string;
239
+ video?: string;
240
+ data?: string;
241
+ mediaType?: string;
242
+ latitude?: number;
243
+ longitude?: number;
244
+ }
245
+ /**
246
+ * One message returned by chat-history endpoints.
247
+ *
248
+ * Only `user` and `assistant` roles surface — system / tool messages are
249
+ * filtered out before transport.
250
+ */
251
+ interface ChatHistoryMessage {
252
+ id: string;
253
+ role: 'user' | 'assistant';
254
+ /** ISO-8601 timestamp string. */
255
+ createdAt: string;
256
+ content: ChatHistoryContent[];
257
+ }
258
+ /**
259
+ * Strip `::: navigate …:::` blocks from a text string.
260
+ *
261
+ * Exposed because both lua-core's chat-history transform and any future
262
+ * external transform may need it independently.
263
+ */
264
+ declare function removeNavigateBlock(input: string): string;
265
+ /**
266
+ * Transform raw Mastra `MastraMessageContentV2.parts[]` into the
267
+ * `ChatHistoryContent[]` wire shape.
268
+ *
269
+ * Single source of truth for the conversion: used by lua-core's
270
+ * `toChatHistoryMessage` (HTTP + VM sandbox paths) and lua-api's
271
+ * `thread.service.ts` admin org-thread listing. Any future divergence
272
+ * starts here.
273
+ *
274
+ * Parts are deliberately typed `unknown` because callers may either pass
275
+ * Mastra v5 `MastraMessagePart` instances (lua-core, via `@mastra/core`)
276
+ * or raw JSON parsed from a SQL row (lua-api, reading `mastra_messages`
277
+ * directly). The function only inspects `.type`, `.text`, `.data`, and
278
+ * `.mimeType` via property access, so both shapes work.
279
+ */
280
+ declare function transformChatHistoryContentParts(parts: unknown[] | undefined | null): ChatHistoryContent[];
281
+
219
282
  /**
220
283
  * Sandbox contract: defines what every sandbox MUST expose.
221
284
  * Both lua-cli and lua-core test against this contract.
@@ -471,6 +534,20 @@ interface BatchingConfig {
471
534
  debounceWindowMs?: number;
472
535
  /** Maximum messages collected in a single batch. */
473
536
  maxBatchMessages?: number;
537
+ /**
538
+ * Strict sequential turn execution. When `true`, a new message arriving while a turn is
539
+ * in-flight does NOT abort the running turn — it waits in a batch until the current turn
540
+ * completes, then runs against the post-reply state. Trades latency for correctness:
541
+ * eliminates duplicate/contradictory replies and side-effect-then-no-reply failures from
542
+ * mid-tool aborts. No effect when `debounceWindowMs` is 0 (batching disabled).
543
+ *
544
+ * Most useful with the stream endpoint. Generate callers (e.g. lua-whatsapp, sync HTTP)
545
+ * may hit ingress / client timeouts on long waits regardless of backend serialization.
546
+ *
547
+ * Per-agent only — there is no platform-wide env var override (deliberate: a global
548
+ * switch would silently flip every batching-enabled agent at once). Default: `false`.
549
+ */
550
+ serializeProcessing?: boolean;
474
551
  }
475
552
  /**
476
553
  * Governance SDK configuration persisted on the SubAgent document.
@@ -526,4 +603,4 @@ interface ChatErrorChunk {
526
603
  timestamp: number;
527
604
  }
528
605
 
529
- export { AGENT_LOG_SOURCES, type AgentInvocationInput, type AgentInvocationOutput, type AgentLogMetadata, type AgentLogSource, type AiGenerateInput, type AiGenerateOutput, type AiGenerateSource, type AiGenerateToolCall, type AiGenerateToolResult, type ApprovedModelDef, type BatchingConfig, CLIENT_TOOLS_MAX, CLIENT_TOOL_PREFIX, type CachedDeviceCommand, type CachedDeviceDefinition, type CachedDeviceTrigger, type CachedDeviceTriggerDefinition, type Channel, type ChatErrorChunk, type ClientToolDef, type CommandHashData, type CommandState, type CommandStreamEntry, type DeviceCommandDefinition, type DeviceCommandMessage, type DeviceInfo, type DeviceResponseMessage, type DeviceStatus, type DeviceTriggerAckMessage, type DeviceTriggerMessage, type GovernanceConfig, type PersonaText, REQUIRED_ENUMS, REQUIRED_NESTED_APIS, REQUIRED_PLATFORM_APIS, REQUIRED_PRIMITIVE_SHIMS, REQUIRED_UTILITIES, type ResponseStreamEntry, type SkillContextText, type TriggerStreamEntry, UNSTRUCTURED_SUPPORTED_MEDIA_TYPES, aiGenerateInputFromSimplified, buildDefaultPersona, flattenPersonaText, flattenPersonaTextAll, hasPersonaTextContent, isPersonaTextObject, personaToLiteral };
606
+ export { AGENT_LOG_SOURCES, type AgentInvocationInput, type AgentInvocationOutput, type AgentLogMetadata, type AgentLogSource, type AiGenerateInput, type AiGenerateOutput, type AiGenerateSource, type AiGenerateToolCall, type AiGenerateToolResult, type ApprovedModelDef, type BatchingConfig, CLIENT_TOOLS_MAX, CLIENT_TOOL_PREFIX, type CachedDeviceCommand, type CachedDeviceDefinition, type CachedDeviceTrigger, type CachedDeviceTriggerDefinition, type Channel, type ChatErrorChunk, type ChatHistoryContent, type ChatHistoryMessage, type ClientToolDef, type CommandHashData, type CommandState, type CommandStreamEntry, type DeviceCommandDefinition, type DeviceCommandMessage, type DeviceInfo, type DeviceResponseMessage, type DeviceStatus, type DeviceTriggerAckMessage, type DeviceTriggerMessage, type GovernanceConfig, type PersonaText, REQUIRED_ENUMS, REQUIRED_NESTED_APIS, REQUIRED_PLATFORM_APIS, REQUIRED_PRIMITIVE_SHIMS, REQUIRED_UTILITIES, type ResponseStreamEntry, type SkillContextText, type TriggerStreamEntry, UNSTRUCTURED_SUPPORTED_MEDIA_TYPES, aiGenerateInputFromSimplified, buildDefaultPersona, flattenPersonaText, flattenPersonaTextAll, hasPersonaTextContent, isPersonaTextObject, personaToLiteral, removeNavigateBlock, transformChatHistoryContentParts };
@@ -216,6 +216,69 @@ interface AgentInvocationOutput {
216
216
  threadId?: string;
217
217
  }
218
218
 
219
+ /**
220
+ * Wire-format types for `GET /chat/history/:agentId` and the VM-sandbox
221
+ * `User.getChatHistory()` API. Both paths return the same shape.
222
+ *
223
+ * Shared between lua-core (HTTP controller + VM devtools) and lua-cli
224
+ * (HTTP client typing for `lua test`). Also shared with lua-api's
225
+ * `thread.service.ts` (admin org-thread listing) so the parser never
226
+ * diverges across services.
227
+ */
228
+ /**
229
+ * One content part of a chat-history message.
230
+ *
231
+ * `text` parts carry plain text. Media parts (`image` / `video` / `audio`
232
+ * / `file`) carry a URL or base64 payload in `data` (or `image` / `video`
233
+ * for legacy reasons) plus a `mediaType` MIME string.
234
+ */
235
+ interface ChatHistoryContent {
236
+ type: 'text' | 'image' | 'video' | 'audio' | 'file';
237
+ text?: string;
238
+ image?: string;
239
+ video?: string;
240
+ data?: string;
241
+ mediaType?: string;
242
+ latitude?: number;
243
+ longitude?: number;
244
+ }
245
+ /**
246
+ * One message returned by chat-history endpoints.
247
+ *
248
+ * Only `user` and `assistant` roles surface — system / tool messages are
249
+ * filtered out before transport.
250
+ */
251
+ interface ChatHistoryMessage {
252
+ id: string;
253
+ role: 'user' | 'assistant';
254
+ /** ISO-8601 timestamp string. */
255
+ createdAt: string;
256
+ content: ChatHistoryContent[];
257
+ }
258
+ /**
259
+ * Strip `::: navigate …:::` blocks from a text string.
260
+ *
261
+ * Exposed because both lua-core's chat-history transform and any future
262
+ * external transform may need it independently.
263
+ */
264
+ declare function removeNavigateBlock(input: string): string;
265
+ /**
266
+ * Transform raw Mastra `MastraMessageContentV2.parts[]` into the
267
+ * `ChatHistoryContent[]` wire shape.
268
+ *
269
+ * Single source of truth for the conversion: used by lua-core's
270
+ * `toChatHistoryMessage` (HTTP + VM sandbox paths) and lua-api's
271
+ * `thread.service.ts` admin org-thread listing. Any future divergence
272
+ * starts here.
273
+ *
274
+ * Parts are deliberately typed `unknown` because callers may either pass
275
+ * Mastra v5 `MastraMessagePart` instances (lua-core, via `@mastra/core`)
276
+ * or raw JSON parsed from a SQL row (lua-api, reading `mastra_messages`
277
+ * directly). The function only inspects `.type`, `.text`, `.data`, and
278
+ * `.mimeType` via property access, so both shapes work.
279
+ */
280
+ declare function transformChatHistoryContentParts(parts: unknown[] | undefined | null): ChatHistoryContent[];
281
+
219
282
  /**
220
283
  * Sandbox contract: defines what every sandbox MUST expose.
221
284
  * Both lua-cli and lua-core test against this contract.
@@ -471,6 +534,20 @@ interface BatchingConfig {
471
534
  debounceWindowMs?: number;
472
535
  /** Maximum messages collected in a single batch. */
473
536
  maxBatchMessages?: number;
537
+ /**
538
+ * Strict sequential turn execution. When `true`, a new message arriving while a turn is
539
+ * in-flight does NOT abort the running turn — it waits in a batch until the current turn
540
+ * completes, then runs against the post-reply state. Trades latency for correctness:
541
+ * eliminates duplicate/contradictory replies and side-effect-then-no-reply failures from
542
+ * mid-tool aborts. No effect when `debounceWindowMs` is 0 (batching disabled).
543
+ *
544
+ * Most useful with the stream endpoint. Generate callers (e.g. lua-whatsapp, sync HTTP)
545
+ * may hit ingress / client timeouts on long waits regardless of backend serialization.
546
+ *
547
+ * Per-agent only — there is no platform-wide env var override (deliberate: a global
548
+ * switch would silently flip every batching-enabled agent at once). Default: `false`.
549
+ */
550
+ serializeProcessing?: boolean;
474
551
  }
475
552
  /**
476
553
  * Governance SDK configuration persisted on the SubAgent document.
@@ -526,4 +603,4 @@ interface ChatErrorChunk {
526
603
  timestamp: number;
527
604
  }
528
605
 
529
- export { AGENT_LOG_SOURCES, type AgentInvocationInput, type AgentInvocationOutput, type AgentLogMetadata, type AgentLogSource, type AiGenerateInput, type AiGenerateOutput, type AiGenerateSource, type AiGenerateToolCall, type AiGenerateToolResult, type ApprovedModelDef, type BatchingConfig, CLIENT_TOOLS_MAX, CLIENT_TOOL_PREFIX, type CachedDeviceCommand, type CachedDeviceDefinition, type CachedDeviceTrigger, type CachedDeviceTriggerDefinition, type Channel, type ChatErrorChunk, type ClientToolDef, type CommandHashData, type CommandState, type CommandStreamEntry, type DeviceCommandDefinition, type DeviceCommandMessage, type DeviceInfo, type DeviceResponseMessage, type DeviceStatus, type DeviceTriggerAckMessage, type DeviceTriggerMessage, type GovernanceConfig, type PersonaText, REQUIRED_ENUMS, REQUIRED_NESTED_APIS, REQUIRED_PLATFORM_APIS, REQUIRED_PRIMITIVE_SHIMS, REQUIRED_UTILITIES, type ResponseStreamEntry, type SkillContextText, type TriggerStreamEntry, UNSTRUCTURED_SUPPORTED_MEDIA_TYPES, aiGenerateInputFromSimplified, buildDefaultPersona, flattenPersonaText, flattenPersonaTextAll, hasPersonaTextContent, isPersonaTextObject, personaToLiteral };
606
+ export { AGENT_LOG_SOURCES, type AgentInvocationInput, type AgentInvocationOutput, type AgentLogMetadata, type AgentLogSource, type AiGenerateInput, type AiGenerateOutput, type AiGenerateSource, type AiGenerateToolCall, type AiGenerateToolResult, type ApprovedModelDef, type BatchingConfig, CLIENT_TOOLS_MAX, CLIENT_TOOL_PREFIX, type CachedDeviceCommand, type CachedDeviceDefinition, type CachedDeviceTrigger, type CachedDeviceTriggerDefinition, type Channel, type ChatErrorChunk, type ChatHistoryContent, type ChatHistoryMessage, type ClientToolDef, type CommandHashData, type CommandState, type CommandStreamEntry, type DeviceCommandDefinition, type DeviceCommandMessage, type DeviceInfo, type DeviceResponseMessage, type DeviceStatus, type DeviceTriggerAckMessage, type DeviceTriggerMessage, type GovernanceConfig, type PersonaText, REQUIRED_ENUMS, REQUIRED_NESTED_APIS, REQUIRED_PLATFORM_APIS, REQUIRED_PRIMITIVE_SHIMS, REQUIRED_UTILITIES, type ResponseStreamEntry, type SkillContextText, type TriggerStreamEntry, UNSTRUCTURED_SUPPORTED_MEDIA_TYPES, aiGenerateInputFromSimplified, buildDefaultPersona, flattenPersonaText, flattenPersonaTextAll, hasPersonaTextContent, isPersonaTextObject, personaToLiteral, removeNavigateBlock, transformChatHistoryContentParts };
@@ -35,7 +35,9 @@ __export(index_exports, {
35
35
  flattenPersonaTextAll: () => flattenPersonaTextAll,
36
36
  hasPersonaTextContent: () => hasPersonaTextContent,
37
37
  isPersonaTextObject: () => isPersonaTextObject,
38
- personaToLiteral: () => personaToLiteral
38
+ personaToLiteral: () => personaToLiteral,
39
+ removeNavigateBlock: () => removeNavigateBlock,
40
+ transformChatHistoryContentParts: () => transformChatHistoryContentParts
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
41
43
 
@@ -165,6 +167,74 @@ function aiGenerateInputFromSimplified(prompt, content) {
165
167
  }
166
168
  __name(aiGenerateInputFromSimplified, "aiGenerateInputFromSimplified");
167
169
 
170
+ // src/chat-history.types.ts
171
+ function removeNavigateBlock(input) {
172
+ return input.replace(/::: navigate[\s\S]*?:::/g, "").trim();
173
+ }
174
+ __name(removeNavigateBlock, "removeNavigateBlock");
175
+ function transformChatHistoryContentParts(parts) {
176
+ const content = [];
177
+ for (const rawPart of parts ?? []) {
178
+ const part = rawPart;
179
+ if (part?.type !== "text" && part?.type !== "file") continue;
180
+ if (part.type === "text" && typeof part.text === "string") {
181
+ const rawText = part.text || "";
182
+ if (rawText.includes("::: hide")) continue;
183
+ const audioMatch = rawText.match(/::: audio\s*!\[(.*?)\]\((.*?)\)\s*:::/);
184
+ const videoMatch = rawText.match(/::: video\s*!\[(.*?)\]\((.*?)\)\s*:::/);
185
+ if (audioMatch) {
186
+ content.push({
187
+ type: "audio",
188
+ data: audioMatch[2],
189
+ mediaType: audioMatch[1]
190
+ });
191
+ } else if (videoMatch) {
192
+ content.push({
193
+ type: "video",
194
+ video: videoMatch[2],
195
+ mediaType: videoMatch[1]
196
+ });
197
+ } else {
198
+ let text = rawText.replace(/\\\\\\n/g, "\n");
199
+ text = removeNavigateBlock(text);
200
+ content.push({
201
+ type: "text",
202
+ text
203
+ });
204
+ }
205
+ } else if (part.type === "file") {
206
+ const mediaType = part.mimeType || "";
207
+ if (mediaType.startsWith("image/")) {
208
+ content.push({
209
+ type: "image",
210
+ image: part.data,
211
+ mediaType
212
+ });
213
+ } else if (mediaType.startsWith("video/")) {
214
+ content.push({
215
+ type: "video",
216
+ video: part.data,
217
+ mediaType
218
+ });
219
+ } else if (mediaType.startsWith("audio/")) {
220
+ content.push({
221
+ type: "audio",
222
+ data: part.data,
223
+ mediaType
224
+ });
225
+ } else {
226
+ content.push({
227
+ type: "file",
228
+ data: part.data,
229
+ mediaType
230
+ });
231
+ }
232
+ }
233
+ }
234
+ return content;
235
+ }
236
+ __name(transformChatHistoryContentParts, "transformChatHistoryContentParts");
237
+
168
238
  // src/sandbox-contract.ts
169
239
  var REQUIRED_PRIMITIVE_SHIMS = [
170
240
  // Class-based
@@ -359,5 +429,7 @@ var AGENT_LOG_SOURCES = [
359
429
  flattenPersonaTextAll,
360
430
  hasPersonaTextContent,
361
431
  isPersonaTextObject,
362
- personaToLiteral
432
+ personaToLiteral,
433
+ removeNavigateBlock,
434
+ transformChatHistoryContentParts
363
435
  });
@@ -127,6 +127,74 @@ function aiGenerateInputFromSimplified(prompt, content) {
127
127
  }
128
128
  __name(aiGenerateInputFromSimplified, "aiGenerateInputFromSimplified");
129
129
 
130
+ // src/chat-history.types.ts
131
+ function removeNavigateBlock(input) {
132
+ return input.replace(/::: navigate[\s\S]*?:::/g, "").trim();
133
+ }
134
+ __name(removeNavigateBlock, "removeNavigateBlock");
135
+ function transformChatHistoryContentParts(parts) {
136
+ const content = [];
137
+ for (const rawPart of parts ?? []) {
138
+ const part = rawPart;
139
+ if (part?.type !== "text" && part?.type !== "file") continue;
140
+ if (part.type === "text" && typeof part.text === "string") {
141
+ const rawText = part.text || "";
142
+ if (rawText.includes("::: hide")) continue;
143
+ const audioMatch = rawText.match(/::: audio\s*!\[(.*?)\]\((.*?)\)\s*:::/);
144
+ const videoMatch = rawText.match(/::: video\s*!\[(.*?)\]\((.*?)\)\s*:::/);
145
+ if (audioMatch) {
146
+ content.push({
147
+ type: "audio",
148
+ data: audioMatch[2],
149
+ mediaType: audioMatch[1]
150
+ });
151
+ } else if (videoMatch) {
152
+ content.push({
153
+ type: "video",
154
+ video: videoMatch[2],
155
+ mediaType: videoMatch[1]
156
+ });
157
+ } else {
158
+ let text = rawText.replace(/\\\\\\n/g, "\n");
159
+ text = removeNavigateBlock(text);
160
+ content.push({
161
+ type: "text",
162
+ text
163
+ });
164
+ }
165
+ } else if (part.type === "file") {
166
+ const mediaType = part.mimeType || "";
167
+ if (mediaType.startsWith("image/")) {
168
+ content.push({
169
+ type: "image",
170
+ image: part.data,
171
+ mediaType
172
+ });
173
+ } else if (mediaType.startsWith("video/")) {
174
+ content.push({
175
+ type: "video",
176
+ video: part.data,
177
+ mediaType
178
+ });
179
+ } else if (mediaType.startsWith("audio/")) {
180
+ content.push({
181
+ type: "audio",
182
+ data: part.data,
183
+ mediaType
184
+ });
185
+ } else {
186
+ content.push({
187
+ type: "file",
188
+ data: part.data,
189
+ mediaType
190
+ });
191
+ }
192
+ }
193
+ }
194
+ return content;
195
+ }
196
+ __name(transformChatHistoryContentParts, "transformChatHistoryContentParts");
197
+
130
198
  // src/sandbox-contract.ts
131
199
  var REQUIRED_PRIMITIVE_SHIMS = [
132
200
  // Class-based
@@ -320,5 +388,7 @@ export {
320
388
  flattenPersonaTextAll,
321
389
  hasPersonaTextContent,
322
390
  isPersonaTextObject,
323
- personaToLiteral
391
+ personaToLiteral,
392
+ removeNavigateBlock,
393
+ transformChatHistoryContentParts
324
394
  };
@@ -9,6 +9,20 @@ export interface BatchingConfig {
9
9
  debounceWindowMs?: number;
10
10
  /** Maximum messages collected in a single batch. */
11
11
  maxBatchMessages?: number;
12
+ /**
13
+ * Strict sequential turn execution. When `true`, a new message arriving while a turn is
14
+ * in-flight does NOT abort the running turn — it waits in a batch until the current turn
15
+ * completes, then runs against the post-reply state. Trades latency for correctness:
16
+ * eliminates duplicate/contradictory replies and side-effect-then-no-reply failures from
17
+ * mid-tool aborts. No effect when `debounceWindowMs` is 0 (batching disabled).
18
+ *
19
+ * Most useful with the stream endpoint. Generate callers (e.g. lua-whatsapp, sync HTTP)
20
+ * may hit ingress / client timeouts on long waits regardless of backend serialization.
21
+ *
22
+ * Per-agent only — there is no platform-wide env var override (deliberate: a global
23
+ * switch would silently flip every batching-enabled agent at once). Default: `false`.
24
+ */
25
+ serializeProcessing?: boolean;
12
26
  }
13
27
 
14
28
  /**
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Wire-format types for `GET /chat/history/:agentId` and the VM-sandbox
3
+ * `User.getChatHistory()` API. Both paths return the same shape.
4
+ *
5
+ * Shared between lua-core (HTTP controller + VM devtools) and lua-cli
6
+ * (HTTP client typing for `lua test`). Also shared with lua-api's
7
+ * `thread.service.ts` (admin org-thread listing) so the parser never
8
+ * diverges across services.
9
+ */
10
+
11
+ /**
12
+ * One content part of a chat-history message.
13
+ *
14
+ * `text` parts carry plain text. Media parts (`image` / `video` / `audio`
15
+ * / `file`) carry a URL or base64 payload in `data` (or `image` / `video`
16
+ * for legacy reasons) plus a `mediaType` MIME string.
17
+ */
18
+ export interface ChatHistoryContent {
19
+ type: 'text' | 'image' | 'video' | 'audio' | 'file';
20
+ text?: string;
21
+ image?: string;
22
+ video?: string;
23
+ data?: string;
24
+ mediaType?: string;
25
+ latitude?: number;
26
+ longitude?: number;
27
+ }
28
+
29
+ /**
30
+ * One message returned by chat-history endpoints.
31
+ *
32
+ * Only `user` and `assistant` roles surface — system / tool messages are
33
+ * filtered out before transport.
34
+ */
35
+ export interface ChatHistoryMessage {
36
+ id: string;
37
+ role: 'user' | 'assistant';
38
+ /** ISO-8601 timestamp string. */
39
+ createdAt: string;
40
+ content: ChatHistoryContent[];
41
+ }
42
+
43
+ /**
44
+ * Strip `::: navigate …:::` blocks from a text string.
45
+ *
46
+ * Exposed because both lua-core's chat-history transform and any future
47
+ * external transform may need it independently.
48
+ */
49
+ export function removeNavigateBlock(input: string): string {
50
+ return input.replace(/::: navigate[\s\S]*?:::/g, '').trim();
51
+ }
52
+
53
+ /**
54
+ * Transform raw Mastra `MastraMessageContentV2.parts[]` into the
55
+ * `ChatHistoryContent[]` wire shape.
56
+ *
57
+ * Single source of truth for the conversion: used by lua-core's
58
+ * `toChatHistoryMessage` (HTTP + VM sandbox paths) and lua-api's
59
+ * `thread.service.ts` admin org-thread listing. Any future divergence
60
+ * starts here.
61
+ *
62
+ * Parts are deliberately typed `unknown` because callers may either pass
63
+ * Mastra v5 `MastraMessagePart` instances (lua-core, via `@mastra/core`)
64
+ * or raw JSON parsed from a SQL row (lua-api, reading `mastra_messages`
65
+ * directly). The function only inspects `.type`, `.text`, `.data`, and
66
+ * `.mimeType` via property access, so both shapes work.
67
+ */
68
+ export function transformChatHistoryContentParts(parts: unknown[] | undefined | null): ChatHistoryContent[] {
69
+ const content: ChatHistoryContent[] = [];
70
+
71
+ for (const rawPart of parts ?? []) {
72
+ const part = rawPart as { type?: string; text?: string; data?: string; mimeType?: string };
73
+ if (part?.type !== 'text' && part?.type !== 'file') continue;
74
+
75
+ if (part.type === 'text' && typeof part.text === 'string') {
76
+ const rawText = part.text || '';
77
+
78
+ // Filter out hidden text
79
+ if (rawText.includes('::: hide')) continue;
80
+
81
+ // Check for audio/video markdown patterns and surface as native
82
+ // audio/video parts (matches the ChatHistoryContent type contract).
83
+ const audioMatch = rawText.match(/::: audio\s*!\[(.*?)\]\((.*?)\)\s*:::/);
84
+ const videoMatch = rawText.match(/::: video\s*!\[(.*?)\]\((.*?)\)\s*:::/);
85
+
86
+ if (audioMatch) {
87
+ content.push({ type: 'audio', data: audioMatch[2], mediaType: audioMatch[1] });
88
+ } else if (videoMatch) {
89
+ content.push({ type: 'video', video: videoMatch[2], mediaType: videoMatch[1] });
90
+ } else {
91
+ // Cleanup text: de-escape \\\\\\n and strip navigate blocks
92
+ let text = rawText.replace(/\\\\\\n/g, '\n');
93
+ text = removeNavigateBlock(text);
94
+ content.push({ type: 'text', text });
95
+ }
96
+ } else if (part.type === 'file') {
97
+ const mediaType = part.mimeType || '';
98
+
99
+ if (mediaType.startsWith('image/')) {
100
+ content.push({ type: 'image', image: part.data, mediaType });
101
+ } else if (mediaType.startsWith('video/')) {
102
+ content.push({ type: 'video', video: part.data, mediaType });
103
+ } else if (mediaType.startsWith('audio/')) {
104
+ content.push({ type: 'audio', data: part.data, mediaType });
105
+ } else {
106
+ content.push({ type: 'file', data: part.data, mediaType });
107
+ }
108
+ }
109
+ }
110
+
111
+ return content;
112
+ }
@@ -4,6 +4,7 @@ export * from './unstructured-types';
4
4
  export * from './ai-generate.types';
5
5
  export * from './ai-generate.utils';
6
6
  export * from './agent-invocation.types';
7
+ export * from './chat-history.types';
7
8
  export * from './sandbox-contract';
8
9
  export * from './device.types';
9
10
  export * from './client-tools.types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lua-cli",
3
- "version": "3.15.0",
3
+ "version": "3.15.2",
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",
@@ -20,7 +20,7 @@
20
20
  "inquirer": "^12.9.6",
21
21
  "stripe": "^17.5.0",
22
22
  "js-yaml": "^4.1.0",
23
- "lua-cli": "^3.15.0",
23
+ "lua-cli": "^3.15.2",
24
24
  "openai": "^5.23.0",
25
25
  "uuid": "^13.0.0",
26
26
  "zod": "^3.24.1"