Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "lua-agent-builder",
"source": "./plugins/lua-agent-builder",
"description": "Build, test, and deploy Lua AI agents from inside Claude Code",
"version": "1.11.0",
"version": "1.12.0",
"homepage": "https://github.com/lua-ai-global/claude-code-lua-plugin#readme",
"repository": "https://github.com/lua-ai-global/claude-code-lua-plugin.git",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Then `/lua-auth`: an existing credential is kept; a new login runs `lua auth con

| Plugin | Description |
|---|---|
| [`lua-agent-builder`](./plugins/lua-agent-builder/) | 23 slash commands, 10 subagents, 13 hooks, a 9-file knowledge base verified against lua-cli source (3.33.0 base; 1.3.0 added per-step model classes and the workflow autonomy envelope from lua-cli 3.36.0, 1.4.0 the Job-tier billing rules and the lua-cli 3.37.0 cost read-outs, 1.5.0 log drains and the `lua logs` read window from lua-cli 3.38.0, read from `main`; 1.6.0 headless mode and a hardened production-gate parser; 1.7.0 `/lua-devices`, device credentials that never reach the conversation, and a re-check against lua-cli 3.44.0 / 3.45.0; 1.8.0 web apps (Lua Apps); 1.9.0 the full QA suite, `/lua-qa full`; 1.10.0 decisions, `/lua-decisions` and `/lua-new decision` from lua-cli 3.48.0 / 3.49.2), a local read-only platform MCP server and the public docs MCP |
| [`lua-agent-builder`](./plugins/lua-agent-builder/) | 23 slash commands, 10 subagents, 13 hooks, a 9-file knowledge base verified against lua-cli source (3.33.0 base; 1.3.0 added per-step model classes and the workflow autonomy envelope from lua-cli 3.36.0, 1.4.0 the Job-tier billing rules and the lua-cli 3.37.0 cost read-outs, 1.5.0 log drains and the `lua logs` read window from lua-cli 3.38.0, read from `main`; 1.6.0 headless mode and a hardened production-gate parser; 1.7.0 `/lua-devices`, device credentials that never reach the conversation, and a re-check against lua-cli 3.44.0 / 3.45.0; 1.8.0 web apps (Lua Apps); 1.9.0 the full QA suite, `/lua-qa full`; 1.10.0 decisions, `/lua-decisions` and `/lua-new decision` from lua-cli 3.48.0 / 3.49.2; 1.11.0 `/lua-deploy webapp` scoped web-app deploys from lua-cli 3.49.0; 1.12.0 org-shared `Data.shared` and web-app viewer decisions from lua-cli 3.50.0), a local read-only platform MCP server and the public docs MCP |

## Quick walkthrough

Expand Down
2 changes: 2 additions & 0 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ If you just want to start: [Installation](#installation) → [Your first agent](

`/lua-doctor` checks all of these and offers consent-gated fixes. Platforms: macOS 14+, Ubuntu 22.04+, Windows 11; CI runs the suite on all three with Node 18 and 20.

New in 1.12.0: **org-shared Data and viewer decisions.** When several agents of one organization need the same dataset, the builder now recommends `Data.shared(name)` (same methods as `Data`; an org admin creates the collection and names its readers and writers through the admin API) and explains its errors, including why every call fails in a local test run. A web app's routes can decide workflow approvals and answer input steps as the signed-in person with `Workflows.viewer`. Needs lua-cli 3.50.0.

New in 1.10.0: **decisions.** A decision is one call that takes some state and typed questions and returns typed answers with probabilities: the odds that something is true, a choice with its confidence, or a score on a scale. `/lua-new decision <name>` writes a `LuaDecision` with literal questions and registers it; `/lua-push decision` stages a version; `/lua-decisions` lists the agent's decisions, shows their versions, runs one on a message or JSON state (on the version's model, or `--model <id>` for any of the thirteen decision models, Jev and OpenAI included), publishes a version (the rollback) or deletes one. Reads and runs go at once; publish and delete confirm once through the Bash prompt. Needs lua-cli 3.48.0; `--model` and the OpenRouter ids need 3.49.2.

New in 1.9.0: **the full QA suite.** `/lua-qa full` runs at a tier: `smoke` (a 30-minute cap from the moment you approve the plan), `medium` (the default, 1 to 2 hours) or `production-ready` (3 to 5 hours, ending in a release gate). It maps the agent into diagrams you confirm and infers the qualifying answers and metrics from the code, citing a `file:line` for each (`--interview` asks you instead). Then it plays customer personas several times each plus a red team, runs offline workflow flow tests, direct tool tests and (outside smoke) a stress test in parallel, grades every run with two independent graders (one in smoke), clusters the failures by root cause and writes a PDF, HTML and JSON report. Bare `/lua-qa` (or `/lua-qa quick`) is the pass from earlier releases, unchanged and with no new question.
Expand Down
2 changes: 1 addition & 1 deletion plugins/lua-agent-builder/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lua-agent-builder",
"version": "1.11.0",
"version": "1.12.0",
"description": "Build, test, and deploy Lua AI agents from inside Claude Code",
"author": {
"name": "Lua AI",
Expand Down
15 changes: 15 additions & 0 deletions plugins/lua-agent-builder/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

All notable changes to the `lua-agent-builder` plugin. Versions follow the tag `release-prod.yml` cuts from `package.json` (`v<version>`). lua-cli is a TypeScript SDK/CLI; it is unrelated to the Lua programming language.

## 1.12.0 — 2026-10-11

**Org-shared Data and viewer decisions from web apps** (lua-cli 3.50.0).

### Changed

- **`primitives.md`: `Data.shared(name)`.** A new section next to `Data`: org-shared collections that several agents of one organization read and write, with the same methods as `Data` (`collections`, `create`, `get`, `getEntry`, `update`, `patch` with `ifVersion`, `increment`, `search`, `delete`). It covers when to pick it over private `Data` or a supervision grant (`Agents.data(id)`), the org-admin setup (`POST/GET/PUT/DELETE /admin/orgs/:orgId/shared-collections`, explicit agent ids, at most 200 per list, never `*`; the builder hands the request to the user and never runs it), the reader/writer split, the errors (`shared_data_forbidden` 403, also for an unknown or other-org name; `shared_data_test_session_write_refused` 409; `shared_data_unavailable_offline` on every call in a local test run), audit, embedding billing, and no personal data or secrets. The routing table gains the "several agents share one dataset" row.
- **`decision-trees.md`**: "How should I store this data?" routes data shared by several agents of one org to `Data.shared`.
- **`web-apps.md` §3 and `workflows.md` §6: `Workflows.viewer.decideApproval` / `Workflows.viewer.resume`.** A non-GET web-app route can decide a workflow approval or answer an input step as the signed-in viewer (their access, approver, four-eyes and step-up rules, and audit, as in the Inbox); `agentId` names another agent's run for a fleet dashboard; `viewer_unavailable` anywhere else; `STEP_UP_REQUIRED` / `APPROVAL_SURFACE_DENIED` go to the Inbox. Locally the route runs as the developer, so a test call really decides.
- **The debug subagent** (`agents/lua-debug.md`) reads a `SharedDataError` by code: offline is not a code bug, `shared_data_forbidden` is an ACL fix for an org admin, a test session may only read.

Not covered: the supervision additions in lua-cli 3.50.0 (`Agents.secrets(id).put`, `Agents.versions(id).list({ status, offset })`, deleting stale staged versions), because the plugin does not teach the supervision surface. `PINNED_MIN_LUA_CLI` is unchanged: the new sections are marked lua-cli 3.50.0+.

Versioning: 1.11.0 (`/lua-deploy webapp`, #21) is on `main`, so this is 1.12.0.

## 1.11.0 — 2026-10-11

**`/lua-deploy webapp` deploys just the web app (lua-cli 3.49.0+).** lua-cli 3.49.0 adds `lua deploy webapp`, a scoped promote like `lua deploy webhook`: a new agent version identical to the live one except that web app's pin is created and promoted, so nothing else staged on the agent goes live. Until now the plugin made a web app live by creating a whole agent version, diffing it against the live one and stopping when anything else would have changed.
Expand Down
1 change: 1 addition & 0 deletions plugins/lua-agent-builder/agents/lua-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ A `lua compile --ci`, `lua test --ci` or `lua push` invocation failed, or a work

- **"Our logs stopped arriving in Datadog / our receiver"** (⏳ lua-cli 3.38.0 or later; facts in `${CLAUDE_PLUGIN_ROOT}/lib/knowledge/log-drains.md`) → this is a **log drain**, an organization resource, and nothing about the agent's code. Read `lua drains status <id> --json`: `state` (`pending_verification` buffers and never delivers — the endpoint changed and nobody re-verified; `paused` with `pauseReason: 'quota'` is the 150% rung of the quota ladder; `paused` with `'auto'` is 24 h without a success), `health.lastStatusCode` / `health.lastError`, `health.backlog`, and `quota.degradation` (at 100% `debug` is dropped, at 125% `info` too — `warn` and `error` are never dropped before the drain pauses). Then `lua drains deliveries <id> --limit 20 --json` for the per-attempt `errorClass` (`network timeout dns ssrf redirect http_4xx http_429 http_5xx rejected partial encode`). ⚠ `lua drains status` exits **2** both for a usage error *and* for "some drain is `failing`" — decide from `drains[].state`, never from the exit code. Exit 10 is the missing **`logs:manage`** scope, which is sensitive (`logs:*` does not satisfy it) and is re-minted by the user in their own terminal. Exit 3 on every verb means the feature is not rolled out to that organization. Fixing a drain is `/lua-drains`, not an edit here: **never** print a signing secret, and never put a header value on a command line (`--header-from-env <NAME>=<ENV_VAR>` is the CI form).
- **A `DecisionError` from `Decisions.ask` / `Decisions.run`** (⏳ lua-cli 3.48.0 or later; facts in `${CLAUDE_PLUGIN_ROOT}/lib/knowledge/decisions.md`) → read `code` and `decisionId`, then the row with `lua logs --ci --json --type decisions --limit 20`. `decision_invalid` (400) names the field: a question built outside `Decisions.odds|choice|score`, options outside 2–64, levels outside 2–10, state over 120,000 chars, images over 6 MB, `provider: 'openrouter'` without a `model`, or a `model` of another provider (a `typesafe/…` / `openai/…` id on openrouter says which provider to set instead). `decision_not_found` (404) is a `Decisions.run` name the agent does not declare, a deleted decision, or one pushed but never made current — `lua decisions versions <name> --json` shows whether any version is current; the fix is `lua decisions publish <name> <version>` (`/lua-decisions`, ask tier) or `lua version promote` (`/lua-deploy`), never a code change. `decision_rejected` (422) is the model refusing the request itself (Jev pinned with images, an unknown or data-retaining OpenRouter id, state over the model's limit): quote the upstream message; it never falls back. `decision_timeout` / `decision_unavailable` / `decision_disabled` (503) are the provider side — the SDK never retries, `fallback` already ran when `rule` is `'fallback'`; suggest a longer `timeoutMs` (≤ 15,000) only when `durationMs` on nearby rows is near the budget. A `decision_cancelled` on every call is the caller going away, not the model. A compile error naming a decision export means the `LuaDecision` was built by a helper or lists an openrouter id of a direct provider — rewrite it as a literal `new LuaDecision({...})`.
- **A `SharedDataError` from `Data.shared(name)`** (⏳ lua-cli 3.50.0 or later; facts in `${CLAUDE_PLUGIN_ROOT}/lib/knowledge/primitives.md`, `Data.shared`) → read `code`. `shared_data_unavailable_offline` is every call in a local test run (there is no org ACL locally) — not a code bug: guard the call and verify on the deployed agent. `shared_data_forbidden` (403) means the agent is not a reader (or, for a write, not a writer) of that name, or the name does not exist in this org — an org admin fixes the ACL (`PUT /admin/orgs/:orgId/shared-collections/:name`), not code. `shared_data_test_session_write_refused` (409) is a write from a test session (reads are fine).

4. **Unknown message?** Search the docs with `mcp__plugin_lua-agent-builder_lua-docs__search_lua_cli "<message fragment>"` or `mcp__plugin_lua-agent-builder_lua-docs__query_docs_filesystem_lua_cli "rg -n '<fragment>' /"`; `WebFetch https://docs.heylua.ai/cli/troubleshooting` is the fallback. Never guess at internals.

