lua-cli 3.34.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 +298 -20
- package/dist/api-exports.js +438 -198
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +1334 -234
- 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/LuaWorkflow.md +1 -1
- package/docs/api/Workflows.md +1 -1
- package/docs/workflows/migrating-runs.md +1 -1
- package/docs/workflows/recovery.md +4 -4
- package/docs/workflows/testing-offline.md +1 -1
- package/package.json +7 -6
- 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/LuaWorkflow.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The workflow primitive: a **durable, multi-step program** you author in TypeScript, compile with `lua compile`, and push with `lua push workflow`. The engine executes the compiled graph server-side โ steps survive restarts, waits hold no compute, and approvals/signals park the run until a person or a webhook answers.
|
|
4
4
|
|
|
5
|
-
> **Execution model โ read this first.** _`execute` re-runs from the top after resume; execution is at-least-once โ dedupe on `occurrenceId`._ `occurrenceId` is `${lineageId}:${stepId}` โ the same key on retry, resume
|
|
5
|
+
> **Execution model โ read this first.** _`execute` re-runs from the top after resume; execution is at-least-once โ dedupe on `occurrenceId`._ `occurrenceId` is `${lineageId}:${stepId}` โ the same key on retry, resume and `retry-step` (no repair run exists โ a terminal run is never re-run from its ledger); an effect that must be unique across independent runs needs your own business key (`refund:${ticketId}`). Wrap external effects in `ctx.once(key, fn)`.
|
|
6
6
|
|
|
7
7
|
## Import
|
|
8
8
|
|
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.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
_Source of truth: workflows-spec (Cluster D R46/R47, D7-r2). This page is the developer summary; the spec is normative._
|
|
4
4
|
|
|
5
|
-
`lua workflows migrate-runs <workflowId> --from <ver> --to <ver>` re-pins runs to a new version where compatible (`isResumeCompatible` โ same seeding math as
|
|
5
|
+
`lua workflows migrate-runs <workflowId> --from <ver> --to <ver>` re-pins runs to a new version where compatible (`isResumeCompatible` โ the same seeding math as `lua test workflow --from-run`); `cancel-by-version` recalls a bad version's fleet.
|
|
6
6
|
|
|
7
7
|
## Edited inputs
|
|
8
8
|
|
|
@@ -19,13 +19,13 @@ A parked step waits for exactly one of:
|
|
|
19
19
|
|
|
20
20
|
`onError:'continue'` never parks: the step is `failed` on the ledger, the run goes on, and every successor sees the step's **continued-failure value** `{ __lua_workflow:'continued_failure', failed:true, error:{code,message}, text:'' }` under `stepResults.<id>` (the default input, `${stepResults.<id>.text}` โ `''`, `getStepResult(id)`, a `conditional` on `stepResults.<id>.failed`, and the parallel / foreach join entry). A `retry-step` on such a row while the run is still running re-arms it, but a successor that already consumed the value is not re-run.
|
|
21
21
|
|
|
22
|
-
`fail` (or cancelling the run) applies the step's failure as-is. From the CLI: `lua workflows status <runId>` shows the park and names the verbs; `lua workflows retry-step <runId> --step <id>` re-runs, `lua workflows resolve-step <runId> --step <id> --outcome skip|complete|fail [--output <json|@file>] [--note โฆ]` decides (R37 โ `complete` needs `--output`, validated against the step's `outputSchema`; a second decision on the same park is a 200 no-op that names who decided). The desktop
|
|
22
|
+
`fail` (or cancelling the run) applies the step's failure as-is. From the CLI: `lua workflows status <runId>` shows the park and names the verbs; `lua workflows retry-step <runId> --step <id>` re-runs, `lua workflows resolve-step <runId> --step <id> --outcome skip|complete|fail [--output <json|@file>] [--note โฆ]` decides (R37 โ `complete` needs `--output`, validated against the step's `outputSchema`; a second decision on the same park is a 200 no-op that names who decided). The desktop run page carries the same decisions.
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## No repair run
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Nothing re-runs a run from its ledger today (LUA-709 retired the verb; BAC-601): there is no "repair run" in the CLI, the API, the chat tools or the desktop, and a **terminal run cannot be re-run**. `retry-step` and `resolve-step` are the ways past a park; a new run (`lua workflows start`) is the way past a terminal one โ offline, `lua test workflow <name> --from-run <runId>` can seed every `completed` step from the old ledger for a rehearsal. Because `occurrenceId` is stable across retry, resume and `retry-step`, an external effect executes at most once per occurrence within a run.
|
|
27
27
|
|
|
28
|
-
> **The rule to remember:** _execution is at-least-once โ dedupe on `occurrenceId`_; an effect that must be unique across **independent** runs needs your own business key (`refund:${ticketId}`).
|
|
28
|
+
> **The rule to remember:** _execution is at-least-once โ dedupe on `occurrenceId`_; an effect that must be unique across **independent** runs (a new run, seeded or not, is one) needs your own business key (`refund:${ticketId}`).
|
|
29
29
|
|
|
30
30
|
## Rehearsing offline
|
|
31
31
|
|
|
@@ -15,7 +15,7 @@ _Source of truth: workflows-spec 03 ยง3.9 (WF-204/WF-225/WF-332). This page is t
|
|
|
15
15
|
| `--approve <id>[=@payload]` ยท `--deny <id>[=@reason]` | Pre-answer approvals โ the node completes with the engine's own envelope, `WorkflowApprovalOutput` (`{ approved, decision, text, note?, editedPayload?, editRevision?, decidedBy? }`, see [Approvals](./approvals.md)); an edited payload is the FULL payload, judged like the server's inline edit (`changedPointers` against the payload the approval showed + `matchesEditablePath`, then `editedPayloadSchema`); `onDeny` defaults to `'continue'` as on the engine (LUA-751). `editable: true` with no `editablePaths` is whole-root rights (any member may change), as on the server. Because the change set is judged against the payload the approval actually showed, a change outside `editablePaths` fails exit 2 `edit-path-not-allowed` **when the approval's turn comes โ after the steps before it have run** โ not before the run starts; `editedPayloadSchema` is still checked up-front |
|
|
16
16
|
| `--signal <name>=@file\|<json>` (rep.) | Pre-supply `waitForSignal` payloads in order of arrival |
|
|
17
17
|
| `--fixtures <dir>` / `--record <dir>` | Replay / record agent+tool outputs (`<stepId>.<attempt>.json`); a missing fixture is exit **5** `FIXTURE_MISSING`, never a silent fake; `--step-output` wins over a fixture. An agent fixture's `output` is either the `{ text, object? }` reply envelope `--record` writes (the `object` wins, else the text is parsed under the `outputSchema`) or the structured value itself, taken as-is (LUA-751) โ a value that is exactly `{ text }` reads as a text reply; record it as `{ object: { text } }` |
|
|
18
|
-
| `--from-run <runId>` | Seed every `completed` step from a real run (
|
|
18
|
+
| `--from-run <runId>` | Seed every `completed` step from a real run (the ledger seeding `migrate-runs` also uses); graph drift asks for `--force` |
|
|
19
19
|
| `--park <id>` (rep.) | Simulate a platform-fault park of a `sideEffects:'external'` step โ then `retry / skip / complete / fail`, the production verbs (see [When a step parks](./recovery.md)) |
|
|
20
20
|
| `--fast-retries` | Collapse retry backoff waits to 0 |
|
|
21
21
|
| `--real-time` | Actually wait on `sleep`/backoff instead of fast-forwarding the virtual clock |
|
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",
|
|
@@ -113,11 +113,11 @@
|
|
|
113
113
|
"stripe": "^19.2.0",
|
|
114
114
|
"ts-node": "^10.9.2",
|
|
115
115
|
"tsup": "^8.5.1",
|
|
116
|
-
"@lua/shared-types": "0.0.1",
|
|
117
|
-
"@lua/shared-source-sync": "0.0.1",
|
|
118
|
-
"@lua/workflow-graph": "0.0.1",
|
|
119
116
|
"@lua/sandbox-runtime": "0.0.1",
|
|
120
|
-
"@lua/shared-sandbox": "0.0.1"
|
|
117
|
+
"@lua/shared-sandbox": "0.0.1",
|
|
118
|
+
"@lua/shared-source-sync": "0.0.1",
|
|
119
|
+
"@lua/shared-types": "0.0.1",
|
|
120
|
+
"@lua/workflow-graph": "0.0.1"
|
|
121
121
|
},
|
|
122
122
|
"scripts": {
|
|
123
123
|
"clean": "rm -rf dist temp",
|
|
@@ -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
|
+
});
|