lua-cli 3.42.0-beta.1 → 3.42.0-beta.3
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 +2458 -112
- package/dist/api-exports.js +18145 -11424
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +9696 -990
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +54 -54
- package/dist/workflow-builder.d.ts +16 -2
- package/dist/workflow-builder.js +5788 -317
- package/dist/workflow-builder.js.map +1 -1
- package/docs/API_REFERENCE.md +7 -1
- package/docs/api/AI.md +3 -1
- package/docs/api/Agents.md +275 -12
- package/docs/api/Data.md +192 -61
- package/docs/api/Jobs.md +2 -0
- package/docs/api/LuaWebhook.md +168 -87
- package/docs/api/LuaWorkflow.md +15 -15
- package/docs/api/Orders.md +7 -5
- package/docs/api/Products.md +1 -1
- package/docs/api/Progress.md +57 -0
- package/docs/api/Threads.md +122 -0
- package/docs/api/User.md +2 -2
- package/docs/api/Workflows.md +22 -1
- package/docs/workflows/approvals.md +4 -0
- package/docs/workflows/coding-harness.md +27 -1
- package/docs/workflows/workspaces-and-long-steps.md +3 -1
- package/package.json +3 -3
- package/scripts/generate-catalog.lib.ts +813 -0
- package/scripts/generate-catalog.ts +94 -0
- package/template/package.json +1 -1
- package/template-web-app/web/src/lua/client.ts +11 -1
package/docs/API_REFERENCE.md
CHANGED
|
@@ -396,6 +396,8 @@ Custom data storage with vector search.
|
|
|
396
396
|
- `Data.getEntry(collection, id)` - Get single entry
|
|
397
397
|
- `Data.search(collection, query)` - Vector search
|
|
398
398
|
- `Data.create(collection, data)` - Create entry
|
|
399
|
+
- `Data.patch(collection, id, { set?, unset? }, { ifVersion? })` - Atomic field set/unset; `ifVersion` = compare-and-swap (409 `data_version_conflict` on a stale version)
|
|
400
|
+
- `Data.increment(collection, id, field, by?, { upsert? })` - Atomic numeric increment, resolves the new value
|
|
399
401
|
- `entry.update(data)` - Update entry
|
|
400
402
|
- `entry.delete()` - Delete entry
|
|
401
403
|
|
|
@@ -717,11 +719,15 @@ if (Lua.request.webhook) {
|
|
|
717
719
|
|
|
718
720
|
- `Lua.request.channel` - Channel type (`'whatsapp'`, `'web'`, `'api'`)
|
|
719
721
|
- `Lua.request.webhook` - Webhook info (if running in webhook)
|
|
722
|
+
- `Lua.request.agentId` - The agent handling this turn
|
|
723
|
+
- `Lua.request.orgId` - The agent's organization, on deployed runtimes that know it (absent offline in `lua test`
|
|
724
|
+
and on older runtimes; read it defensively)
|
|
720
725
|
- `Lua.request.invokedBy` - Present only when **another agent** created this turn through `Agents.invoke`:
|
|
721
726
|
`{ agentId, supervisor, scopes, delegatedUserId? }`. Verified by the platform, never caller-supplied.
|
|
722
727
|
- `supervisor` is true only when an org admin granted the caller `supervision:act` on this agent. Observe, preview and release grants do not set it.
|
|
723
728
|
- `scopes` lists the caller's supervision scopes on this agent. It is `[]` on version-preview turns.
|
|
724
|
-
- `delegatedUserId` is set when the caller passed `input.userId`.
|
|
729
|
+
- `delegatedUserId` is set when the caller passed `input.userId`. It is also set when the caller's own turn
|
|
730
|
+
had such a user, or ran in a Job or workflow run: in those cases the platform did not pick the user.
|
|
725
731
|
- The field is absent on user, channel, job, webhook and self-invoked turns, and offline in `lua test`.
|
|
726
732
|
|
|
727
733
|
**Gating writes on agent invocation.** Treat a turn with `invokedBy` as agent-initiated. A mutating tool should
|
package/docs/api/AI.md
CHANGED
|
@@ -98,6 +98,8 @@ The full-options response mirrors AI SDK `GenerateTextResult` (serializable fiel
|
|
|
98
98
|
| `toolCalls?` | `AiGenerateToolCall[]` | Tool calls made during generation |
|
|
99
99
|
| `toolResults?` | `AiGenerateToolResult[]` | Tool results from generation |
|
|
100
100
|
| `warnings?` | `CallWarning[]` | Provider warnings (e.g. unsupported settings) |
|
|
101
|
+
| `model?` | `string` | The model that actually produced `text` (`provider/model`) |
|
|
102
|
+
| `modelFallback?` | `{ requestedModel, servedModel, reason }` | Present only when `model` differs from the model the call resolved to. `reason`: `'not-approved'` (the model is not in the catalogue, so the platform default ran; it takes precedence when both apply) or `'provider-unavailable'` (a fallback model served during a provider outage) |
|
|
101
103
|
|
|
102
104
|
## Supported Providers
|
|
103
105
|
|
|
@@ -108,7 +110,7 @@ The full-options response mirrors AI SDK `GenerateTextResult` (serializable fiel
|
|
|
108
110
|
| OpenAI | `openai/` | `openai/gpt-5.4` |
|
|
109
111
|
| Anthropic | `anthropic/` | `anthropic/claude-sonnet-5` |
|
|
110
112
|
|
|
111
|
-
Any other code from `lua models list` works too.
|
|
113
|
+
Any other code from `lua models list` works too. A model that is not available (unknown, misspelled or retired) is never swapped silently: the platform default (`alibaba/qwen3.8-flash`) answers and the response says so in `modelFallback`. A platform switch can turn a model you name into a refusal instead: the call then throws a 400 with `code: 'MODEL_NOT_AVAILABLE'`, a message naming the model, and `availableModels`. Check `model` / `modelFallback`, or catch that code.
|
|
112
114
|
|
|
113
115
|
Google models automatically get Google Search grounding — search result URLs appear in the `sources` field of the full-options response.
|
|
114
116
|
|
package/docs/api/Agents.md
CHANGED
|
@@ -31,13 +31,13 @@ Supervision lets one agent observe, preview or release another agent in the same
|
|
|
31
31
|
|
|
32
32
|
**Nothing in code can grant it.** An org admin creates a grant for each supervisor and target pair, with one or more actions:
|
|
33
33
|
|
|
34
|
-
| Action | Allows
|
|
35
|
-
| ----------------- |
|
|
36
|
-
| `observe` | `logs` (without message bodies), `runs`, `data` (only the collections named on the grant), `versions.list/get/diff`, `persona.versions`, `source`, `env.keys`
|
|
37
|
-
| `observe-content` | Log message bodies in `logs`
|
|
38
|
-
| `preview` | `versions.preview`: chat a staged version
|
|
39
|
-
| `release` | `versions.promote` and `
|
|
40
|
-
| `act` | The target sees `invokedBy.supervisor === true` when the supervisor invokes it
|
|
34
|
+
| Action | Allows |
|
|
35
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `observe` | `logs` (without message bodies), `runs`, `data` (only the collections named on the grant), `versions.list/get/diff`, `persona.versions`, `source`, `env.keys`, `workflows.get` |
|
|
37
|
+
| `observe-content` | Log message bodies in `logs` |
|
|
38
|
+
| `preview` | `versions.preview`: chat a staged version |
|
|
39
|
+
| `release` | `versions.promote`, `persona.activate`, `versions.delete` (a `superseded` version only), `workflows.create` (an inert, inactive workflow with no human author, so it cannot carry a schedule), `workflows.push` (a new, never-activated version of an existing workflow) and `workflows.deploy`. Promote, activate and deploy always need `expectActive`. Every release op runs only from a live workflow step of the supervisor whose workflow the grant names (`releaseWorkflowIds`). |
|
|
40
|
+
| `act` | The target sees `invokedBy.supervisor === true` when the supervisor invokes it |
|
|
41
41
|
|
|
42
42
|
**Discovery.** `Agents.list()` and `Agents.get(id)` return your own organization's agents, and only to an agent that holds at least one supervision grant there. Otherwise `list()` returns `[]`.
|
|
43
43
|
|
|
@@ -56,8 +56,21 @@ const preview = await Agents.versions('base_agent-tp').preview(8, 'What can you
|
|
|
56
56
|
// Release (from a named workflow step): compare-and-swap, refused (409 PROMOTE_CONFLICT) unless 7 is live.
|
|
57
57
|
await Agents.versions('base_agent-tp').promote(8, { expectActive: 7 });
|
|
58
58
|
await Agents.persona('base_agent-tp').activate(3, { expectActive: 2 });
|
|
59
|
+
|
|
60
|
+
// Workflows: push an inactive version, then deploy it. The deploy is compare-and-swap on the workflow's
|
|
61
|
+
// live version ID (409 WORKFLOW_DEPLOY_CONFLICT on a mismatch; the message names the live one).
|
|
62
|
+
const wf = await Agents.workflows('base_agent-tp').get('wf_123'); // { activeVersionId, versions: [...] }
|
|
63
|
+
await Agents.workflows('base_agent-tp').push('wf_123', definition); // the body `lua push workflow` sends
|
|
64
|
+
// A workflow the target does not have yet: create it (inert), push, then deploy with expectActive: null.
|
|
65
|
+
// const { workflowId } = await Agents.workflows('base_agent-tp').create('nightly-digest');
|
|
66
|
+
await Agents.workflows('base_agent-tp').deploy('wf_123', '1.4.0', { expectActive: wf.activeVersionId });
|
|
67
|
+
|
|
68
|
+
// Clean up: only a superseded version can be deleted.
|
|
69
|
+
await Agents.versions('base_agent-tp').delete(5);
|
|
59
70
|
```
|
|
60
71
|
|
|
72
|
+
- A workflow deploy also records a new active agent version. Read `versions.list` again before a later `promote`.
|
|
73
|
+
|
|
61
74
|
### Platform switches
|
|
62
75
|
|
|
63
76
|
- **`preview` and `release` are off until enabled per environment.** Set `LUA_SUPERVISION_PREVIEW_ENABLED` and `LUA_SUPERVISION_RELEASE_ENABLED` on lua-core.
|
|
@@ -83,13 +96,12 @@ await Agents.persona('base_agent-tp').activate(3, { expectActive: 2 });
|
|
|
83
96
|
- The write actions (`release`, `act`) are non-transitive: a supervised agent cannot hold one, an agent that holds one cannot be supervised, and two agents cannot supervise each other.
|
|
84
97
|
- These checks and the write are atomic for each org.
|
|
85
98
|
- **Every call is audited on the target.** It writes a `supervisor_access` row to the target's logs (`lua logs --type supervision`). If the audit row cannot be written, the call fails.
|
|
86
|
-
- **
|
|
87
|
-
- **Promotes record the actor.** A supervisor's promote records `promotedBy: service:agent:<supervisorId>` on the version.
|
|
99
|
+
- **Releases record the actor.** A supervisor's promote records `promotedBy: service:agent:<supervisorId>` on the version, and a workflow deploy records it as the creator of the agent version it mints. A version delete is logged with the actor.
|
|
88
100
|
|
|
89
101
|
### Offline (`lua test`, `lua dev`)
|
|
90
102
|
|
|
91
103
|
- **Read members** call the developer API with **your** credential and mirror the projections above: env keys only, and logs without user ids or message bodies.
|
|
92
|
-
- **Refused offline:** `versions.promote`, `persona.activate`, `versions.preview` and `data`. A local test never changes a live agent, and readable collections live on the grant.
|
|
104
|
+
- **Refused offline:** `versions.promote`, `persona.activate`, `versions.delete`, `workflows.create`, `workflows.push`, `workflows.deploy`, `versions.preview` and `data`. A local test never changes a live agent, and readable collections live on the grant.
|
|
93
105
|
- **Not available offline:** `Agents.list`, `Agents.get` and `source.get`.
|
|
94
106
|
|
|
95
107
|
### Errors
|
|
@@ -101,11 +113,262 @@ Every error is a `SupervisionError` with a `code`:
|
|
|
101
113
|
- `supervision_self_refused`
|
|
102
114
|
- `supervision_release_binding_required` and `supervision_release_binding_refused`
|
|
103
115
|
- `supervision_expect_active_required`
|
|
104
|
-
- `
|
|
105
|
-
- `
|
|
116
|
+
- `supervision_version_not_superseded`, when `versions.delete` targets a version that is not superseded
|
|
117
|
+
- `WORKFLOW_DEPLOY_CONFLICT` and `PROMOTE_CONFLICT`, when `expectActive` is not the live version
|
|
106
118
|
- `supervision_data_not_granted`
|
|
107
119
|
- `supervision_audit_unavailable`
|
|
108
120
|
- `supervision_room_refused`
|
|
109
121
|
- `supervision_unavailable`
|
|
110
122
|
- `supervision_release_disabled` and `supervision_preview_disabled`, while the platform switch is off
|
|
111
123
|
- `PROMOTE_CONFLICT` and `PERSONA_ACTIVATE_CONFLICT`, for a stale `expectActive`
|
|
124
|
+
|
|
125
|
+
## Agent directory and access requests
|
|
126
|
+
|
|
127
|
+
These need no grant. Each platform switch is off until it is enabled per environment (lua-core and lua-api):
|
|
128
|
+
`LUA_AGENT_DIRECTORY_ENABLED` and `LUA_SUPERVISION_ACCESS_REQUESTS_ENABLED`. While a switch is off, the call fails
|
|
129
|
+
with `agent_directory_disabled` / `supervision_access_requests_disabled`.
|
|
130
|
+
|
|
131
|
+
**`Agents.list()` versus `Agents.directory()`.** Once your agent holds any supervision grant in the org, `list()`
|
|
132
|
+
returns **every** live org agent's id and name, private ones included. `directory()` shows only what the
|
|
133
|
+
**user of the current turn** may see: a private agent only to its owner or an agent-grant holder.
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Agents the turn's user can see: owner, privacy, build kind, last activity, 30-day volume, your actions.
|
|
137
|
+
const entries = await Agents.directory();
|
|
138
|
+
// [{ agentId, name, ownerUserId, isPrivate, buildKind: 'repo' | 'backup-source' | 'dashboard',
|
|
139
|
+
// lastActivityAt, volume30d, callerActions: ['observe'] }]
|
|
140
|
+
|
|
141
|
+
// Ask for access. The owner of a private agent (or the org admins / agent builders of a shared one)
|
|
142
|
+
// gets an inbox card; approving creates the grant, expiring at `expiresAt` when given.
|
|
143
|
+
const req = await Agents.requestAccess('base_agent-tp', {
|
|
144
|
+
actions: ['observe', 'preview'],
|
|
145
|
+
reason: 'Triage the failing nightly runs',
|
|
146
|
+
expiresAt: '2026-12-31T00:00:00Z',
|
|
147
|
+
});
|
|
148
|
+
const mine = await Agents.accessRequests(); // newest first: pending | approved | denied | cancelled | expired | revoked
|
|
149
|
+
await Agents.cancelAccessRequest(req.requestId); // only your own pending request
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `directory()` needs a turn the platform ran for a member of your org. These fail with
|
|
153
|
+
`agent_directory_requires_user`:
|
|
154
|
+
- a turn with no such user, for example a channel turn;
|
|
155
|
+
- a turn whose user was chosen by the agent that invoked it (`Agents.invoke(…, { userId })`);
|
|
156
|
+
- code in a Job or a workflow run (step, script or job), because the run's user comes from the turn that
|
|
157
|
+
started it and the platform does not keep where that user came from;
|
|
158
|
+
- any turn invoked onward from one of these. `volume30d` counts chat exchanges (the nightly rollup, up to yesterday) and
|
|
159
|
+
workflow runs; job and trigger executions are not counted.
|
|
160
|
+
- One pending request per agent (`supervision_access_request_pending`). A pending request lapses after 7 days.
|
|
161
|
+
- When nobody can approve (the owner of a private agent has left the org, or a shared agent has no org admin or
|
|
162
|
+
agent builder), the request is refused with `supervision_access_no_approver` and nothing is filed.
|
|
163
|
+
At most 3 new requests per agent and 20 in all per 24 hours, cancelled ones included
|
|
164
|
+
(`supervision_access_request_rate_limited`).
|
|
165
|
+
- An approval never overwrites a grant you already hold (`supervision_access_grant_exists`), and the
|
|
166
|
+
non-transitive rules above still apply. The grant records the approver as its creator. Without the two switches
|
|
167
|
+
below it carries no `dataCollections`, `releaseWorkflowIds` or `unattendedWorkflowIds` (an org admin can add them).
|
|
168
|
+
- An expired grant is suspended within about a minute of its `expiresAt`, even while the access-request switch
|
|
169
|
+
is off. A request whose `expiresAt` has passed
|
|
170
|
+
before anyone approves it cannot be approved; it expires instead.
|
|
171
|
+
- Owners see who supervises their agent with `GET /developer/agents/:agentId/supervisors`, and the owner or an
|
|
172
|
+
org admin revokes one with `DELETE /developer/agents/:agentId/supervisors/:grantId`. Both routes first need
|
|
173
|
+
`agents:read` on the agent: on a private agent that is its creator, an agent-grant holder or the org owner, so
|
|
174
|
+
an org admin who is none of these is refused. The approver decides with
|
|
175
|
+
`POST /developer/supervision-access-requests/:requestId/decide` (`{ decision: 'approve' | 'deny' }`). Revoke and
|
|
176
|
+
decide need a signed-in person who is an active member of the org; a typed API key is refused.
|
|
177
|
+
- Not available offline (`supervision_offline_unsupported`).
|
|
178
|
+
|
|
179
|
+
### Asking for "ship" in chat, and upgrading a grant
|
|
180
|
+
|
|
181
|
+
Two more switches, each off until enabled on lua-api: `LUA_SUPERVISION_ACCESS_BINDINGS_ENABLED` and
|
|
182
|
+
`LUA_SUPERVISION_ACCESS_UPGRADES_ENABLED`.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Ask for everything a "watch, suggest and ship" supervisor needs, in one request.
|
|
186
|
+
await Agents.requestAccess('base_agent-tp', {
|
|
187
|
+
actions: ['observe', 'observe-content', 'preview', 'act', 'release'],
|
|
188
|
+
reason: 'Stage and release the fixes you approve',
|
|
189
|
+
releaseWorkflowIds: [svReleaseId, svRollbackId], // YOUR workflows whose code steps may release
|
|
190
|
+
unattendedWorkflowIds: [svImproveId], // YOUR workflow whose Job-tier steps may stage code unattended
|
|
191
|
+
dataCollections: ['leads'], // the target's collections you may read
|
|
192
|
+
notifyOwner: true, // ask the owner to let you message them (see Agents.notifyOwner)
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- **Bindings** (`LUA_SUPERVISION_ACCESS_BINDINGS_ENABLED`; otherwise `supervision_access_bindings_disabled`):
|
|
197
|
+
- Workflow ids must be live workflows of the **requesting** agent (`supervision_access_workflow_not_owned`).
|
|
198
|
+
Release still happens only inside a code step of one of those workflows.
|
|
199
|
+
- The grant's own rules apply to the result: `releaseWorkflowIds` needs `release`; `unattendedWorkflowIds` needs
|
|
200
|
+
`act` or `release`, plus `observe` and `observe-content`; `dataCollections` needs `observe`. At most 10 ids per
|
|
201
|
+
workflow list and 20 collections; `*` is refused.
|
|
202
|
+
- An unattended Job step's staging key is owned by the person who approved, and can never do more than they can.
|
|
203
|
+
- **Upgrades** (`LUA_SUPERVISION_ACCESS_UPGRADES_ENABLED`; otherwise approval refuses
|
|
204
|
+
`supervision_access_grant_exists`, as before). When you already hold an admin (`pap`) or access-request grant on
|
|
205
|
+
the agent, a new request **adds** to it:
|
|
206
|
+
- The request records the grant as it is now (`upgradeOf`), and the approver's card lists exactly what approval
|
|
207
|
+
adds (`adds`), plus what you already have. A request that adds nothing is refused
|
|
208
|
+
(`supervision_access_nothing_to_add`).
|
|
209
|
+
- Approval merges: nothing is ever removed, and the grant keeps its source and creator.
|
|
210
|
+
- If your access changed after you asked (revoked, narrowed, widened), approval is refused
|
|
211
|
+
(`supervision_access_grant_changed`) and the request stays pending: the approver denies it and you ask again.
|
|
212
|
+
- An admin grant never expires, so an upgrade of one cannot set `expiresAt`. An access-request grant takes the new
|
|
213
|
+
expiry when you give one.
|
|
214
|
+
- Only the grant's creator can approve adding `unattendedWorkflowIds` to it (they own the staging key).
|
|
215
|
+
- Grants created by agent provisioning are never upgraded.
|
|
216
|
+
|
|
217
|
+
## Evals on a supervised agent
|
|
218
|
+
|
|
219
|
+
`Agents.evals(agentId)` runs the target's eval suites (the ones its owner pushed with `lua evals push`) against a
|
|
220
|
+
staged version. Off until the platform enables evals (`LUA_AGENT_EVALS_ENABLED` on lua-core, lua-api and lua-agents);
|
|
221
|
+
`run` also needs `LUA_SUPERVISION_PREVIEW_ENABLED`.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const ev = Agents.evals('base_agent-tp');
|
|
225
|
+
const suites = await ev.suites(); // [{ key, revision, caseCount, minPassRate }]
|
|
226
|
+
const { runId } = await ev.run({ suite: 'release-gate', version: 9 }); // a staged version, or 'active'
|
|
227
|
+
const run = await ev.getRun(runId); // poll until status is completed / failed / cancelled / budget_exhausted
|
|
228
|
+
if (run.status === 'completed' && run.passed === false) {
|
|
229
|
+
const t = await ev.transcript(runId, run.cases.find((c) => c.status === 'failed')!.caseId); // observe-content
|
|
230
|
+
}
|
|
231
|
+
await ev.cancel(runId); // only runs you started
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
| Member | Needs | Notes |
|
|
235
|
+
| ------------------------------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
236
|
+
| `suites()`, `suite(key)` | `observe` | The suite's cases; its author shows as `user` / `apiKey` / `service:agent` |
|
|
237
|
+
| `run({ suite, version, maxCredits?, caseIds? })` | `preview` + the preview switch | Runs are attributed to your agent. Bounded by the suite budget, the agent's monthly eval allowance and 10 supervised runs per agent per day (`LUA_SUPERVISION_EVAL_RUNS_PER_DAY`, `supervision_eval_runs_rate_limited`; `supervision_eval_cap_unavailable` when the counter is down) |
|
|
238
|
+
| `runs(opts?)`, `getRun(runId)` | `observe` | Per-check `detail` and the judge's `reasoning` only with `observe-content`; another principal's runs show a coarse `initiatedBy` |
|
|
239
|
+
| `transcript(runId, caseId)` | `observe` + `observe-content` | `supervision_content_not_granted` without it; a holdout case is not found |
|
|
240
|
+
| `cancel(runId)` | `preview` | Only runs you started. Works even with evals or preview switched off (stopping work is always safe) |
|
|
241
|
+
|
|
242
|
+
- Supervisors never write suites: a suite decides which live tools an eval turn may use, which is the owner's call.
|
|
243
|
+
- Eval turns run with built-in tools isolated, and live tools only where the owner opted them in on the suite.
|
|
244
|
+
- Offline (`lua test`): reads use your developer credential; `run` and `cancel` are refused.
|
|
245
|
+
- A Job-tier plugin step can use `lua evals` (reads, start, cancel) on its OWN agent through the `:8092` proxy when
|
|
246
|
+
`LUA_JOB_STEP_EVALS_ENABLED` is on. On a supervised (foreign) target it gets no eval routes: use `Agents.evals(...)`
|
|
247
|
+
in a code step, which carries the grant checks, the daily cap and the audit row. Suite writes are always refused.
|
|
248
|
+
|
|
249
|
+
## Messaging a supervised agent's owner
|
|
250
|
+
|
|
251
|
+
`Agents.notifyOwner(agentId, { message })` posts a plain message (no model turn) into the **target owner's** home
|
|
252
|
+
thread with your agent, from any run, a schedule or an event trigger included. `Threads.followUp` from those runs
|
|
253
|
+
reaches only your own notify list.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// in an event trigger or a scheduled sweep
|
|
257
|
+
await Agents.notifyOwner('base_agent-tp', {
|
|
258
|
+
message: 'Lead Desk: the order lookup started failing at 14:05 (12 errors, signed out of Shopify).',
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
- Needs `observe` on the agent and the **owner's opt-in on your grant**: the owner approved a
|
|
263
|
+
`requestAccess({ notifyOwner: true })` themselves, or turned it on with
|
|
264
|
+
`PUT /developer/agents/:agentId/supervisors/:grantId/owner-notify` (`{ enabled }`, the agent's creator only). An org
|
|
265
|
+
admin approving a shared agent's request cannot opt its owner in.
|
|
266
|
+
- Refused `supervision_owner_notify_not_opted_in` without the opt-in, after the agent changes owner, or once the owner
|
|
267
|
+
has left the org; `{ prompt }` is refused (`supervision_owner_notify_invalid`): it would start a turn as the owner.
|
|
268
|
+
- Shares `Threads.followUp`'s limits (12 per thread and 120 per agent per hour) and its audit row; the target's logs
|
|
269
|
+
get a `supervisor_access` row as for every supervision call.
|
|
270
|
+
- The platform does not inspect the text. An owner who opts in should know the supervisor may summarise what it
|
|
271
|
+
observes on any agent it supervises; replying in that thread talks to the supervisor as the owner.
|
|
272
|
+
- Off until `LUA_SUPERVISION_OWNER_NOTIFY_ENABLED` is on (lua-core and lua-api): `supervision_owner_notify_disabled`.
|
|
273
|
+
Not available offline.
|
|
274
|
+
- Add an `idempotencyKey` to make retries safe:
|
|
275
|
+
`Agents.notifyOwner(id, { message, idempotencyKey: 'incident-7' })`. A second call from your agent with the same key
|
|
276
|
+
within 24 hours posts nothing and returns the first receipt, with the same `followUpId` and `threadId`. A
|
|
277
|
+
concurrent duplicate waits for the first, or is refused `thread_follow_up_in_progress`; retry it. A first call
|
|
278
|
+
refused before it wrote anything frees the key; a failed write keeps it reserved for about two minutes, and a later
|
|
279
|
+
re-post replaces the same message. Delivery is at most once unless the key store fails mid-delivery. The key is 1-256 characters, and an invalid one is `supervision_owner_notify_invalid`, as is
|
|
280
|
+
a key already used to notify the owner of a different agent. Keys are separate from `Threads.followUp` keys.
|
|
281
|
+
Without a key, every call posts.
|
|
282
|
+
|
|
283
|
+
## Handing a supervised agent one of its owner's connections
|
|
284
|
+
|
|
285
|
+
`Agents.connections(agentId).assign(connectionId, { approval })` mounts one of the agent **owner's own personal
|
|
286
|
+
connections** (their Gmail, their Slack…) on the agent, for example on an agent your Supervisor built for them (a
|
|
287
|
+
provisioned agent gets none of its owner's connections by itself). The owner consents on Lua's own approval card, in
|
|
288
|
+
their signed-in Desktop or inbox session:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// a code step of a workflow the OWNER started from their own turn (the run's creator is the owner)
|
|
292
|
+
const card = {
|
|
293
|
+
connectionAssignment: { connectionId, agentId, agentName, supervisorName, integration: 'googlemail', account },
|
|
294
|
+
};
|
|
295
|
+
// …previous step returns `card`, then:
|
|
296
|
+
// .approval('giveGmail', { approver: 'creator', title: `Give your googlemail connection to "${agentName}"?` })
|
|
297
|
+
await Agents.connections(agentId).assign(connectionId, { approval: { runId: ctx.runId, stepId: 'giveGmail' } });
|
|
298
|
+
// → { agentId, connectionId, integration, status: 'assigned' }
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
- The payload is exactly
|
|
302
|
+
`{ connectionAssignment: { connectionId, agentId, agentName, supervisorName, integration, account } }`: `agentName`
|
|
303
|
+
is the agent's current name, `supervisorName` your agent's, `integration` the connection's integration type and
|
|
304
|
+
`account` the label Lua gives the connection (its display name, else the provider's first email). Call `assign` from
|
|
305
|
+
a code step of the same run that holds the approval (`connection_handover_binding_required` otherwise); the card
|
|
306
|
+
carries no artefacts. Lua shows these facts on the card (and that your agent can reach the account through the agent it
|
|
307
|
+
supervises) and checks each against what it stores; an extra key such as `summary` is refused. The step's `title`
|
|
308
|
+
must be exactly `Give your <integration> connection to "<agentName>"?` (`connectionHandoverApprovalTitle`). A
|
|
309
|
+
connection with no label, or whose label another of the owner's connections of that kind shares, is refused
|
|
310
|
+
(`connection_handover_account_unidentified`); so is an agent whose name (or yours) has invisible format characters or
|
|
311
|
+
that shares its name with another of the owner's agents (`connection_handover_agent_unidentified`). The card shows the
|
|
312
|
+
agent's name with the last 6 characters of its id.
|
|
313
|
+
- Only the owner's own decision counts: approved in a first-party session with a proved sign-in (not a chat card, a
|
|
314
|
+
channel reply or an API key), by the person who created the agent and started your run, within 10 minutes, unedited.
|
|
315
|
+
One assignment per approval. Refusals: `connection_handover_approval_required`, `connection_handover_approval_mismatch`.
|
|
316
|
+
- The connection must be the owner's own user-owned connection (not one delegated to them) and the agent their private
|
|
317
|
+
agent, else `connection_handover_not_assignable`. Needs `act` on the agent.
|
|
318
|
+
- The owner removes it like any mount (`DELETE /developer/unifiedto/connections/:agentId/:connectionId/attach`).
|
|
319
|
+
- Off until `LUA_SUPERVISED_CONNECTION_HANDOVER_ENABLED` is on (lua-core and lua-api): `connection_handover_disabled`.
|
|
320
|
+
Not available offline.
|
|
321
|
+
|
|
322
|
+
## Keys for an agent that already exists
|
|
323
|
+
|
|
324
|
+
Ask the person for a key for one of THEIR agents; they submit it signed in, Lua tests it, then you attach it:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// in a tool, on the owner's own chat turn
|
|
328
|
+
const { requestId } = await Secrets.request({
|
|
329
|
+
provider: 'openai',
|
|
330
|
+
key: 'OPENAI_API_KEY',
|
|
331
|
+
target: { pendingFor: 'lead-desk', agentId: 'base_agent-tp' },
|
|
332
|
+
reason: 'Lead Desk summarises new leads',
|
|
333
|
+
});
|
|
334
|
+
// the OWNER checks what they are submitting, then submits it signed in (their session or their own API key):
|
|
335
|
+
// GET /developer/secrets/held-requests/<requestId> → provider, key, env key names, agent, state, expiry
|
|
336
|
+
// POST /developer/secrets/held-requests/<requestId>/submit {"values":{"apiKey":"…"}}
|
|
337
|
+
// later, on the owner's turn again:
|
|
338
|
+
await Agents.secrets('base_agent-tp').attach([requestId]); // { agentId, attached, envKeys }
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
- Asking needs `supervision:act` on the agent, an agent of your org that is not your own, created by the person in the
|
|
342
|
+
conversation, on that person's **live interactive turn** (a tool in a chat turn; never a scheduled task, Job,
|
|
343
|
+
workflow step or a user another agent chose). Keys starting `LUA_` or `NODE_` are refused. Otherwise
|
|
344
|
+
`secret_target_not_allowed` / `secret_key_invalid`.
|
|
345
|
+
- The value is HELD (encrypted), never written to the agent until `attach`. Only the owner can submit it, signed in:
|
|
346
|
+
the public secret page (and its token) refuses these requests (`secret_submit_requires_owner`).
|
|
347
|
+
- `attach` needs `supervision:act` and the owner's live interactive turn, as above. It takes only requests YOU held
|
|
348
|
+
for THIS agent that the owner submitted signed in and whose value passed Lua's test (`ok`; never `unverified`), all
|
|
349
|
+
or none (`secret_pending_not_attachable`), and never replaces a key the agent already has (`secret_key_exists`).
|
|
350
|
+
The agent's logs get a `supervisor_access` row and an outcome row (request ids and key names, never values).
|
|
351
|
+
- A key held for an existing agent is never attached to a newly provisioned one, and a `{ pendingFor }` key for a new
|
|
352
|
+
agent is never taken by `attach`. If the agent is archived, or changes owner or org after the hold, the owner can no
|
|
353
|
+
longer view or submit it.
|
|
354
|
+
- Accepted residuals: (1) "signed in" includes the owner's own API key, so an owner's PERSONAL API key placed in some
|
|
355
|
+
agent's env could submit as the owner (kept so API-only owners can submit at all); (2) the live-turn marker follows an
|
|
356
|
+
`Agents.invoke` chain that started on the owner's live turn (a tool the owner's turn invoked counts as that turn).
|
|
357
|
+
- Off until `LUA_SECRET_ATTACH_EXISTING_ENABLED` is on (lua-core and lua-api, with lua-agents on the same build), with `LUA_SECRET_REQUESTS_ENABLED`
|
|
358
|
+
(`secret_attach_existing_disabled`); while it is off, `agentId` in a target is ignored as before. Not available
|
|
359
|
+
offline.
|
|
360
|
+
|
|
361
|
+
## Events about failing tools
|
|
362
|
+
|
|
363
|
+
Subscribe to two more event kinds with `Agents.events(id).subscribe({ kinds, triggerName })`:
|
|
364
|
+
|
|
365
|
+
| Kind | When | `data` |
|
|
366
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
367
|
+
| `tool.auth_failed` | A tool or integration call of the agent failed with HTTP 401 / 403 or an expired / invalid token | `toolName`, `logSource`, `reason` (`unauthorized` / `forbidden` / `token_expired`), `windowStart` |
|
|
368
|
+
| `tool.errors_repeated` | The same tool (or job, webhook…) failed 5 times inside 10 minutes | `toolName`, `logSource`, `count`, `windowSeconds`, `windowStart` |
|
|
369
|
+
| `trigger.held` | A delivery to an inactive or paused trigger was held for replay (at most one per trigger per hour) | `triggerName`, `reason` |
|
|
370
|
+
| `agent.paused` | The agent was paused | `reason` (`owner_reason_given` / `none`, never the text), `pausedBy: 'user'` |
|
|
371
|
+
|
|
372
|
+
- `tool.*` events never carry the error text, and fire once per agent, tool and 10-minute window. Test traffic
|
|
373
|
+
(previews, eval runs, `lua dev`) is not counted. They need `LUA_SUPERVISION_TOOL_FAILURE_EVENTS_ENABLED` on lua-core
|
|
374
|
+
as well as `LUA_SUPERVISION_EVENTS_ENABLED`.
|