Expand Down
1 change: 1 addition & 0 deletions plugins/lua-agent-builder/lib/knowledge/decision-trees.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Not a web app: a public site for anonymous visitors (every route needs a signed-
```
Tied to a specific user? → User.get(id) → .update({...}) / .patch({ set, unset }) (cross-channel profile)
Agent-wide config, lookups, cache, vector search? → Data.create/get/search — declare `index` on the fields you filter (from a `lua test` run: the deployed runtime drops `index`, primitives.md §12)
Shared by several agents of one org? → Data.shared(name) — an org admin creates it and names readers/writers (POST /admin/orgs/:orgId/shared-collections); same methods as Data; offline every call rejects shared_data_unavailable_offline (primitives.md, Data.shared)
Cart / order / catalog and no external shop? → Products / Baskets / Orders
Binary (image, PDF, audio)? → CDN.upload → store the fileId in Data/User
Run-scoped state inside a workflow? → ctx.state.get/set (≤ 64 KB) and step outputs; large outputs → ctx.artefacts
Expand Down
16 changes: 16 additions & 0 deletions plugins/lua-agent-builder/lib/knowledge/primitives.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,21 @@ Data.delete(collection, entryId): Promise<{ status, message }>
```
`LuaQuery` is a bounded Mongo-style filter: `$eq $ne $gt $gte $lt $lte $in $nin $exists`, root `$and`/`$or`. **Indexes** — ⚠ runtime caveat first: the `{ searchText?, index? }` options object is what lua-cli 3.33.0 types and what `lua test` (lua-cli's local VM) sends, but the **production sandbox runtime** (`sandbox-runtime` `src/devtools/apis/custom.data.api.service.ts`) still has `create(collection, data, searchText?: string)` **and** `update(collection, entryId, data, searchText?: string)` and posts that argument as `searchText` (lua-api's DTO is `@IsString() searchText?`), so a deployed `Data.create(c, doc, { index: [...] })` **or** `Data.update(c, id, doc, { … })` fails with `400 searchText must be a string` (seen live on 2026-09-12) while the same calls pass locally — lua-cli's own local service normalises the object form, the deployed one does not. Until the runtime ships the object form, deployed code must use the legacy shapes `Data.create(c, doc)` / `Data.create(c, doc, 'search text')` / `Data.update(c, id, doc, 'search text')`. **The deployed service never forwards `index` at all** — not from the object form (400) and not from the string form (its `create`/`update` bodies are `{ data, searchText }`, `custom.data.api.service.ts` ~38-48 / ~160-175; the word `index` does not occur in that file) — so **an index can only be declared from a `lua test` run** (lua-cli's local `custom.data.api.service.ts` sends `{ data, searchText, index }`; validated live 2026-09-13). Recipe: put the declaring write in a small seed tool and run it once locally — `lua test skill --name seed_indexes --input '{}'` where the tool calls `Data.create(c, doc, { index: ['business_id'] })` against the real agent (local runs hit the real dev API) — then ship deployed code that writes with the legacy shapes and only *filters* on those fields. (`CreateCustomDataOptions` is also not exported, so the options type cannot even be named.) Index semantics: declare the fields you filter on — `{ index: ['business_id'] }`; compound = nested array `{ index: [['country','business_id']] }` (serves leftmost prefixes only). Builds are async (minutes); limits 2 fields/index, 3 declarations/call, 5 indexes/agent; invalid ones are rejected (visible in `Data.collections()`), unused ones expire after ~14 days. A large unindexed filter eventually fails with a message naming the field. Never store secrets in `Data` — use `lua env` + `env()`.

