lua-cli 3.32.6 → 3.33.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.
@@ -851,15 +851,16 @@ lua push all --force
851
851
 
852
852
  Every `lua` command exits with one of the classes below; `lua --help` prints the same table. An error is one line on stderr — `✖ <code>: <message>` — plus a `💡` hint when there is one; `LUA_DEBUG=1` (or `--debug`) prints the stack under it. `--help` / `--version` and a prompt cancelled with Ctrl+C exit `0`.
853
853
 
854
- | Exit | Code | When |
855
- | ---- | ------------- | ---------------------------------------------------------------------------------------------- |
856
- | `0` | `ok` | Success |
857
- | `1` | `error` | An unclassified failure — the message says what (a compile failure is `compile_failed`) |
858
- | `2` | `usage` | Bad arguments — an unknown option or command, an unknown action, no project / agent configured |
859
- | `3` | `not_found` | The named thing does not exist (HTTP 404) |
860
- | `9` | `auth` | No credential, or the server refused it (HTTP 401) |
861
- | `10` | `forbidden` | The credential is fine but may not do this (HTTP 403 — any other 4xx as `http_<status>`) |
862
- | `11` | `unavailable` | The server or the network failed (HTTP 5xx, connection refused, request timeout) |
854
+ | Exit | Code | When |
855
+ | ---- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
856
+ | `0` | `ok` | Success |
857
+ | `1` | `error` | An unclassified failure — the message says what (a compile failure is `compile_failed`) |
858
+ | `2` | `usage` | Bad arguments — an unknown option or command, an unknown action, no project / agent configured |
859
+ | `3` | `not_found` | The named thing does not exist (HTTP 404) |
860
+ | `9` | `auth` | No credential, or the server refused it (HTTP 401) |
861
+ | `10` | `forbidden` | The credential is fine but may not do this (HTTP 403 — any other 4xx as `http_<status>`) |
862
+ | `11` | `unavailable` | The server or the network failed (HTTP 5xx, connection refused, request timeout) |
863
+ | `12` | `provider_rejected` | The model provider behind the agent refused the request (HTTP 424 `PROVIDER_REJECTED` — a bad BYOK key, an unknown model id, a refused prompt); the hint says what to fix, and a retry fails identically |
863
864
 
864
865
  `lua workflows` adds its run outcomes — which is why the classes above skip 4–8 — and keeps `1` for the server's refusal of a verb (a 4xx other than 404):
865
866
 
