lua-cli 3.35.0 โ 3.36.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 +278 -18
- package/dist/api-exports.js +433 -196
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +1319 -227
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +18 -18
- package/dist/workflow-builder.d.ts +47 -0
- package/dist/workflow-builder.js +370 -195
- package/dist/workflow-builder.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/api/Workflows.md +1 -1
- package/package.json +3 -2
- package/scripts/compat-matrix-previous-cli.mjs +266 -0
- package/template/package.json +1 -1
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# lua-cli v3.
|
|
1
|
+
# lua-cli v3.36.0
|
|
2
2
|
|
|
3
|
-
Welcome to the comprehensive API documentation for lua-cli v3.
|
|
3
|
+
Welcome to the comprehensive API documentation for lua-cli v3.36.0. This guide covers every API, class, and function exported by the package.
|
|
4
4
|
|
|
5
5
|
## ๐ Documentation Index
|
|
6
6
|
|
package/docs/api/Workflows.md
CHANGED
|
@@ -15,7 +15,7 @@ Inside a sandbox (tools, jobs, webhooks, workflow code steps) `Workflows` is als
|
|
|
15
15
|
- `start` **never awaits execution** โ it returns `{ runId, status }` immediately. `waitSeconds` (โค 55) asks the server to hold the response for early terminal state; it changes the response, never the execution model.
|
|
16
16
|
- Runs are idempotent on `idempotencyKey` โ re-sending the same key returns the existing run instead of a duplicate.
|
|
17
17
|
- A workflow with `concurrencyPolicy: 'forbid'` refuses a second start while one is in flight with a thrown `WorkflowApiError { code: 'RUNS_IN_FLIGHT', blockingRunId }` โ never a 429, never a gate.
|
|
18
|
-
- `status: 'gated'` means the run is parked (quota, billing or consent) and holds no slot.
|
|
18
|
+
- `status: 'gated'` means the run is parked (quota, billing or consent) and holds no slot. A **consent** gate is cleared by a person resolving its approval โ from the desktop, or through the approvals API / inbox. `lua workflows approve` cannot clear one: it requires an `--approval wfa_โฆ` id, which belongs to a workflow `approval` node, and a consent gate has no such id. `lua workflows status <runId>` reports a gated run, and exits 6 under `--strict`.
|
|
19
19
|
- `nameOrId` resolves the workflow **name on the calling agent first**, then a workflow id.
|
|
20
20
|
- `get` / `list` honour output visibility: a restricted run comes back with `restricted: true` and no `output` โ never an error.
|
|
21
21
|
- From a workflow code step, `Workflows.start` creates a **detached** run (no parent link). Use the `.workflow(...)` builder node when the parent should wait.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lua-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.36.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",
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
129
129
|
"test": "node ../../scripts/test-gate.mjs jest",
|
|
130
130
|
"test:watch": "jest --watch",
|
|
131
|
-
"test:coverage": "node ../../scripts/test-gate.mjs jest --coverage"
|
|
131
|
+
"test:coverage": "node ../../scripts/test-gate.mjs jest --coverage",
|
|
132
|
+
"test:compat-matrix": "node scripts/compat-matrix-previous-cli.mjs"
|
|
132
133
|
}
|
|
133
134
|
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* [WMC-E7] The CLI compatibility matrix's real leg (plan ยง6.9, "Older CLIs keep working").
|
|
4
|
+
*
|
|
5
|
+
* Installs the PREVIOUS MINOR RELEASE of this CLI from npm into a temporary directory and drives it, as a
|
|
6
|
+
* child process, against the mock API in `tests/helpers/workflow-compat-server.mjs`. It runs the matrix
|
|
7
|
+
* twice: once against a server serving definitions and runs WITHOUT any of the per-step model-selection
|
|
8
|
+
* fields, and once WITH all of them, including the run-event types the older CLI has never heard of.
|
|
9
|
+
*
|
|
10
|
+
* The verbs exercised are the three ยง6.9 names: **push**, **view** and **watch**.
|
|
11
|
+
*
|
|
12
|
+
* โโ Safety โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
13
|
+
* Nothing here touches a real account or a real agent. The child CLI is pointed at `127.0.0.1` through
|
|
14
|
+
* `LUA_API_URL` and given a throwaway `LUA_API_KEY`, so there is no `lua auth`, no production host and no
|
|
15
|
+
* production verb against anything that exists. The temp directory is removed on the way out.
|
|
16
|
+
*
|
|
17
|
+
* โโ Why it is gated โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
18
|
+
* It needs the npm registry. Every property it proves is also asserted offline in
|
|
19
|
+
* `tests/__tests__/workflows.compat-matrix.test.ts`, which runs on every PR; this leg is the end-to-end
|
|
20
|
+
* confirmation and belongs in the nightly job and on demand:
|
|
21
|
+
*
|
|
22
|
+
* pnpm --filter lua-cli test:compat-matrix
|
|
23
|
+
* LUA_COMPAT_MATRIX_VERSION=3.33.0 pnpm --filter lua-cli test:compat-matrix
|
|
24
|
+
*
|
|
25
|
+
* Exit 0 = the previous release coped with both halves. Non-zero names the leg that failed.
|
|
26
|
+
*/
|
|
27
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
28
|
+
import fs from 'node:fs';
|
|
29
|
+
import os from 'node:os';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
|
+
import { AGENT_ID, RUN_ID, WORKFLOW_ID, startCompatServer } from '../tests/helpers/workflow-compat-server.mjs';
|
|
33
|
+
|
|
34
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(HERE, '..', 'package.json'), 'utf8'));
|
|
36
|
+
|
|
37
|
+
/** The previous MINOR of the version in package.json โ 3.34.x โ 3.33.0 โ unless one is named. */
|
|
38
|
+
function previousMinor(current) {
|
|
39
|
+
const [major, minor] = current.split('.').map(Number);
|
|
40
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor)) throw new Error(`cannot read a version from "${current}"`);
|
|
41
|
+
if (minor === 0) throw new Error(`no previous minor for ${current} โ name one with LUA_COMPAT_MATRIX_VERSION`);
|
|
42
|
+
return `${major}.${minor - 1}.0`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const TARGET = process.env.LUA_COMPAT_MATRIX_VERSION || previousMinor(PKG.version);
|
|
46
|
+
|
|
47
|
+
function say(msg) {
|
|
48
|
+
process.stdout.write(`${msg}\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Run the installed previous CLI with the mock API as its whole world.
|
|
53
|
+
*
|
|
54
|
+
* ASYNC on purpose. `spawnSync` blocks this process's event loop, and the mock API runs in this process โ
|
|
55
|
+
* so a synchronous spawn freezes the server and every request the child makes hangs until the child is
|
|
56
|
+
* killed on its timeout. That failure looks exactly like a compatibility failure (empty output, no requests
|
|
57
|
+
* recorded) and is not one, which is why the harness records the signal as well as the exit code below.
|
|
58
|
+
*/
|
|
59
|
+
function runOldCli(cliEntry, cwd, baseUrl, args, { timeoutMs = 60_000 } = {}) {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
const child = spawn(process.execPath, [cliEntry, ...args], {
|
|
62
|
+
cwd,
|
|
63
|
+
// stdin CLOSED, not inherited: an open pipe leaves any interactive prompt waiting for ever.
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
LUA_API_URL: baseUrl,
|
|
68
|
+
LUA_AUTH_URL: baseUrl,
|
|
69
|
+
LUA_API_KEY: 'compat-matrix-not-a-real-key',
|
|
70
|
+
// Keep the child from phoning home for an update check or writing analytics during the matrix.
|
|
71
|
+
LUA_DISABLE_ANALYTICS: '1',
|
|
72
|
+
LUA_SKIP_UPDATE_CHECK: '1',
|
|
73
|
+
CI: '1',
|
|
74
|
+
NO_COLOR: '1',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
let stdout = '';
|
|
78
|
+
let stderr = '';
|
|
79
|
+
child.stdout.on('data', (c) => (stdout += c));
|
|
80
|
+
child.stderr.on('data', (c) => (stderr += c));
|
|
81
|
+
const timer = setTimeout(() => child.kill('SIGTERM'), timeoutMs);
|
|
82
|
+
child.on('error', (error) => {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
resolve({ status: null, signal: null, stdout, stderr, error });
|
|
85
|
+
});
|
|
86
|
+
child.on('close', (status, signal) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
resolve({ status, signal, stdout, stderr });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** One half of the matrix: a server serving old-only or new-bearing shapes, and the three verbs against it. */
|
|
94
|
+
async function runLeg({ withNewFields, cliEntry, projectDir }) {
|
|
95
|
+
const label = withNewFields ? 'WITH the new fields' : 'WITHOUT the new fields';
|
|
96
|
+
const server = await startCompatServer({ withNewFields });
|
|
97
|
+
const failures = [];
|
|
98
|
+
try {
|
|
99
|
+
const checks = [
|
|
100
|
+
{ verb: 'view', args: ['workflows', 'view', 'compat', '--json'] },
|
|
101
|
+
{ verb: 'list', args: ['workflows', 'list', '--json'] },
|
|
102
|
+
{ verb: 'status', args: ['workflows', 'status', RUN_ID, '--steps', '--json'] },
|
|
103
|
+
// `watch` must reach the terminal frame and exit 0 even with event types it does not know.
|
|
104
|
+
{ verb: 'watch', args: ['workflows', 'watch', RUN_ID, '--timeout', '20'] },
|
|
105
|
+
// The push half of ยง6.9: the old client compiles its own source and sends a body with none of the new
|
|
106
|
+
// members. `compile` is part of it on purpose โ a push body an older CLI cannot even BUILD is the same
|
|
107
|
+
// failure as one the server rejects.
|
|
108
|
+
{ verb: 'compile', args: ['compile', '--ci'] },
|
|
109
|
+
{ verb: 'push', args: ['push', 'workflow', '--force'] },
|
|
110
|
+
];
|
|
111
|
+
for (const { verb, args } of checks) {
|
|
112
|
+
const out = await runOldCli(cliEntry, projectDir, server.baseUrl, args);
|
|
113
|
+
const ok = out.status === 0;
|
|
114
|
+
say(
|
|
115
|
+
` ${ok ? 'โ' : 'โ'} ${TARGET} ยท ${verb} ยท ${label}${ok ? '' : ` (${out.status === null ? `signal ${out.signal}` : `exit ${out.status}`})`}`
|
|
116
|
+
);
|
|
117
|
+
if (!ok) {
|
|
118
|
+
// `status: null` means the child never exited on its own โ name the signal and the spawn error, or the
|
|
119
|
+
// failure is indistinguishable from a real incompatibility.
|
|
120
|
+
const how = out.status === null ? `killed by ${out.signal ?? 'an unknown signal'}` : `exit ${out.status}`;
|
|
121
|
+
failures.push(`${verb} (${label}) ${how}`);
|
|
122
|
+
if (out.error) say(` spawn error: ${out.error.message}`);
|
|
123
|
+
say(` stdout: ${(out.stdout || '').trim().slice(0, 2500)}`);
|
|
124
|
+
say(` stderr: ${(out.stderr || '').trim().slice(0, 2500)}`);
|
|
125
|
+
say(` routes: ${server.requests.join(' | ')}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// A leg that asked the server for nothing proves nothing โ the "No tests found" failure mode.
|
|
129
|
+
if (server.requests.length === 0) failures.push(`${label}: the old CLI made no requests at all`);
|
|
130
|
+
|
|
131
|
+
// The push must have actually reached the version route, and what it sent is the thing under test:
|
|
132
|
+
// a body an older CLI builds carries NONE of the new members and leaves the envelope marker at 1.
|
|
133
|
+
if (server.pushed.length === 0) {
|
|
134
|
+
failures.push(`${label}: the old CLI never sent a version body`);
|
|
135
|
+
} else {
|
|
136
|
+
for (const body of server.pushed) {
|
|
137
|
+
const marker = body?.graph?.luaWorkflow;
|
|
138
|
+
if (marker !== 1) failures.push(`${label}: the old CLI sent luaWorkflow ${marker}, expected 1`);
|
|
139
|
+
const nodes = body?.graph?.definition?.graph ?? [];
|
|
140
|
+
const leaked = JSON.stringify(nodes).match(/"(taskClass|requires|modelReason|effort)"/g);
|
|
141
|
+
if (leaked) failures.push(`${label}: the old CLI's body carried ${[...new Set(leaked)].join(', ')}`);
|
|
142
|
+
say(` โ ${TARGET} ยท push body ยท ${label} ยท luaWorkflow ${marker}, no new members`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} finally {
|
|
146
|
+
await server.close();
|
|
147
|
+
}
|
|
148
|
+
return failures;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function main() {
|
|
152
|
+
say(`\n๐งญ CLI compatibility matrix โ driving lua-cli@${TARGET} against the current shapes\n`);
|
|
153
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'lua-compat-'));
|
|
154
|
+
const failures = [];
|
|
155
|
+
try {
|
|
156
|
+
// 1. Install the previous release into a scratch directory of its own.
|
|
157
|
+
say(` โฆ installing lua-cli@${TARGET} into ${tmp}`);
|
|
158
|
+
fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({ name: 'compat-host', private: true }), 'utf8');
|
|
159
|
+
execFileSync('npm', ['install', '--no-audit', '--no-fund', '--silent', `lua-cli@${TARGET}`], {
|
|
160
|
+
cwd: tmp,
|
|
161
|
+
stdio: 'inherit',
|
|
162
|
+
timeout: 300_000,
|
|
163
|
+
});
|
|
164
|
+
const cliEntry = path.join(tmp, 'node_modules', 'lua-cli', 'dist', 'index.js');
|
|
165
|
+
if (!fs.existsSync(cliEntry)) throw new Error(`installed lua-cli@${TARGET} has no dist/index.js at ${cliEntry}`);
|
|
166
|
+
|
|
167
|
+
// 2. A project directory the old CLI recognises. No credential file: the key rides the environment.
|
|
168
|
+
const projectDir = path.join(tmp, 'project');
|
|
169
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
path.join(projectDir, 'lua.skill.yaml'),
|
|
172
|
+
[
|
|
173
|
+
'agent:',
|
|
174
|
+
` agentId: ${AGENT_ID}`,
|
|
175
|
+
' orgId: org_compat',
|
|
176
|
+
' name: compat',
|
|
177
|
+
'workflows:',
|
|
178
|
+
' - name: compat',
|
|
179
|
+
` workflowId: ${WORKFLOW_ID}`,
|
|
180
|
+
' version: 1.0.0',
|
|
181
|
+
'',
|
|
182
|
+
].join('\n'),
|
|
183
|
+
'utf8'
|
|
184
|
+
);
|
|
185
|
+
// A project the OLD CLI can compile: nothing in it uses anything this change added, which is the point.
|
|
186
|
+
fs.mkdirSync(path.join(projectDir, 'src', 'workflows'), { recursive: true });
|
|
187
|
+
fs.writeFileSync(
|
|
188
|
+
path.join(projectDir, 'package.json'),
|
|
189
|
+
JSON.stringify({ name: 'compat', private: true, type: 'module', dependencies: { 'lua-cli': TARGET } }, null, 2),
|
|
190
|
+
'utf8'
|
|
191
|
+
);
|
|
192
|
+
fs.symlinkSync(path.join(tmp, 'node_modules'), path.join(projectDir, 'node_modules'), 'dir');
|
|
193
|
+
fs.writeFileSync(
|
|
194
|
+
path.join(projectDir, 'tsconfig.json'),
|
|
195
|
+
JSON.stringify(
|
|
196
|
+
{
|
|
197
|
+
compilerOptions: {
|
|
198
|
+
target: 'ES2022',
|
|
199
|
+
module: 'ESNext',
|
|
200
|
+
moduleResolution: 'bundler',
|
|
201
|
+
strict: true,
|
|
202
|
+
skipLibCheck: true,
|
|
203
|
+
esModuleInterop: true,
|
|
204
|
+
outDir: 'dist',
|
|
205
|
+
},
|
|
206
|
+
include: ['src/**/*'],
|
|
207
|
+
},
|
|
208
|
+
null,
|
|
209
|
+
2
|
|
210
|
+
),
|
|
211
|
+
'utf8'
|
|
212
|
+
);
|
|
213
|
+
fs.writeFileSync(
|
|
214
|
+
path.join(projectDir, 'src', 'workflows', 'compat.ts'),
|
|
215
|
+
[
|
|
216
|
+
"import { createWorkflow } from 'lua-cli';",
|
|
217
|
+
"import { z } from 'zod';",
|
|
218
|
+
'',
|
|
219
|
+
'export const compat = createWorkflow({',
|
|
220
|
+
" name: 'compat',",
|
|
221
|
+
" description: 'compatibility matrix fixture',",
|
|
222
|
+
' inputSchema: z.object({}),',
|
|
223
|
+
' outputSchema: z.object({ text: z.string() }),',
|
|
224
|
+
'})',
|
|
225
|
+
" .agentStep('summarise', { agentId: '$self', toolScope: {}, prompt: 'hi' })",
|
|
226
|
+
' .commit();',
|
|
227
|
+
'',
|
|
228
|
+
].join('\n'),
|
|
229
|
+
'utf8'
|
|
230
|
+
);
|
|
231
|
+
fs.writeFileSync(
|
|
232
|
+
path.join(projectDir, 'src', 'index.ts'),
|
|
233
|
+
[
|
|
234
|
+
"import { LuaAgent } from 'lua-cli';",
|
|
235
|
+
"import { compat } from './workflows/compat';",
|
|
236
|
+
'',
|
|
237
|
+
'export default new LuaAgent({',
|
|
238
|
+
" name: 'compat',",
|
|
239
|
+
" persona: 'compatibility matrix fixture',",
|
|
240
|
+
' workflows: [compat],',
|
|
241
|
+
'});',
|
|
242
|
+
'',
|
|
243
|
+
].join('\n'),
|
|
244
|
+
'utf8'
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// 3. Both halves.
|
|
248
|
+
for (const withNewFields of [false, true]) {
|
|
249
|
+
failures.push(...(await runLeg({ withNewFields, cliEntry, projectDir })));
|
|
250
|
+
}
|
|
251
|
+
} finally {
|
|
252
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (failures.length) {
|
|
256
|
+
say(`\nโ compatibility matrix: ${failures.length} failure(s)`);
|
|
257
|
+
for (const f of failures) say(` โข ${f}`);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
say(`\nโ compatibility matrix: lua-cli@${TARGET} pushes, views and watches against the current shapes.\n`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
main().catch((err) => {
|
|
264
|
+
process.stderr.write(`compatibility matrix failed to run: ${err?.stack || err}\n`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
});
|