### `Data.shared(name)` — org-shared collections (lua-cli 3.50.0+)
`Data` is private to one agent. When **several agents of the same organization** must read (and some write) one dataset — a catalog one agent maintains and others answer from, a fleet map, shared lookup tables — use an org-shared collection instead of copying rows between agents or routing every read through `Agents.invoke`.
```ts
const map = Data.shared('fleet_map'); // a namespace; holds several collections
const card = await map.create('repos', { repo: 'lua-core-services', tier: 1 }, { searchText: 'backend monorepo' }); // writers
await map.patch('repos', card.id, { set: { tier: 2 } }, { ifVersion: card._version }); // writers, compare-and-swap
const { data } = await map.get('repos', { tier: 1 }); // readers (a writer can read)
const hits = await map.search('repos', 'backend services', 10); // vector search, same as Data.search
```
- **Same methods and signatures as `Data`**: `collections`, `create`, `get`, `getEntry`, `update`, `patch` (with `ifVersion`), `increment`, `search`, `delete` — same filters, limits, indexes and vector search. An entry it returns writes back to the shared namespace (`entry.update()` / `entry.delete()`).
- **Access is an org-admin ACL, never set by an agent.** Readers may call `collections`, `get`, `getEntry`, `search`; writers also `create`, `update`, `patch`, `increment`, `delete`. Setup is the admin API with an org admin's credential (`members:manage` to list, `members:assign-role` to change): `POST /admin/orgs/:orgId/shared-collections` `{ "name": "fleet_map", "readers": ["<agentId>"], "writers": ["<agentId>"] }` (name 1-128 of `[A-Za-z0-9_.-]`, unique in the org; explicit live agent ids of that org, at most 200 per list, never `*`); `GET` lists; `PUT …/:name` `{ readers, writers }` replaces both lists; `DELETE …/:name` ends access at once and purges the entries (irreversible). A change applies to the next `Data.shared` call. The builder never runs these for the user — hand them the request to make as an org admin.
- **Errors** (`SharedDataError`, read `err.code`): `shared_data_forbidden` (403) — the agent is not in the ACL for that call, **or** the name does not exist, **or** it belongs to another org (deliberately indistinguishable); `shared_data_test_session_write_refused` (409) — a test session may read, not write; `shared_data_unavailable_offline` — **every** call in a local test run (no org ACL locally), so a tool that uses `Data.shared` cannot be exercised offline: guard it (`catch` that code and fall back or skip) and verify it on the deployed agent with `/lua-chat`.
- Every write is audited (agent, collection, entry id — never the value); a search embedding is billed to the calling agent. Do not store end-customer personal data in a shared collection (per-agent erasure does not cover it) and never secrets.
- Choose: one agent's own state → `Data`; many agents of one org share reference data → `Data.shared`; a supervisor reading or acting on a *specific* other agent's own Data under a supervision grant → `Agents.data(id)`, not a shared collection.

