lua-cli 3.31.0 โ 3.32.1
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 +81 -5
- package/dist/api-exports.js +686 -126
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +2003 -308
- package/dist/index.js.map +1 -1
- package/dist/workflow-builder.d.ts +37 -3
- package/dist/workflow-builder.js +658 -117
- package/dist/workflow-builder.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/api/LuaWorkflow.md +44 -28
- package/docs/api/Workflows.md +2 -1
- package/docs/workflows/approvals.md +14 -1
- package/docs/workflows/connections-in-coding-turns.md +1 -0
- package/docs/workflows/git-credentials.md +22 -1
- package/docs/workflows/goals.md +46 -0
- package/docs/workflows/recovery.md +6 -2
- package/docs/workflows/replay-local.md +10 -10
- package/docs/workflows/schedules.md +15 -0
- package/docs/workflows/testing-offline.md +22 -19
- package/docs/workflows/workspaces-and-long-steps.md +2 -0
- package/package.json +3 -3
- package/template/examples/workflows/CLAUDE.md +16 -13
- package/template/examples/workflows/pr-review-round.ts +57 -20
- package/template/examples/workflows/provision-tenant.ts +25 -8
- package/template/examples/workflows/refund-approval.ts +30 -17
- package/template/examples/workflows/support-triage.ts +59 -22
- package/template/examples/workflows/ticket-to-pr.ts +103 -31
- package/template/examples/workflows/vendor-invoices.ts +69 -16
- package/template/package.json +1 -1
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# lua-cli v3.
|
|
1
|
+
# lua-cli v3.32.1
|
|
2
2
|
|
|
3
|
-
Welcome to the comprehensive API documentation for lua-cli v3.
|
|
3
|
+
Welcome to the comprehensive API documentation for lua-cli v3.32.1. 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,29 +2,45 @@
|
|
|
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.**
|
|
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, `retry-step` and a repair run; 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
|
|
|
9
9
|
```typescript
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createStep,
|
|
12
|
+
createWorkflow,
|
|
13
|
+
defineWorkflow,
|
|
14
|
+
step,
|
|
15
|
+
stepOf,
|
|
16
|
+
eq,
|
|
17
|
+
gt,
|
|
18
|
+
gte,
|
|
19
|
+
lit,
|
|
20
|
+
template,
|
|
21
|
+
fromInit,
|
|
22
|
+
fromStep,
|
|
23
|
+
fromKnowledge,
|
|
24
|
+
rows,
|
|
25
|
+
env,
|
|
26
|
+
} from 'lua-cli';
|
|
11
27
|
```
|
|
12
28
|
|
|
13
29
|
## `createStep(config)`
|
|
14
30
|
|
|
15
31
|
A typed code step. `inputSchema` / `outputSchema` / `resumeSchema` are zod schemas; the compiler serializes them to JSON Schema.
|
|
16
32
|
|
|
17
|
-
| Field
|
|
18
|
-
|
|
19
|
-
| `id`
|
|
20
|
-
| `inputSchema` / `outputSchema`
|
|
21
|
-
| `timeoutSeconds`
|
|
22
|
-
| `retry`
|
|
23
|
-
| `sideEffects`
|
|
24
|
-
| `onError`
|
|
25
|
-
| `requiredConnections`
|
|
26
|
-
| `tier` / `workspace` / `jobResources` / `jobTools` | Job-tier fields โ see [Workspaces and long steps](../workflows/workspaces-and-long-steps.md)
|
|
27
|
-
| `execute(ctx)`
|
|
33
|
+
| Field | Meaning |
|
|
34
|
+
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `id` | Unique step id (referenced by `then`, containers, `fromStep`) |
|
|
36
|
+
| `inputSchema` / `outputSchema` | zod; outputs are validated after every attempt |
|
|
37
|
+
| `timeoutSeconds` | Per-attempt wall (worker tier โค 600; `tier:'job'` up to 86 400) |
|
|
38
|
+
| `retry` | `{ maxAttempts, backoffSeconds?, backoff?: 'fixed'\|'exponential', maxBackoffSeconds? }` โ waits `backoffSeconds ยท 2^(attempt-1)` capped at `maxBackoffSeconds` |
|
|
39
|
+
| `sideEffects` | `'external'` โ never auto-retried on a platform-fault reclaim โ the step **parks** instead (see [When a step parks](../workflows/recovery.md)) |
|
|
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
|
+
| `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) |
|
|
43
|
+
| `execute(ctx)` | `ctx`: `inputData`, `resumeData`, `getInitData()`, `getStepResult(id)`, `state`, `suspend()`, `bail()`, `bailRun()`, `once()`, `log()`, `env`, `artefacts`, `occurrenceId`, `workspace?` |
|
|
28
44
|
|
|
29
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.
|
|
30
46
|
|
|
@@ -38,21 +54,21 @@ Config: `name`, `description?`, `inputSchema`, `outputSchema?`, `budget?` (`maxC
|
|
|
38
54
|
|
|
39
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()`.
|
|
40
56
|
|
|
41
|
-
| Verb
|
|
42
|
-
|
|
43
|
-
| `.then(step, input?)`
|
|
44
|
-
| `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, โฆ })`
|
|
45
|
-
| `.specialistStep(id, { role, prompt, โฆ })`
|
|
46
|
-
| `.toolStep(id, { toolId, input? })`
|
|
47
|
-
| `.map(descriptors, { id })`
|
|
48
|
-
| `.parallel([...arms], { merge? })`
|
|
49
|
-
| `.switch([[predicate, armRef]...], otherwise?)` / `.branch(...)`
|
|
50
|
-
| `.foreach(step, { concurrency?, maxItems?, chunk?, rateLimit? })` / `.foreach({ items })`
|
|
51
|
-
| `.dowhile(ref, predicate, { maxIterations, intervalSeconds? })` / `.dountil(...)`
|
|
52
|
-
| `.sleep(ms)` / `.sleepUntil(template)`
|
|
53
|
-
| `.approval(id, { title, approver, timeoutHours, onTimeout, editable?, editablePaths?, itemsPath?, โฆ })` | Human gate โ see [Approvals](../workflows/approvals.md)
|
|
54
|
-
| `.waitForSignal(id, { signal, schema?, timeoutHours, acceptedSources? })`
|
|
55
|
-
| `.workflow(id, ref, input?, { workspace? })`
|
|
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 (lowered to an implicit subrun) |
|
|
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 }` |
|
|
71
|
+
| `.workflow(id, ref, input?, { workspace? })` | Child run the parent waits for |
|
|
56
72
|
|
|
57
73
|
### Typed predicates
|
|
58
74
|
|
package/docs/api/Workflows.md
CHANGED
|
@@ -67,7 +67,7 @@ Resumes a step suspended with `ctx.suspend(...)`. The loser of a resume race rec
|
|
|
67
67
|
|
|
68
68
|
### `Workflows.signal(runId, name, payload?, opts?)`
|
|
69
69
|
|
|
70
|
-
Delivers a named signal to a run waiting on `waitForSignal(name)`. `opts.dedupeKey` makes a re-send a no-op (`{ accepted: true, duplicate: true }`). Returns `{ accepted, reason?: 'source_not_accepted' | 'duplicate' | 'parked' }`.
|
|
70
|
+
Delivers a named signal to a run waiting on `waitForSignal(name)`. `opts.dedupeKey` makes a re-send a no-op (`{ accepted: true, duplicate: true }`). Returns `{ accepted, reason?: 'source_not_accepted' | 'duplicate' | 'parked' }`. The waiting step completes with `{ payload, source: { kind, id, principalId? }, signalId, receivedAt }` โ read the payload as `stepResults.<id>.payload`.
|
|
71
71
|
|
|
72
72
|
### Reserved members
|
|
73
73
|
|
|
@@ -107,4 +107,5 @@ lua test workflow outreach --input @leads.json \
|
|
|
107
107
|
## Related
|
|
108
108
|
|
|
109
109
|
- `lua workflows` โ list, start, runs, status, watch, attach, cancel, resume, approve, signal, replay, logs, delete โ `attach` re-attaches to a running run's event stream (the same stream your chat card re-attaches to)
|
|
110
|
+
- `lua workflows goals <list|get|create|pause|resume|close>` and `lua workflows schedules <list|delete>` โ the operator surface for goals (R57โR62) and schedule Jobs; `lua workflows view --json` carries `schedules` (goal-owned rows tagged `goalId`) and `goals`. See [workflows/goals.md](../workflows/goals.md) and [workflows/schedules.md](../workflows/schedules.md).
|
|
110
111
|
- [LuaWorkflow](./LuaWorkflow.md) โ the builder primitive
|
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
_Source of truth: workflows-spec 06 ยง6.4. This page is the developer summary; the spec is normative._
|
|
4
4
|
|
|
5
|
-
`.approval(id, cfg)` parks the run until a person decides. The node completes with
|
|
5
|
+
`.approval(id, cfg)` parks the run until a person decides. The node completes with the decision as data โ branchable, never an exception. A human decision completes it with
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
{
|
|
9
|
+
approved: boolean;
|
|
10
|
+
note?: string; // the approver's note, when one was left
|
|
11
|
+
editedPayload?: unknown; // only when the approver edited (see below)
|
|
12
|
+
editRevision: number; // 0 when the payload was never edited
|
|
13
|
+
decidedBy: { id?: string; kind: string }; // who decided
|
|
14
|
+
evidence?: string[]; // decision artefact ids frozen with the decision
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A timeout that ends the chain in `'deny'` completes it with the sweep's shape instead โ `{ approved: false, timedOut: true, escalations: <hops> }` (no `decidedBy`, `editRevision` or `evidence`); `'continue'` is treated as `'deny'` for approvals; `'cancel-run'` cancels the run. The original payload is not echoed back โ read it from the step's own input (`stepResults`), not from the output.
|
|
6
19
|
|
|
7
20
|
## Approver specs
|
|
8
21
|
|
|
@@ -4,6 +4,7 @@ _Source of truth: workflows-spec 05 ยง5.17.6 / 11 ยง11.5. This page is the devel
|
|
|
4
4
|
|
|
5
5
|
`toolScope.connectionIds` on a Job-tier agent step mounts MCP connections into the coding turn **through a local proxy**: the model calls `tools/list` / `tools/call`; the proxy holds the token โ the model never sees it.
|
|
6
6
|
|
|
7
|
+
- `requiredConnections` (and `workspace.credentialsRef`) accept a declared `connections[].key` instead of an id โ declare a key; it resolves against the owner agent's connections on any agent at run time (see [Git credentials](./git-credentials.md)).
|
|
7
8
|
- A tool that needs approval is **refused inside the turn** (`mcp_call_denied`) โ hand it to a worker-tier step where the approval card can park the run.
|
|
8
9
|
- Calls are capped per segment; the cap and usage appear in the step detail.
|
|
9
10
|
- Offline: connections are **not mounted** (no proxy, no token mint) โ `--mcp <connectionId>=@fixture.json` stubs `tools/list` / `tools/call` from a `{ tools:[โฆ], calls:{ [tool]: result } }` fixture; an unstubbed call returns the production `mcp_call_denied{reason:'not_in_scope'}` shape.
|
|
@@ -6,6 +6,27 @@ _Source of truth: workflows-spec 11 ยง11.5.6 / 05 ยง5.17.3. This page is the dev
|
|
|
6
6
|
|
|
7
7
|
> **Never put a PAT in `env`.** Rotation or revocation of the underlying connection makes the next mint fail: the step fails `credentials_revoked{git_token_mint_failed}` and โ with `onError:'park'` โ enters the park/`retry-step` loop, so a re-grant resumes the run without losing work.
|
|
8
8
|
|
|
9
|
+
## Declare a key; it resolves on any agent
|
|
10
|
+
|
|
11
|
+
Don't freeze a connection id in source. Declare the connection once on `createWorkflow` and reference it by key:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
createWorkflow({
|
|
15
|
+
name: 'ticket-pilot',
|
|
16
|
+
connections: [{ key: 'github', integrationType: 'github', required: true }],
|
|
17
|
+
workspace: { kind: 'git', repo: 'acme/app', credentialsRef: 'github' },
|
|
18
|
+
// โฆ
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
At run time the engine resolves `github` against the **owner agent's own connections** โ an agent-scoped GitHub connection first, then an org-scoped one; a user's personal connection is never picked. The same definition therefore runs unchanged on a template install (where the install binding still wins), on a hand-built agent and on a duplicated agent โ no "two builds of one workflow", no compile-time env var. `requiredConnections` on a step accepts declared keys the same way.
|
|
23
|
+
|
|
24
|
+
- The resolution is recorded once per run as `connection.resolved {key, connectionId, scope}`; `lua workflows status <runId> --steps --json` shows it under `connections.resolved`, beside `connections.declared` (the keys the version declares) โ a declared key missing from `resolved` never resolved on that run.
|
|
25
|
+
- A step that fails before the key resolves carries the reason on `error` (`message`, `providerStatus`, `providerMessage` and the engine's `detail` bag โ e.g. `detail.connectionId` names the ref the git mint was asked for).
|
|
26
|
+
- Two connections of the same type on the agent are ambiguous โ label the one to use with the key (its display name) or the step fails `credentials_unresolved` listing the candidates.
|
|
27
|
+
- No connection of that type at all โ `credentials_unresolved` (non-retryable, no turn billed) and a notice naming the key and the integration to connect; with `onError:'park'` the run resumes through `retry-step` once it is connected. A revoked or unhealthy connection still fails `credentials_revoked` exactly as before.
|
|
28
|
+
- A literal connection id keeps working exactly as it does today. A key that is not declared โ with or without a `connections` block โ fails `lua compile` and `lua push` with `connection-key-undeclared`, which names the declaration to add.
|
|
29
|
+
|
|
9
30
|
- `jobTools:['gh']` is what mints `GH_TOKEN` for a code step (`pull_requests:write` + `contents:write` + `issues:read`, repo-scoped). Merge is a contents write โ human-only-merge orgs keep `contents:write` off the App.
|
|
10
|
-
- Templates
|
|
31
|
+
- Templates declare `connections[].key` too: an install binds the key to the installer's connection; a definition installed without a binding falls back to the run-time resolution above.
|
|
11
32
|
- Offline, `credentialsRef` is printed and **not** resolved (no token mint) โ `--credentials-ref-ok` suppresses the notice; the local driver uses your own git credentials.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Goals
|
|
2
|
+
|
|
3
|
+
_Source of truth: workflows-spec 05 ยง5.20 / 09 ยง9.5.9. This page is the developer summary; the spec is normative._
|
|
4
|
+
|
|
5
|
+
A goal runs a workflow again and again until a judge says it is done: `objective` is what "done" means, `judge` decides it after every iteration (a deterministic `predicate` over the run's `{ output, state, steps.<stepId>, iteration, runStatus }`, or a judge agent answering a JsonSchema with a boolean `done`), `cadence` is when the next iteration starts (a schedule Job the goal owns; none โ the next iteration starts immediately), and `maxRuns` caps the loop. A goal is `active`, `paused` (by you, or by the platform on `budget` / `max_runs` / `strikes`), `done` (the judge said so) or `closed` (by you โ final).
|
|
6
|
+
|
|
7
|
+
Goals are created from chat (`setWorkflowGoal`), from the SDK (`Workflows.setGoal`) and from the CLI; the CLI is the audit surface for all of them.
|
|
8
|
+
|
|
9
|
+
## Verbs
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
lua workflows goals list [<workflow>] [-i <workflow>] [--status active|paused|done|closed] [--limit n] [--cursor c] [--json]
|
|
13
|
+
lua workflows goals get <goalId> [--json] # the goal card + its iteration runs with verdicts
|
|
14
|
+
lua workflows goals pause <goalId> # R60 โ the cadence stays with the goal
|
|
15
|
+
lua workflows goals resume <goalId> # R61 โ no backfill of missed fires
|
|
16
|
+
lua workflows goals close <goalId> [--note <text>] # R62 โ final
|
|
17
|
+
lua workflows goals create <workflow> --objective <text> --max-runs <n> [-v <semver|latest|versionId>] \
|
|
18
|
+
(--judge-predicate '<path> <op> [value]' | --judge-agent <agentId|'$self'> [--judge-role <json|@file>] --schema <json|@file>) \
|
|
19
|
+
[--cadence <cron|json|@file> โฆ] [--timezone <tz>] [--input <json|@file>] \
|
|
20
|
+
[--budget-credits <n>] [--max-total-credits <n>] [--idempotency-key <key>]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`lua workflows view <workflow>` prints the workflow's goals (and its schedules); `--json` carries them under `goals` and `schedules`.
|
|
24
|
+
|
|
25
|
+
### `goals create`
|
|
26
|
+
|
|
27
|
+
- `--judge-predicate` is the deterministic judge: `output.signups gte 50`, `output.done truthy`, `state.phase eq "shipped"`. The value is parsed as JSON when it parses (`50` โ number, `true` โ boolean) and kept as a string otherwise; ops are `exists ยท truthy ยท eq ยท neq ยท gt ยท gte ยท lt ยท lte`. A JSON object (`{"path":โฆ,"op":โฆ,"value":โฆ}`) or `@file` works too.
|
|
28
|
+
- `--judge-agent` runs a judge turn instead: another agent by id, or `'$self'` โ the workflow's own agent; quote it, an unquoted `$self` is expanded by the shell to an empty string (`self` is accepted as the same thing) โ then `--judge-role` with the D25 `{ name, instructions, tools }` is required. `--schema` is required for an agent judge and must declare a boolean `done` at the root; the judge answers it and `done` ends the goal.
|
|
29
|
+
- `--cadence` is repeatable (โค 5): a bare cron expression (`'0 9 * * 1'`, `--timezone Europe/London` applies to it) or a JSON slot / array for `interval` (`{"type":"interval","seconds":3600}`) and `once` entries โ a timezone for those goes inside the entry (`--timezone` beside JSON-only cadences is an error). `-v` pins the iteration version (a semver, `latest` or a version id โ resolved like `start -v`); without it every iteration runs the active version. Without a cadence the goal runs `immediate`: each iteration starts as soon as the previous one is judged.
|
|
30
|
+
- `--input` seeds every iteration's run input; `--budget-credits` is the per-run budget; `--max-total-credits` is the lineage-wide gate that pauses the goal (`budget`) when the sum of its runs reaches it.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
lua workflows goals create -i outreach \
|
|
36
|
+
--objective "Reach 50 trial signups from the March cohort" \
|
|
37
|
+
--judge-predicate 'output.signups gte 50' \
|
|
38
|
+
--cadence '0 9 * * 1' --timezone Europe/London \
|
|
39
|
+
--max-runs 12 --input '{"segment":"trial-march"}'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Errors and exit codes
|
|
43
|
+
|
|
44
|
+
The usual `lua workflows` codes: 0 ok ยท 1 API refusal ยท 2 usage ยท 3 not found. The goal routes' refusals are printed with their hint: `GOAL_NOT_ACTIVE {status}` (only an active goal pauses, only a paused one resumes, a done/closed goal stays closed), `GOAL_CAP {cap}` (close or finish a goal first), `VALIDATION_FAILED {issues}` (each issue with its path โ `goal-judge-schema-missing-done`, `goal-cadence-invalid`, `goal-objective-too-long`, `ephemeral-role-required`, โฆ), `GOAL_MAX_RUNS_INVALID`, `WORKFLOW_NOT_ON_AGENT`.
|
|
45
|
+
|
|
46
|
+
`lua workflows schedules delete` on a goal's cadence Job is refused (`goal_schedule`) โ use `goals pause` / `goals close`; see [schedules.md](./schedules.md).
|
|
@@ -5,7 +5,9 @@ _Source of truth: workflows-spec 01 ยง1.6-r1 / 06 ยง6.3.5. This page is the deve
|
|
|
5
5
|
A **park** is the platform refusing to guess. Two things park a step:
|
|
6
6
|
|
|
7
7
|
1. **`sideEffects:'external'` + a platform fault.** A worker crash, an eviction or a network partition while an external-effect step was in flight means the platform cannot know whether the effect happened (`EFFECT_IN_DOUBT`). **The platform never re-runs an external step on its own** โ the step parks and the run gates `exception`.
|
|
8
|
-
2. **`onError:'park'`.** A final (retries exhausted) failure parks instead of failing the run โ for steps where an operator should decide.
|
|
8
|
+
2. **`onError:'park'`.** A final (retries exhausted, or non-retryable) failure parks instead of failing the run โ for steps where an operator should decide. The row carries `park: { reason: 'onerror_park' }`, the run is `suspended` with `gate.kind: 'exception'`, and the events show `step.parked` + `run.gated`; nothing downstream is skipped. A failure that happened **before dispatch** (`binding_unresolved`, `input_schema_invalid`, `credentials_unresolved`, `credentials_revoked`, `TOOL_REF_UNRESOLVED`, `BUNDLE_UNRESOLVED`) parks the same way โ but a plain `retry-step` re-renders the same input against the same definition and connections and parks again unless one of those was fixed in between; the retry response carries `detail.hint` saying so, and `resolve-step` is the way past it.
|
|
9
|
+
|
|
10
|
+
If a **sibling** step fails under `onError:'fail'` while the run is parked, the run keeps the park but is marked `failing`: after `retry-step` the parked step still re-runs, and the run then terminalizes `failed` (never `completed`) once nothing is in flight.
|
|
9
11
|
|
|
10
12
|
## The three verbs
|
|
11
13
|
|
|
@@ -15,13 +17,15 @@ A parked step waits for exactly one of:
|
|
|
15
17
|
- **`resolve-step`** โ you supply the step's output (validated against `outputSchema`); the run continues as if the step had returned it.
|
|
16
18
|
- **`skip`** โ marks the row skipped; downstream bindings render `status="skipped"`.
|
|
17
19
|
|
|
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
|
+
|
|
18
22
|
`fail` (or cancelling the run) applies the step's failure as-is. From the CLI: `lua workflows status <runId>` shows the park, `lua workflows resume` / the desktop inbox carry the verbs.
|
|
19
23
|
|
|
20
24
|
## Repair runs
|
|
21
25
|
|
|
22
26
|
When a whole run is beyond per-step repair, a **repair run** re-drives the graph seeded from the ledger (the ยง05 ยง5.5.4.1 seeding math โ the same `seedLedgerFromRun` the CLI's `--from-run` uses): every `completed` step keeps its outputs, attempts and settled effects, and the frontier restarts at the first non-completed step. Because `occurrenceId` is stable across retry, resume, `retry-step` **and** a repair run, an external effect executes at most once per occurrence.
|
|
23
27
|
|
|
24
|
-
> **The rule to remember:**
|
|
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}`).
|
|
25
29
|
|
|
26
30
|
## Rehearsing offline
|
|
27
31
|
|
|
@@ -8,10 +8,10 @@ _Source of truth: workflows-spec 03 ยง3.10, 04 ยง4.3.5, 05 ยง5.7.1, 12 ยง12.9.11
|
|
|
8
8
|
|
|
9
9
|
Two options were on the table:
|
|
10
10
|
|
|
11
|
-
| Option
|
|
12
|
-
|
|
|
13
|
-
| **A โ project-local runtime, resolved lazily**
|
|
14
|
-
| B โ a published replay runtime (`@lua/workflow-replay`) | Extract the wrapper source + `createSandboxContext` into a small published package the CLI depends on.
|
|
11
|
+
| Option | Shape | Verdict |
|
|
12
|
+
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| **A โ project-local runtime, resolved lazily** | `@lua/sandbox-runtime` is NOT bundled into the published CLI. The verb resolves it with `createRequire(<project>/package.json)` at call time; a miss is the typed error `REPLAY_RUNTIME_UNAVAILABLE` (exit 3), never a silent "stable". | **Chosen.** |
|
|
14
|
+
| B โ a published replay runtime (`@lua/workflow-replay`) | Extract the wrapper source + `createSandboxContext` into a small published package the CLI depends on. | Deferred: it would fork the wrapper text away from the runner (the ยง2.2 parity rule says one implementation), and the wrapper is versioned by `journalProtocolVersion` (N-1 window, 04 ยง4.3.5) โ a second package would have to track that window. Revisit at GA if partner projects outside the monorepo need `replay --local`. |
|
|
15
15
|
|
|
16
16
|
Why A: the wrapper drags the whole runtime plane (`@lua/sandbox-runtime` โ `@lua/shared-sandbox` polyfills, artefact/dataset contexts) into the CLI bundle otherwise; every project that can run a workflow-script replay today is a monorepo/dev checkout that already has the runtime in `node_modules`; and resolving from the **project** rather than the CLI guarantees the wrapper version matches the runner the project deploys.
|
|
17
17
|
|
|
@@ -19,15 +19,15 @@ Why A: the wrapper drags the whole runtime plane (`@lua/sandbox-runtime` โ `@l
|
|
|
19
19
|
|
|
20
20
|
- Inside the monorepo (or any project that lists `@lua/sandbox-runtime` in its devDependencies): `replay --local` and `lua test workflow <script>` work as-is.
|
|
21
21
|
- Elsewhere: `REPLAY_RUNTIME_UNAVAILABLE: replay --local needs @lua/sandbox-runtime resolvable from the project (โฆ)` โ add the devDependency, or run the verb from a monorepo checkout with `--project <dir>`.
|
|
22
|
-
- The graph-form `replay --local` (R4/R5 โ `replayLedger`) has no runtime dependency and always works.
|
|
22
|
+
- The graph-form `replay --local` (R4/R5 โ `replayLedger`) has no runtime dependency and always works. It re-evaluates every derived row over the same ancestor context the engine used โ including a `failed` ancestor under `onError:'continue'`, which binds its continued-failure value (`{ __lua_workflow:'continued_failure', failed:true, error, text:'' }`) exactly as the engine did, so a mapping over one replays stable rather than `binding_unresolved`.
|
|
23
23
|
|
|
24
24
|
## Exit matrix (script form)
|
|
25
25
|
|
|
26
|
-
| Exit | Meaning
|
|
27
|
-
|
|
|
28
|
-
| 0
|
|
29
|
-
| 3
|
|
30
|
-
| 4
|
|
26
|
+
| Exit | Meaning |
|
|
27
|
+
| ---- | ------------------------------------------------------------------------------------------------------------ |
|
|
28
|
+
| 0 | stable โ every async journal entry re-issued with the same `callHash` |
|
|
29
|
+
| 3 | runtime unavailable (`REPLAY_RUNTIME_UNAVAILABLE`) or the run/bundle could not be fetched |
|
|
30
|
+
| 4 | `LEDGER_DIVERGENCE` / `JOURNAL_DIVERGENCE` โ at least one `hash_mismatch` / `journal_longer_than_script` row |
|
|
31
31
|
|
|
32
32
|
## Related
|
|
33
33
|
|
|
@@ -9,3 +9,18 @@ _Source of truth: workflows-spec 07 ยง7.1. This page is the developer summary; t
|
|
|
9
9
|
A paused or auto-disabled schedule never replays missed fires by itself; opt in with `schedule.backfillOnEnable: { maxOccurrences }` (or `lua workflows activate --backfill` for one re-enable) and the most recent misses start as one batch, deduplicated against `lua workflows backfill` by the shared `backfill:<workflowId>:<occurrenceIso>` key, capped, and summarised in your inbox.
|
|
10
10
|
|
|
11
11
|
Offline, schedules do not fire (`backfillOnEnable` is not emulated) โ start runs with `lua test workflow` / `lua workflows start`.
|
|
12
|
+
|
|
13
|
+
## From the CLI
|
|
14
|
+
|
|
15
|
+
Schedules are `Job{kind:'workflow'}` rows on the agent; the CLI reads them through `GET /workflows/:agentId/schedules` and removes one through R28.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
lua workflows schedules list -i outreach # this workflow's schedule Jobs (omit -i for every workflow of the agent)
|
|
19
|
+
lua workflows schedules list --json # { success, data: { items: [{ jobId, workflowId, trigger, paused, nextRunAt, lastFiredAt?, consecutiveFailures, autoDisabled?, goalId? }] } }
|
|
20
|
+
lua workflows schedules delete <jobId> [--yes] # R28 โ asks first unless --yes
|
|
21
|
+
lua workflows view outreach # the "Schedules:" line + table; --json carries the same rows under `schedules`
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Each row shows the trigger (`cron 0 9 * * 1 (Europe/London)` ยท `every 3600s` ยท `once <iso>`), the next fire, the last fire, its status (`active` ยท `paused` ยท `auto-disabled` after the PRO-726 strike limit) and the strike count.
|
|
25
|
+
|
|
26
|
+
**Goal-owned schedules.** A goal's cadence is a schedule Job too; it is tagged with the goal's id (`goalId` in `--json`, the `Goal` column in the table). The CLI refuses to delete it โ `schedules delete` on such a row answers `goal_schedule` (exit 1) without calling the API, the same refusal the chat tool gives โ because unscheduling a goal's job is recorded as a failure of the goal, never as stopping it. Stop the goal instead: `lua workflows goals pause <goalId>` (it can come back) or `lua workflows goals close <goalId>` (final). The server refuses it too: R28 answers `409 GOAL_SCHEDULE {goalId}` for every caller (SDK, desktop, curl), and the CLI renders that with the same message. See [goals.md](./goals.md).
|
|
@@ -4,30 +4,33 @@ _Source of truth: workflows-spec 03 ยง3.9 (WF-204/WF-225/WF-332). This page is t
|
|
|
4
4
|
|
|
5
5
|
`lua test workflow <name>` (alias `lua workflows run <name>`) drives a compiled workflow **offline**: the same `compilePlan`, predicates and mappings the engine uses, code steps executed in the `lua test` sandbox, agent steps faked by default, waits fast-forwarded on a virtual clock. Exit codes: `0` completed ยท `2` usage/validation ยท `4` failed ยท `5` missing fixture.
|
|
6
6
|
|
|
7
|
+
**No API call.** The verb compiles first, but that compile skips the server โ YAML sync every other `lua compile` performs โ nothing is created on the agent and no run exists on the server. Only `lua push workflow` persists a definition; `lua workflows start` creates a run. The exceptions are the flags that ask for the platform: `--from-run <runId>` (reads the seed run) and `--agents live` (calls the dev API for agent steps).
|
|
8
|
+
|
|
7
9
|
## Steering flags โ fully scriptable, no stdin
|
|
8
10
|
|
|
9
|
-
| Flag
|
|
10
|
-
|
|
11
|
-
| `--input @file\|<json>`
|
|
12
|
-
| `--step-output <id>=@file\|<json>` (rep.)
|
|
13
|
-
| `--approve <id>[=@payload]` ยท `--deny <id>[=@reason]` | Pre-answer approvals (edited payloads use the server's `matchesEditablePath` + `editedPayloadSchema`)
|
|
14
|
-
| `--signal <name>=@file\|<json>` (rep.)
|
|
15
|
-
| `--fixtures <dir>` / `--record <dir>`
|
|
16
|
-
| `--from-run <runId>`
|
|
17
|
-
| `--park <id>` (rep.)
|
|
18
|
-
| `--fast-retries`
|
|
19
|
-
| `--real-time`
|
|
20
|
-
| `--now <iso>`
|
|
21
|
-
| `--artefacts-dir <dir>`
|
|
22
|
-
| `--env KEY=value` (rep.)
|
|
23
|
-
| `--step-wall <s>`
|
|
24
|
-
| `--max-foreach-items <n>`
|
|
25
|
-
| `--agents fake\|live`
|
|
26
|
-
| `--ledger-out <file>`
|
|
27
|
-
| `--json`
|
|
11
|
+
| Flag | Effect |
|
|
12
|
+
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `--input @file\|<json>` | Run input (validated against `inputSchema`) |
|
|
14
|
+
| `--step-output <id>=@file\|<json>` (rep.) | Completes the row **without** running it โ validated against the step's `outputSchema`. This is how a predicate over an agent output gets BOTH truth values under `--agents fake` |
|
|
15
|
+
| `--approve <id>[=@payload]` ยท `--deny <id>[=@reason]` | Pre-answer approvals (edited payloads use the server's `matchesEditablePath` + `editedPayloadSchema`) |
|
|
16
|
+
| `--signal <name>=@file\|<json>` (rep.) | Pre-supply `waitForSignal` payloads in order of arrival |
|
|
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 |
|
|
18
|
+
| `--from-run <runId>` | Seed every `completed` step from a real run (same math as repair runs); graph drift asks for `--force` |
|
|
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
|
+
| `--fast-retries` | Collapse retry backoff waits to 0 |
|
|
21
|
+
| `--real-time` | Actually wait on `sleep`/backoff instead of fast-forwarding the virtual clock |
|
|
22
|
+
| `--now <iso>` | Pin the virtual clock start (resolves `sleepUntil` dateFrom, business-hours deadlines) |
|
|
23
|
+
| `--artefacts-dir <dir>` | Back `ctx.artefacts.*` on disk (`<dir>/<artefactId>` + `<artefactId>.meta.json`, ids `local-<n>`) |
|
|
24
|
+
| `--env KEY=value` (rep.) | The local `env.template()` overlay โ a missing key is exit 2 `env-template-missing`; values never reach `--ledger-out` |
|
|
25
|
+
| `--step-wall <s>` | Per-step wall, default 600 |
|
|
26
|
+
| `--max-foreach-items <n>` | Override the local foreach cap |
|
|
27
|
+
| `--agents fake\|live` | Fake stubs (default) or the dev API |
|
|
28
|
+
| `--ledger-out <file>` | Write the in-memory ledger (steps, outputs, attempts, effects, events) โ the input of `replay --local` |
|
|
29
|
+
| `--json` | Machine-readable result envelope |
|
|
28
30
|
|
|
29
31
|
## Semantics worth knowing
|
|
30
32
|
|
|
33
|
+
- **`sleep(ms)`** advances the virtual clock by exactly `ms` **milliseconds** โ the SDK's unit and the engine's (`.sleep(3000)` is 3 s, printed as `<id> ยท sleep 3000 ms (3 s) (virtual clock)`); `sleepUntil` jumps to the resolved instant. `--real-time` waits the same `ms`.
|
|
31
34
|
- **Retry backoff** runs on the virtual clock: `backoffSeconds ยท 2^(attempt-1)` capped at `maxBackoffSeconds`, printed as `[retry:<id>] backoff <n>s`.
|
|
32
35
|
- **`foreach` `rateLimit`** is an in-memory token bucket โ delayed starts print `[throttle:<id>] resumeAt=<iso>`; loop `intervalSeconds` prints `kind=loop_interval`.
|
|
33
36
|
- **`suspend`** re-runs `execute` from the top with `resumeData` set โ the documented Mastra semantics, exercised offline.
|
|
@@ -8,6 +8,8 @@ _Source of truth: workflows-spec 05 ยง5.17 (D19-r1/r2). This page is the develop
|
|
|
8
8
|
|
|
9
9
|
`workspace` on `createWorkflow` declares the checkout: `{ kind:'git', repo, ref, credentialsRef, sizeGb?, verify? }` or `{ kind:'empty' }`. Steps mount it with `workspace: { mount: 'rw'|'ro', isolation?: 'worktree' }` and read `ctx.workspace = { path, mount, branch }`.
|
|
10
10
|
|
|
11
|
+
`credentialsRef` is a connection id **or a key you declare under `connections: [{ key, integrationType }]`** โ declare a key; it resolves against the owner agent's own connections on any agent at run time (see [Git credentials](./git-credentials.md)), so the definition never freezes a production id.
|
|
12
|
+
|
|
11
13
|
**The release-at-suspension rule:** when your run waits for a person or a signal, its volume is released: committed work is on the run branch `lua/wf-<lineageId>` on your remote; untracked files โ `node_modules`, build output โ are rebuilt when the next step restores the checkout.
|
|
12
14
|
|
|
13
15
|
**Worktree arms:** parallel arms never share a filesystem; each gets its own clone and branch and the `merge` step integrates them โ put `verify` on the workspace so an agent-resolved conflict must pass your tests. `onConflict:'agent'` lets one resolver turn fix conflicts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lua-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.32.1",
|
|
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",
|
|
@@ -114,9 +114,9 @@
|
|
|
114
114
|
"ts-node": "^10.9.2",
|
|
115
115
|
"tsup": "^8.5.1",
|
|
116
116
|
"@lua/shared-sandbox": "0.0.1",
|
|
117
|
-
"@lua/
|
|
117
|
+
"@lua/workflow-graph": "0.0.1",
|
|
118
118
|
"@lua/shared-source-sync": "0.0.1",
|
|
119
|
-
"@lua/
|
|
119
|
+
"@lua/shared-types": "0.0.1"
|
|
120
120
|
},
|
|
121
121
|
"scripts": {
|
|
122
122
|
"clean": "rm -rf dist temp",
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
# Workflow examples
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Worked examples from workflows-spec 03 ยง3.2 (the spec is normative โ do not restyle them). A file whose header carries a
|
|
4
|
+
`LUA-635:` note departs from the spec's listing on purpose: the compiler is what ships, and the listing was wrong against
|
|
5
|
+
it (a `.then(createStep)` placement used as a string-ref target; a `template(โฆ)` approval title). The note says what
|
|
6
|
+
changed and why; `tests/__tests__/compile.workflow.examples.test.ts` compiles every file here, so a drift cannot return.
|
|
4
7
|
|
|
5
|
-
| File
|
|
6
|
-
|
|
7
|
-
| `research-brief.ts`
|
|
8
|
-
| `outreach.ts`
|
|
9
|
-
| `provision-tenant.ts`
|
|
10
|
-
| `reviewed-brief.ts`
|
|
11
|
-
| `refund-approval.ts`
|
|
8
|
+
| File | Shows |
|
|
9
|
+
| ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
10
|
+
| `research-brief.ts` | (a) sequential + parallel research, typed predicates (`stepOf`/`gt`/`lit`), switch + otherwise |
|
|
11
|
+
| `outreach.ts` | (b) `foreach` + editable approval + an exactly-once send (`ctx.once`), cron schedule, `concurrencyPolicy` |
|
|
12
|
+
| `provision-tenant.ts` | (c) nested workflow + `sleep` + `waitForSignal` |
|
|
13
|
+
| `reviewed-brief.ts` | (d) `specialistStep` โ an ephemeral reviewer role on the owning agent (D25) |
|
|
14
|
+
| `refund-approval.ts` | (e) maker-checker + four-eyes + business-hours escalation chain + a recoverable external step (`onError:'park'`) |
|
|
12
15
|
| `ticket-to-pr.ts` + `pr-review-round.ts` + `linear-ready.trigger.ts` + `github-review.webhook.ts` | (f) the SWE headline: Job tier, worktree arms + merge, review loop over a child workflow, trigger `startWorkflow`, webhook โ `Workflows.signal` |
|
|
13
|
-
| `support-triage.ts`
|
|
14
|
-
| `adversarial-verify.workflow.script.js`
|
|
15
|
-
| `vendor-invoices.ts`
|
|
16
|
+
| `support-triage.ts` | (g) knowledge grounding, mandatory `toolScope` on external content, dataset rows, `ctx.artefacts` |
|
|
17
|
+
| `adversarial-verify.workflow.script.js` | (i) script form โ `step()` inline effect + a library `role:{ref}` |
|
|
18
|
+
| `vendor-invoices.ts` | (j) per-item approvals (`itemsPath`), `env.template()` overlays, `outputVisibility` |
|
|
16
19
|
|
|
17
20
|
Recipes:
|
|
18
21
|
|
|
19
22
|
- Run any of these offline: `lua test workflow <name>` (steering flags in `docs/api/Workflows.md` โ "Testing locally").
|
|
20
23
|
- Give a predicate BOTH truth values under `--agents fake` with `--step-output <id>=<json>` โ the fake stub alone
|
|
21
24
|
cannot reach the `gt(confidence, 0.6)` arm of `research-brief`.
|
|
22
|
-
- Every doc rule worth repeating:
|
|
23
|
-
on `occurrenceId
|
|
25
|
+
- Every doc rule worth repeating: _execute re-runs from the top after resume; execution is at-least-once โ dedupe
|
|
26
|
+
on `occurrenceId`_ (`${lineageId}:${stepId}`); an effect unique across independent runs needs your own business
|
|
24
27
|
key (`refund:${ticketId}`).
|
|
@@ -1,38 +1,75 @@
|
|
|
1
1
|
// One review iteration; runs as a child of ticket-to-pr with the SAME workspace.
|
|
2
|
-
//
|
|
2
|
+
// From workflows-spec 03 ยง3.2 (f) (WF-215 / WF-223 โ the spec is normative). LUA-635: a switch arm is ONE step and a
|
|
3
|
+
// string arm names an agentStep / specialistStep / toolStep / map / workflow declaration (03 ยง3.2.0) โ the listing's
|
|
4
|
+
// `otherwise: 'done'` over a later `.then(createStep({ id: 'done' }))` was a goto the placement rule never had.
|
|
3
5
|
// src/workflows/pr-review-round.ts โ one review iteration; runs as a child of ticket-to-pr with the SAME workspace
|
|
4
6
|
import { z } from 'zod';
|
|
5
7
|
import { createStep, createWorkflow, step, eq, lit, template } from 'lua-cli';
|
|
6
8
|
|
|
7
9
|
const pushFix = createStep({
|
|
8
10
|
id: 'pushFix',
|
|
9
|
-
inputSchema: z.any(),
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
inputSchema: z.any(),
|
|
12
|
+
outputSchema: z.object({ headSha: z.string() }),
|
|
13
|
+
tier: 'job',
|
|
14
|
+
workspace: { mount: 'rw' },
|
|
15
|
+
timeoutSeconds: 600,
|
|
16
|
+
async execute({ workspace }) {
|
|
17
|
+
// git runs through the credential proxy โ no token in this container (05 ยง5.17.3)
|
|
18
|
+
const sha = (await $`git -C ${workspace!.path} rev-parse HEAD`).stdout.trim(); // `$` = the Job image's shell helper (`@lua/coding-harness/shell`); a plain child_process spawn works too โ the Job site has no REQUIRE_BLOCKLIST for `child_process`
|
|
13
19
|
return { headSha: sha };
|
|
14
20
|
},
|
|
15
21
|
});
|
|
16
22
|
|
|
23
|
+
const changesRequested = eq(step('review').path('payload.state'), lit('changes_requested'));
|
|
24
|
+
|
|
17
25
|
export const prReviewRound = createWorkflow({
|
|
18
26
|
name: 'pr-review-round',
|
|
19
27
|
inputSchema: z.object({ prNumber: z.number(), repo: z.string() }),
|
|
20
|
-
outputSchema: z.object({
|
|
28
|
+
outputSchema: z.object({
|
|
29
|
+
state: z.enum(['approved', 'changes_requested', 'timed_out']),
|
|
30
|
+
round: z.number().optional(),
|
|
31
|
+
}),
|
|
21
32
|
})
|
|
22
|
-
.waitForSignal('review', {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
.waitForSignal('review', {
|
|
34
|
+
signal: 'github.review',
|
|
35
|
+
timeoutHours: 168, // delivered by the GitHub webhook (ยง3.7) โ to the PARENT run; the engine descends it into this child (06 ยง6.5.3 step 2b)
|
|
36
|
+
schema: z.object({
|
|
37
|
+
state: z.enum(['approved', 'changes_requested', 'commented']),
|
|
38
|
+
comments: z.array(z.object({ path: z.string().optional(), body: z.string() })),
|
|
39
|
+
}),
|
|
40
|
+
acceptedSources: ['webhook'],
|
|
41
|
+
onTimeout: 'continue',
|
|
42
|
+
})
|
|
43
|
+
.switch([[changesRequested, 'addressReview']]) // no `otherwise`: approved / commented / timed out falls through to `done`
|
|
44
|
+
.agentStep('addressReview', {
|
|
45
|
+
// a CODING TURN: Claude Code headless with git/gh/shell/edit on the mounted checkout (05 ยง5.17.6)
|
|
46
|
+
agentId: 'swe-implementer',
|
|
47
|
+
tier: 'job',
|
|
48
|
+
workspace: { mount: 'rw' },
|
|
49
|
+
timeoutSeconds: 7200,
|
|
50
|
+
jobResources: 'medium',
|
|
51
|
+
prompt: template(
|
|
52
|
+
'Address every review comment in ${stepResults.review.payload.comments} on the current branch. Run the relevant tests. Commit with a message that references the comment you addressed. Do not force-push.'
|
|
53
|
+
),
|
|
54
|
+
toolScope: { jobTools: ['shell', 'read', 'write', 'edit', 'glob', 'grep', 'git'] }, // no `gh`: the push + PR update is the next code step
|
|
30
55
|
outputSchema: z.object({ summary: z.string() }),
|
|
31
56
|
})
|
|
32
|
-
.
|
|
33
|
-
.then(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
.switch([[changesRequested, pushFix]]) // the fix path's second step โ same predicate, an inline createStep as the arm
|
|
58
|
+
.then(
|
|
59
|
+
createStep({
|
|
60
|
+
id: 'done',
|
|
61
|
+
inputSchema: z.any(),
|
|
62
|
+
outputSchema: z.object({ state: z.enum(['approved', 'changes_requested', 'timed_out']) }),
|
|
63
|
+
async execute({ getStepResult }) {
|
|
64
|
+
const r = getStepResult<{ received: boolean; timedOut?: boolean; payload?: { state: string } }>('review');
|
|
65
|
+
return {
|
|
66
|
+
state: r?.received
|
|
67
|
+
? r.payload!.state === 'changes_requested'
|
|
68
|
+
? 'changes_requested'
|
|
69
|
+
: 'approved'
|
|
70
|
+
: 'timed_out',
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
)
|
|
38
75
|
.commit();
|