@@ -875,8 +876,9 @@ As printed by `lua --help` (`CLI_EXIT_CODE_HELP` in `src/errors/cli.error.ts`; `
875
876
 
876
877
  ```text
877
878
  Exit codes: 0 ok · 1 error · 2 usage · 3 not found · 9 auth (401) · 10 forbidden (403 / other 4xx) ·
878
- 11 unavailable (5xx / network). lua workflows adds 4 run failed · 5 run cancelled · 6 run gated ·
879
- 7 --timeout reached · 8 run parked waiting for a human.
879
+ 11 unavailable (5xx / network) · 12 provider rejected (424 the model provider refused the request).
880
+ lua workflows adds 4 run failed · 5 run cancelled · 6 run gated · 7 --timeout reached ·
881
+ 8 run parked waiting for a human.
880
882
  Errors: one line — ✖ <code>: <message> — plus a 💡 hint; LUA_DEBUG=1 prints the stack.
881
883
  ```
882
884
 
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # lua-cli v3.32.6
1
+ # lua-cli v3.33.0
2
2
 
3
- Welcome to the comprehensive API documentation for lua-cli v3.32.6. This guide covers every API, class, and function exported by the package.
3
+ Welcome to the comprehensive API documentation for lua-cli v3.33.0. This guide covers every API, class, and function exported by the package.
4
4
 
5
5
  ## 📚 Documentation Index
6
6
 
package/docs/api/AI.md CHANGED
@@ -60,7 +60,7 @@ const analysis = await AI.generate(
60
60
  Full control over generation parameters. Returns a rich result object with metadata.
61
61
 
62
62
  **Parameters (AiGenerateInput):**
63
- - `model?` (string) — Model to use, e.g. `'google/gemini-2.0-flash'`, `'openai/gpt-4o'`, `'anthropic/claude-sonnet-4-20250514'`. Defaults to the agent's configured model.
63
+ - `model?` (string) — Model to use, e.g. `'alibaba/qwen3.8-flash'`, `'google/gemini-3.8-flash'`, `'openai/gpt-5.4'`, `'anthropic/claude-sonnet-5'`. Defaults to the agent's configured model (the platform default, `alibaba/qwen3.8-flash`, unless the agent pins one).
64
64
  - `system?` (string) — System instruction.
65
65
  - `prompt?` (string) — User prompt (simple text).
66
66
  - `messages?` (array) — Conversation messages (AI SDK `ModelMessage[]`).
@@ -71,7 +71,7 @@ Full control over generation parameters. Returns a rich result object with metad
71
71
 
72
72
  ```typescript
73
73
  const result = await AI.generate({
74
- model: 'google/gemini-2.0-flash',
74
+ model: 'google/gemini-3.8-flash',
75
75
  system: 'You are concise.',
76
76
  prompt: 'What is the weather in London?',
77
77
  temperature: 0.7,
@@ -92,7 +92,7 @@ The full-options response mirrors AI SDK `GenerateTextResult` (serializable fiel
92
92
  | `text` | `string` | Generated text |
93
93
  | `finishReason` | `FinishReason` | `'stop'`, `'length'`, `'content-filter'`, `'tool-calls'`, `'error'`, `'other'`, `'unknown'` |
94
94
  | `usage` | `LanguageModelUsage` | `{ promptTokens, completionTokens, totalTokens }` |
95
- | `reasoning?` | `ReasoningOutput[]` | Model reasoning steps (e.g. Gemini thinking) |
95
+ | `reasoning?` | `ReasoningOutput[]` | Model reasoning steps (Qwen or Gemini thinking, Claude extended thinking) |
96
96
  | `reasoningText?` | `string` | Concatenated reasoning text |
97
97
  | `sources?` | `AiGenerateSource[]` | URL sources from Google Search grounding |
98
98
  | `toolCalls?` | `AiGenerateToolCall[]` | Tool calls made during generation |
@@ -103,11 +103,12 @@ The full-options response mirrors AI SDK `GenerateTextResult` (serializable fiel
103
103
 
104
104
  | Provider | Model prefix | Example |
105
105
  |----------|-------------|---------|
106
- | Google (Vertex AI) | `google/` | `google/gemini-2.0-flash` |
107
- | OpenAI | `openai/` | `openai/gpt-4o` |
108
- | Anthropic | `anthropic/` | `anthropic/claude-sonnet-4-20250514` |
106
+ | Alibaba (Qwen, the platform default) | `alibaba/` | `alibaba/qwen3.8-flash` |
107
+ | Google (Vertex AI) | `google/` | `google/gemini-3.8-flash` |
108
+ | OpenAI | `openai/` | `openai/gpt-5.4` |
109
+ | Anthropic | `anthropic/` | `anthropic/claude-sonnet-5` |
109
110
 
110
- If the requested provider's API key is not configured, the request falls back to the default Vertex AI model.
111
+ Any other code from `lua models list` works too. If the requested model is not available, the request falls back to the platform default (`alibaba/qwen3.8-flash`).
111
112
 
112
113
  Google models automatically get Google Search grounding — search result URLs appear in the `sources` field of the full-options response.
113
114
 
@@ -188,7 +189,7 @@ export default class WeatherSearchTool implements LuaTool {
188
189
 
189
190
  async execute(input: z.infer<typeof this.inputSchema>) {
190
191
  const result = await AI.generate({
191
- model: 'google/gemini-2.0-flash',
192
+ model: 'google/gemini-3.8-flash',
192
193
  system: 'You report current weather conditions concisely.',
193
194
  prompt: `What is the current weather in ${input.location}?`,
194
195
  });
@@ -88,18 +88,18 @@ persona: {
88
88
  ### `model` (optional, v3.6.0+)
89
89
  - **Type:** `string | ((request: LuaRequest) => string | Promise<string>)`
90
90
  - **Description:** AI model to use for this agent. Can be a static string (`'provider/model'`) or a resolver function that dynamically selects the model per request.
91
- - **Supported providers:** `google/*`, `openai/*`, `anthropic/*`
92
- - **Default:** Platform default model (currently `google/gemini-2.5-flash`)
91
+ - **Supported providers:** any code from `lua models list` — `alibaba/*`, `google/*`, `openai/*`, `anthropic/*` and the other approved providers
92
+ - **Default:** Platform default model (currently `alibaba/qwen3.8-flash`, with `google/gemini-3.8-flash` as the automatic fallback when Qwen is unavailable)
93
93
  - **Examples:**
94
94
 
95
95
  ```typescript
96
96
  // Static model
97
- model: 'openai/gpt-4o'
97
+ model: 'openai/gpt-5.4'
98
98
 
99
99
  // Dynamic resolver — picks model based on channel
100
100
  model: async (request) => {
101
- if (request.channel === 'whatsapp') return 'openai/gpt-4o-mini';
102
- return 'openai/gpt-4o';
101
+ if (request.channel === 'whatsapp') return 'openai/gpt-5.4-mini';
102
+ return 'openai/gpt-5.4';
103
103
  }
104
104
  ```
105
105
 
@@ -39,7 +39,7 @@ A typed code step. `inputSchema` / `outputSchema` / `resumeSchema` are zod schem
39
39
  | `sideEffects` | `'external'` ⇒ never auto-retried on a platform-fault reclaim — the step **parks** instead (see [When a step parks](../workflows/recovery.md)) |
40
40
  | `onError` | What the **final** failure does to the run: `'fail'` (default — the run unwinds `failed`) · `'continue'` (the run goes on; the step's result is the **continued-failure value** `{ __lua_workflow:'continued_failure', failed:true, error:{code,message}, text:'' }` — `${stepResults.<id>.text}` renders `''`, `getStepResult(id)` returns it, a `conditional` can branch on `stepResults.<id>.failed`) · `'park'` (the step parks on an exception gate for `retry-step` / `resolve-step` — see [When a step parks](../workflows/recovery.md)) |
41
41
  | `requiredConnections` | Connection ids that must mount before `execute` runs |
42
- | `tier` / `workspace` / `jobResources` / `jobTools` | Job-tier fields — see [Workspaces and long steps](../workflows/workspaces-and-long-steps.md) |
42
+ | `tier` / `workspace` / `jobResources` / `jobTools` | Job-tier fields — see [Workspaces and long steps](../workflows/workspaces-and-long-steps.md) — on a `workspace:{mount:'ro'}` step `jobTools` is narrowed: `write`/`edit`/`git`/`shell` are dropped, `read`/`glob`/`grep`/`fetch`/`gh` stay |
43
43
  | `execute(ctx)` | `ctx`: `inputData`, `resumeData`, `getInitData()`, `getStepResult(id)`, `state`, `suspend()`, `bail()`, `bailRun()`, `once()` (worker tier — not on the Job tier yet, LUA-706), `log()`, `env`, `signal`, `runtime`, `artefacts`, `occurrenceId`, `lineageId`, `workspace?` (Job tier; `exec?` / `$?` with it) |
44
44
 
45
45
  `ctx.suspend(payload)` parks the step for `Workflows.resume(runId, stepId, resumeData)`; on resume **`execute` re-runs from the top** with `ctx.resumeData` set.
@@ -54,21 +54,21 @@ Config: `name`, `description?`, `inputSchema`, `outputSchema?`, `budget?` (`maxC
54
54
 
55
55
  **The call site places.** Every builder call — `then`, `agentStep`, `specialistStep`, `toolStep`, `map`, `approval`, `waitForSignal`, `sleep`, `sleepUntil`, `foreach`, `parallel`, `branch`, `switch`, `dowhile`, `dountil`, `workflow` — appends **exactly one** entry to the chain where it is called. Containers take `StepRef`s: an inline step object, or a **string** naming an `agentStep`/`specialistStep`/`toolStep` declared elsewhere in the same chain — a string ref inside a container means "declare here, inside me" and does not append a second top-level entry. An id declared but never placed is `WORKFLOW_UNPLACED_STEP` (warning locally, error at push); referenced but never declared is `unknown-step-ref` at `.commit()`.
56
56
 
57
- | Verb | Entry |
58
- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
- | `.then(step, input?)` | Sequential step |
60
- | `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, … })` | Agent turn (`prompt` is a `template(...)`) |
61
- | `.specialistStep(id, { role, prompt, … })` | Ephemeral role on the owning agent (D25) |
62
- | `.toolStep(id, { toolId, input? })` | One tool call — `toolId` must name a `LuaTool` the compiler can see as a tool primitive (declared in its own module and exported, or registered on the agent); a tool object defined inline in the workflow file is not detected and fails `WORKFLOW_TOOL_REF_UNRESOLVED` at compile |
63
- | `.map(descriptors, { id })` | Data reshaping — `id` is **required** once the workflow has ≥ 2 maps (`map-id-required`) |
64
- | `.parallel([...arms], { merge? })` | Concurrent arms; a `[map, step]` pair is a legal arm — the step runs with the map as its own `input` (the arm node carries `input`, like a `toolStep` with one). Not a `foreach` / loop body: those receive their item / the previous output (`mapping-placement` at `.commit()`) — map the items before the container instead |
65
- | `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)` | Conditional (exclusive / inclusive) |
66
- | `.foreach(step, { concurrency?, maxItems?, chunk?, rateLimit? })` / `.foreach({ items })` | Fan-out over an upstream array |
67
- | `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)` | Loop |
68
- | `.sleep(ms)` / `.sleepUntil(template)` | Engine-side waits — `sleepUntil` lowers to an `<id>_at` mapping + `sleepUntil{dateFrom}` (D6-r1) |
69
- | `.approval(id, { title, approver, timeoutHours, onTimeout, editable?, editablePaths?, itemsPath?, … })` | Human gate — see [Approvals](../workflows/approvals.md) |
70
- | `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })` | External event via `Workflows.signal` — completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }`; payload keys named like a credential (`token`, `secret`, `apiKey`, `accessToken`, …) are persisted `[REDACTED]` (the `signal-reserved-key` warning says so at push / compose) — see [Reserved payload keys](Workflows.md#reserved-payload-keys) |
71
- | `.workflow(id, ref, input?, { workspace? })` | Child run the parent waits for |
57
+ | Verb | Entry |
58
+ | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
+ | `.then(step, input?)` | Sequential step |
60
+ | `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, … })` | Agent turn (`prompt` is a `template(...)`) |
61
+ | `.specialistStep(id, { role, prompt, … })` | Ephemeral role on the owning agent (D25) |
62
+ | `.toolStep(id, { toolId, input? })` | One tool call — `toolId` must name a `LuaTool` the compiler can see as a tool primitive (declared in its own module and exported, or registered on the agent); a tool object defined inline in the workflow file is not detected and fails `WORKFLOW_TOOL_REF_UNRESOLVED` at compile |
63
+ | `.map(descriptors, { id })` | Data reshaping — `id` is **required** once the workflow has ≥ 2 maps (`map-id-required`) |
64
+ | `.parallel([...arms], { merge? })` | Concurrent arms; a `[map, step]` pair is a legal arm — the step runs with the map as its own `input` (the arm node carries `input`, like a `toolStep` with one). Not a `foreach` / loop body: those receive their item / the previous output (`mapping-placement` at `.commit()`) — map the items before the container instead |
65
+ | `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)` | Conditional (exclusive / inclusive) |
66
+ | `.foreach(step, { items?, concurrency?, maxItems?, chunk?, rateLimit? })` | Fan-out over an upstream array. `items` lowers to a `<bodyId>_items` mapping ahead of the entry and takes `fromInit(path)` / `fromStep(id, path)` or an `init(...)` / `step(...).path(...)` ref (LUA-864) — the node is byte-identical whichever form you write; any other descriptor (`value`, `template`, `fromRequest`, `rows`, `fromKnowledge`, a fan-in `fromStep([...])`) is `invalid-envelope` at build: `.map({ '': ... }, { id })` before the foreach instead |
67
+ | `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)` | Loop |
68
+ | `.sleep(ms)` / `.sleepUntil(template)` | Engine-side waits — `sleepUntil` lowers to an `<id>_at` mapping + `sleepUntil{dateFrom}` (D6-r1) |
69
+ | `.approval(id, { title, approver, timeoutHours, onTimeout, editable?, editablePaths?, itemsPath?, … })` | Human gate — see [Approvals](../workflows/approvals.md) |
70
+ | `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })` | External event via `Workflows.signal` — completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }`; payload keys named like a credential (`token`, `secret`, `apiKey`, `accessToken`, …) are persisted `[REDACTED]` (the `signal-reserved-key` warning says so at push / compose) — see [Reserved payload keys](Workflows.md#reserved-payload-keys) |
71
+ | `.workflow(id, ref, input?, { workspace? })` | Child run the parent waits for |
72
72
 
73
73
  ### Typed predicates
74
74
 
@@ -8,6 +8,10 @@ _Source of truth: workflows-spec (P1-8; Cluster K B40). This page is the develop
8
8
 
9
9
  An array-typed step output over **8 MB** becomes a `{__datasetRef}` (NDJSON on the CDN) instead of `OUTPUT_TOO_LARGE`. Bind pages with `rows(stepId, path, { offset, limit })` — the bare ref renders as the ref object. `--input` values may carry `{"__artefactRef": "<id>"}` to hand a run an existing artefact.
10
10
 
11
+ ## Reading an offloaded output from outside the run
12
+
13
+ A step output past **256 KB** is stored as `{__cdnRef: {artefactId, size, sha256, mediaType}}` and an array output past 8 MB as the `{__datasetRef}` above; `lua workflows status <runId> --steps` (R4 `fields=full`) and the step read (R5) serve the ref itself plus an `outputRef` beside it — `{kind, artefactId, url, size | rowCount, contentType}` — and print the route under the steps table (`⇩ <stepId>: offloaded output … → GET <url>`). `GET <url>` is R32 (`/workflows/:agentId/runs/:runId/artefacts/:artefactId`): a `cdn` ref answers `{url, expiresAt, contentType, bytes, sha256, source:{stepId, attempt}}` — a 300 s presigned download of the canonical JSON; a `dataset` ref answers the same presigned download of the whole NDJSON plus `rowCount`, `schema` and the first `page: {rows, offset, limit, nextOffset}` (`?offset=&limit=`, ≤ 1000 rows a page). Both need `workflows:read-outputs` on the run (403 `READ_OUTPUTS_REQUIRED`); an id that is not one of the run's own is 404 `ARTEFACT_NOT_FOUND`.
14
+
11
15
  ## Streaming and datasets
12
16
 
13
17
  `get(id).stream({ range })` reads large artefacts without buffering; `get(id).rows({ offset, limit })` pages NDJSON/parquet datasets (parquet row groups pass through untranscoded). Ceilings: 256 MB inline, 4 GiB streamed.
@@ -44,13 +44,13 @@ const runTests = createStep({
44
44
  - **Errors.** A non-zero exit is data (`result.code`). `exec.strict` / `$.strict` throw an `ExecError` — `name === 'ExecError'`, `code` one of `EXEC_FAILED` | `EXEC_TIMEOUT` | `EXEC_REFUSED` | `EXEC_UNAVAILABLE`, with `exitCode`, `stdout`, `stderr`. Check `name` / `code`, not `instanceof` (the pod runs a step in its own realm). `undefined` interpolated into `$` is refused, never stringified.
45
45
  - **Tier.** Worker-tier steps have neither `exec` nor `$`. Shell work with a model in the loop is a Job-tier `agentStep` (its `shell` tool, below). The command line, exit code and duration are logged to the run's job-logs; the output is not — `log()` what matters.
46
46
 
47
- An `ro` mount is read-only at the volume (the PVC is mounted `readOnly`): a command that writes fails `EROFS`, and `git push` is refused on it.
47
+ An `ro` mount is read-only at the volume (the PVC is mounted `readOnly`): a command that writes fails `EROFS`, and `git push` is refused on it. A coding turn on an `ro` mount also has its `jobTools` narrowed to exactly this: `write`, `edit`, `git` and `shell` are dropped (read-only git — `status`, `log`, `diff`, `show` — still runs as a built-in), while `read`, `glob`, `grep`, `fetch` and `gh` stay, so an `ro` step can still open or comment on a PR through the sidecar's gh proxy. If every tool an `ro` step declares is one the mount drops (say `jobTools: ['shell', 'write', 'edit']`), the turn would run with no tools at all — `lua compile` warns `ro-step-has-no-tools` (a warning, the workflow still compiles); keep a read-only tool or mount `rw`.
48
48
 
49
49
  **No code from strings, no WebAssembly — Job tier only.** Inside a Job-tier code step, `eval`, `new Function` (the `Function` constructor reached any way, `Buffer.constructor` included) and `new WebAssembly.Module` / `WebAssembly.compile` / `WebAssembly.instantiate` throw — `EvalError` / `CompileError`, surfaced as `USER_CODE_ERROR` with the reason named — because the pod runs the step with code generation off in both realms (`--disallow-code-generation-from-strings` on the child process, `codeGeneration: { strings: false, wasm: false }` on the vm context). `WebAssembly.validate` still answers (it compiles nothing). A library that builds code from strings at import time fails the same way. The worker tier has neither restriction: a step that needs either stays `tier: 'worker'`.
50
50
 
51
51
  ## Coding turns
52
52
 
53
- A Job-tier `agentStep` is a coding turn (Claude Code or the generic harness — see [Coding harness](./coding-harness.md)) with the `WORKFLOW_JOB_TOOLS` set (`shell`, `read`, `write`, `edit`, `glob`, `grep`, `git`, …). MCP connections mount via `toolScope.connectionIds`: your connections are mounted through a local proxy; the model never sees a token, and a tool that needs approval is refused inside the turn — hand it to a worker-tier step.
53
+ A Job-tier `agentStep` is a coding turn (Claude Code or the generic harness — see [Coding harness](./coding-harness.md)) with the `WORKFLOW_JOB_TOOLS` set (`shell`, `read`, `write`, `edit`, `glob`, `grep`, `git`, `gh`, `fetch`, `ripwire`). `ripwire` admits `ripwire …` alone — the checkout's codebase map (one verb per call, the `=` form only: `--for`/`--pack-task`/`--pr-context` take `--token-budget` — at most 2000, injected when absent; `--callers`/`--callees`/`--uses`/`--grep` take `--limit` — at most 50, injected when absent — never `--token-budget` or `--top-k`; `--expand`/`--outline` take `--top-k=0` only; `--affected`, `--at`, `--path`, `--connect`, `--situ[=FILE]`, `--help-task` take no sizing knob; `--legend=full|compact`, `--json`, `--no-cache` ride any verb and nothing injects `--legend` — ask for `compact` in the prompt) — read-only THROUGH the Job image's `ripwire` shim, which admits only those verbs and knobs and refuses everything else with exit 2: it survives `mount:'ro'`, never implies `shell`, and as belts `--impact`, `--listen`, `--mcp` and `--run-trace` are also denied on every turn (`shell` steps included), in either flag position. **Invoke it bare** — `ripwire . --for=""` at the head of the command line: a `timeout` wrapper (stacked behind `nice` / `nohup` or not) is denied, an `env` / `VAR=value` prefix, a `cd` out of the checkout and a redirect to a file are refused, and the shim already owns the 90 s wall and the output size. MCP connections mount via `toolScope.connectionIds`: your connections are mounted through a local proxy; the model never sees a token, and a tool that needs approval is refused inside the turn — hand it to a worker-tier step.
54
54
 
55
55
  GitHub review loops: the PR body carries `<!-- lua-run:<runId> -->`; the GitHub webhook routes `pull_request_review` to `Workflows.signal(runId, 'github.review', …)` — see the `ticket-to-pr` example.
56
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lua-cli",
3
- "version": "3.32.6",
3
+ "version": "3.33.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",
@@ -116,8 +116,8 @@
116
116
  "@lua/sandbox-runtime": "0.0.1",
117
117
  "@lua/shared-sandbox": "0.0.1",
118
118
  "@lua/shared-source-sync": "0.0.1",
119
- "@lua/shared-types": "0.0.1",
120
- "@lua/workflow-graph": "0.0.1"
119
+ "@lua/workflow-graph": "0.0.1",
120
+ "@lua/shared-types": "0.0.1"
121
121
  },
122
122
  "scripts": {
123
123
  "clean": "rm -rf dist temp",
@@ -125,6 +125,7 @@
125
125
  "build:vendor": "node scripts/build-vendor.mjs",
126
126
  "build:dts": "tsc -p tsconfig.build.json",
127
127
  "build:dts-rollup": "node scripts/run-api-extractor.mjs",
128
+ "typecheck": "tsc -p tsconfig.typecheck.json",
128
129
  "test": "node ../../scripts/test-gate.mjs jest",
129
130
  "test:watch": "jest --watch",
130
131
  "test:coverage": "node ../../scripts/test-gate.mjs jest --coverage"
@@ -7,15 +7,26 @@ export default new LuaTrigger({
7
7
  name: 'linear-ready-for-agent',
8
8
  description: 'Linear "issue labeled" webhook → start ticket-to-pr',
9
9
  source: 'webhook',
10
- filter: (ctx) => ctx.payload?.type === 'Issue' && ctx.payload?.action === 'update'
11
- && (ctx.payload.data?.labels ?? []).some((l: { name: string }) => l.name === 'ready-for-agent'), // any other label edit: the trigger's ordinary "filtered" verdict, no run
10
+ // LUA-875: the HTTP trigger context carries the parsed request body as `ctx.body` (`TriggerContext` — no `payload`).
11
+ filter: (ctx) =>
12
+ ctx.body?.type === 'Issue' &&
13
+ ctx.body?.action === 'update' &&
14
+ (ctx.body.data?.labels ?? []).some((l: { name: string }) => l.name === 'ready-for-agent'), // any other label edit: the trigger's ordinary "filtered" verdict, no run
12
15
  transform: (ctx) => {
13
- const issue = ctx.payload.data;
14
- return { startWorkflow: {
15
- name: 'ticket-to-pr',
16
- input: { ticketId: issue.identifier, title: issue.title, spec: issue.description ?? '', repo: 'https://github.com/acme/backend', baseRef: 'main' },
17
- idempotencyKey: `linear:${issue.identifier}:ready-for-agent`, // a redelivered webhook or a label toggled twice returns the SAME run (§3.7; 07 §7.2.3 step 2)
18
- // no `notify`: a trigger start runs as the system principal, and `notify` addresses the creator only (07 §7.4.1 — inert here); the human-facing surface is the PR itself + the approval inbox
19
- } };
16
+ const issue = ctx.body.data;
17
+ return {
18
+ startWorkflow: {
19
+ name: 'ticket-to-pr',
20
+ input: {
21
+ ticketId: issue.identifier,
22
+ title: issue.title,
23
+ spec: issue.description ?? '',
24
+ repo: 'https://github.com/acme/backend',
25
+ baseRef: 'main',
26
+ },
27
+ idempotencyKey: `linear:${issue.identifier}:ready-for-agent`, // a redelivered webhook or a label toggled twice returns the SAME run (§3.7; 07 §7.2.3 step 2)
28
+ // no `notify`: a trigger start runs as the system principal, and `notify` addresses the creator only (07 §7.4.1 — inert here); the human-facing surface is the PR itself + the approval inbox
29
+ },
30
+ };
20
31
  },
21
32
  });
@@ -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.32.6",
23
+ "lua-cli": "^3.33.0",
24
24
  "openai": "^5.23.0",
25
25
  "uuid": "^13.0.0",
26
26
  "zod": "^3.24.1"