### `Products`, `Baskets`, `Orders` — built-in commerce
```ts
Products.get(page?, limit?) | Products.get({ page?, limit?, filter?: LuaQuery }) → ProductPaginationInstance (products[], pagination, nextPage())
Expand Down Expand Up @@ -546,6 +561,7 @@ Channel constraints the architect should plan for: WhatsApp → template strateg
| "Reformat every reply for a channel" | `PostProcessor` |
| "Per-user state" | `User.get(...)` + `update`/`patch` |
| "Agent-wide data, lookup tables, vector search" | `Data` (+ `index` declarations — from a `lua test` run only, §12) |
| "Several agents of one org read / write the same dataset" | `Data.shared(name)` (org-admin ACL; lua-cli 3.50.0+) |
| "Known SaaS (Linear, HubSpot, Gmail, Stripe, GitHub …)" | `lua integrations connect` → auto-provisioned MCP (+ `Integrations.passthrough` for raw calls) |
| "My own MCP server" | `LuaMCPServer` |
| "Classify / summarise inside code" | `AI.generate` for prose; a typed judgment the code branches on (odds, a choice, a score, with probabilities) → `Decisions.ask`, or a versioned `LuaDecision` + `Decisions.run` when several places ask it or it must be tunable without a redeploy (decisions.md, `/lua-decisions`) |
Expand Down
Loading
Loading