lua-cli 3.32.1 โ 3.32.2
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 +339 -102
- package/dist/api-exports.js +1334 -201
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +3287 -1634
- package/dist/index.js.map +1 -1
- package/dist/voice/test/index.d.ts +54 -54
- package/dist/workflow-builder.d.ts +224 -45
- package/dist/workflow-builder.js +747 -171
- package/dist/workflow-builder.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/api/LuaWorkflow.md +16 -16
- package/docs/api/Workflows.md +10 -0
- package/docs/workflows/correlation-keys.md +1 -0
- package/docs/workflows/limits.md +6 -0
- package/docs/workflows/script-form.md +20 -10
- package/docs/workflows/testing-offline.md +22 -20
- package/docs/workflows/workspaces-and-long-steps.md +36 -2
- package/package.json +3 -3
- package/template/examples/workflows/pr-review-round.ts +7 -3
- package/template/examples/workflows/ticket-to-pr.ts +43 -36
- package/template/package.json +1 -1
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# lua-cli v3.32.
|
|
1
|
+
# lua-cli v3.32.2
|
|
2
2
|
|
|
3
|
-
Welcome to the comprehensive API documentation for lua-cli v3.32.
|
|
3
|
+
Welcome to the comprehensive API documentation for lua-cli v3.32.2. 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
|
@@ -40,7 +40,7 @@ A typed code step. `inputSchema` / `outputSchema` / `resumeSchema` are zod schem
|
|
|
40
40
|
| `onError` | What the **final** failure does to the run: `'fail'` (default โ the run unwinds `failed`) ยท `'continue'` (the run goes on; the step's result is the **continued-failure value** `{ __lua_workflow:'continued_failure', failed:true, error:{code,message}, text:'' }` โ `${stepResults.<id>.text}` renders `''`, `getStepResult(id)` returns it, a `conditional` can branch on `stepResults.<id>.failed`) ยท `'park'` (the step parks on an exception gate for `retry-step` / `resolve-step` โ see [When a step parks](../workflows/recovery.md)) |
|
|
41
41
|
| `requiredConnections` | Connection ids that must mount before `execute` runs |
|
|
42
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()
|
|
43
|
+
| `execute(ctx)` | `ctx`: `inputData`, `resumeData`, `getInitData()`, `getStepResult(id)`, `state`, `suspend()`, `bail()`, `bailRun()`, `once()` (worker tier โ not on the Job tier yet, LUA-706), `log()`, `env`, `signal`, `runtime`, `artefacts`, `occurrenceId`, `lineageId`, `workspace?` (Job tier; `exec?` / `$?` with it) |
|
|
44
44
|
|
|
45
45
|
`ctx.suspend(payload)` parks the step for `Workflows.resume(runId, stepId, resumeData)`; on resume **`execute` re-runs from the top** with `ctx.resumeData` set.
|
|
46
46
|
|
|
@@ -54,21 +54,21 @@ Config: `name`, `description?`, `inputSchema`, `outputSchema?`, `budget?` (`maxC
|
|
|
54
54
|
|
|
55
55
|
**The call site places.** Every builder call โ `then`, `agentStep`, `specialistStep`, `toolStep`, `map`, `approval`, `waitForSignal`, `sleep`, `sleepUntil`, `foreach`, `parallel`, `branch`, `switch`, `dowhile`, `dountil`, `workflow` โ appends **exactly one** entry to the chain where it is called. Containers take `StepRef`s: an inline step object, or a **string** naming an `agentStep`/`specialistStep`/`toolStep` declared elsewhere in the same chain โ a string ref inside a container means "declare here, inside me" and does not append a second top-level entry. An id declared but never placed is `WORKFLOW_UNPLACED_STEP` (warning locally, error at push); referenced but never declared is `unknown-step-ref` at `.commit()`.
|
|
56
56
|
|
|
57
|
-
| Verb | Entry
|
|
58
|
-
| ------------------------------------------------------------------------------------------------------- |
|
|
59
|
-
| `.then(step, input?)` | Sequential step
|
|
60
|
-
| `.agentStep(id, { agentId, prompt, outputSchema?, toolScope?, tier?, โฆ })` | Agent turn (`prompt` is a `template(...)`)
|
|
61
|
-
| `.specialistStep(id, { role, prompt, โฆ })` | Ephemeral role on the owning agent (D25)
|
|
62
|
-
| `.toolStep(id, { toolId, input? })` | One tool call โ `toolId` must name a `LuaTool` the compiler can see as a tool primitive (declared in its own module and exported, or registered on the agent); a tool object defined inline in the workflow file is not detected and fails `WORKFLOW_TOOL_REF_UNRESOLVED` at compile
|
|
63
|
-
| `.map(descriptors, { id })` | Data reshaping โ `id` is **required** once the workflow has โฅ 2 maps (`map-id-required`)
|
|
64
|
-
| `.parallel([...arms], { merge? })` | Concurrent arms; a `[map, step]` pair is a legal arm (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
|
|
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 }`; payload keys named like a credential (`token`, `secret`, `apiKey`, `accessToken`, โฆ) are persisted `[REDACTED]` (the `signal-reserved-key` warning says so at push / compose) โ see [Reserved payload keys](Workflows.md#reserved-payload-keys) |
|
|
71
|
+
| `.workflow(id, ref, input?, { workspace? })` | Child run the parent waits for |
|
|
72
72
|
|
|
73
73
|
### Typed predicates
|
|
74
74
|
|
package/docs/api/Workflows.md
CHANGED
|
@@ -69,6 +69,16 @@ Resumes a step suspended with `ctx.suspend(...)`. The loser of a resume race rec
|
|
|
69
69
|
|
|
70
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
|
+
#### Reserved payload keys
|
|
73
|
+
|
|
74
|
+
The run ledger redacts by **key name**, not by value: a payload member whose key is one of
|
|
75
|
+
|
|
76
|
+
`secret`, `token`, `password`, `passwd`, `pwd`, `passphrase`, `api_key`, `apikey`, `access_key`, `secret_key`, `private_key`, `client_secret`, `authorization`, `auth_token`, `access_token`, `id_token`, `refresh_token`, `session_key`, `credential`, `credentials`
|
|
77
|
+
|
|
78
|
+
is persisted as `[REDACTED]` whatever it holds. A key matches when one of those names is a whole segment of it โ segments split on `_`, `-`, `.` and spaces, case-insensitive (`token`, `user_token`, `x-token`, `Token`; the compounds also as one camelCase word: `apiKey`, `refreshToken`, `accessToken`) โ while a name glued to another word (`tokens`, `tokenCount`, `accessTokenExpiry`) is not. A member whose value is an object or array is walked, not replaced whole.
|
|
79
|
+
|
|
80
|
+
This is by design (a credential must never land in a run), so the placeholder is what `stepResults.<id>.payload`, the run output, `lua workflows status --steps`, exports and the chat run card show โ a workflow cannot read a data field with one of these names back. Name data fields otherwise (`ticketRef`, `handle`, `code`, `verifier`). The same rule applies to every step output and suspend payload. A `waitForSignal` whose `schema` declares a property with a reserved name draws the non-blocking `signal-reserved-key` warning at push / compose (the property, the placeholder it would read as, the rename), and `lua workflows signal` warns before sending a payload that carries a reserved key (the signal is still delivered).
|
|
81
|
+
|
|
72
82
|
### Reserved members
|
|
73
83
|
|
|
74
84
|
`startBatch`, `signalByKey`, `raiseBudget`, `setGoal` and `goals.{list,get,pause,resume,close}` are part of the API surface (the member list is frozen) and throw `WorkflowApiError { code: 'WORKFLOWS_API_UNAVAILABLE' }` until their server routes ship. For `startBatch`: from an agent turn, prefer the `startWorkflowRunBatch` tool (one consent card for the whole batch, D23-r5).
|
|
@@ -6,6 +6,7 @@ _Source of truth: workflows-spec (P1-3 / P1-14). This page is the developer summ
|
|
|
6
6
|
|
|
7
7
|
- `Workflows.signalByKey(nameOrId, correlationKey, name, payload?)` delivers a signal **by business key** โ no runId bookkeeping in your webhook; `allowMultiple` fans out when several runs share the key.
|
|
8
8
|
- `Workflows.list({ correlationKey })` / `lua workflows runs --correlation-key` find the run(s).
|
|
9
|
+
- A signal payload member named like a credential (`token`, `secret`, `password`, `apiKey`, `authorization`, `credentials`, โฆ) is persisted `[REDACTED]` by design and cannot be read back by the workflow โ name data fields otherwise; the full list and the rule are under [Reserved payload keys](../api/Workflows.md#reserved-payload-keys). `lua workflows signal` warns before sending one.
|
|
9
10
|
- `tags` are free-form labels (AND-ed in list filters); `replyTo: { channel, threadId }` routes the terminal reply back to the originating conversation.
|
|
10
11
|
|
|
11
12
|
Offline: `--correlation-key`, `--tag`, `--reply-to <channel>:<threadId>` and `--on-behalf-of` stamp `ctx.runtime.*`; the would-be reply prints on the terminal instead of sending.
|
package/docs/workflows/limits.md
CHANGED
|
@@ -4,6 +4,12 @@ _Source of truth: workflows-spec 10 / 16. This page is the developer summary; th
|
|
|
4
4
|
|
|
5
5
|
Per-run: `budget.maxCredits` (over the org's `maxCreditsPerRun` default 250 โ push blocker `budget-exceeds-cap` until an org admin raises it, R23), `budget.maxDurationSeconds`, `budget.maxJobSeconds`. Structural: `foreach` `maxItems` (default 256 โ with `chunk:{size}` the cap bounds CHUNKS), loop `maxIterations` (default 100), nesting depth โค 3, `startBatch` per-item `idempotencyKey`. Push-time caps preflight reports `warnings[]` + `blockers[]`; `lua push --strict-caps` promotes warnings.
|
|
6
6
|
|
|
7
|
+
## Credits
|
|
8
|
+
|
|
9
|
+
`budget.maxCredits` counts agent steps, never tokens. An inline agent step settles a flat **1** credit when it completes; a `tier:'job'` attempt settles a flat **4** at its first claim (a retry is a new attempt; a seat-metered org settles 0 and is metered from the `workflow:job` usage event instead). Before dispatching an agent step the engine checks `remaining = maxCredits โ spent โ reserved` against the step's reserve (1 inline, 4 Job) and parks the run `suspended{gate.kind:'budget'}` โ `nextAction:'raise_budget'`, `lua workflows raise-budget <runId> --credits <n>` โ when it is short. So `maxCredits: 40` buys ten Job-tier attempts, and a `maxCredits` under 4 can never dispatch one.
|
|
10
|
+
|
|
11
|
+
Nothing meters a Job-tier attempt's tokens against the credits while it runs. Each attempt is bounded by its own ceilings: the step wall (`timeoutSeconds`), `maxInputTokens` (input-side tokens โ prompt + cache read + cache creation โ default **4M**, โ $1โ13 per attempt on a Sonnet-class model; 1M..500M), `maxMessages` (default 400) and `maxTurns`. Crossing one ends the attempt `attempt_budget_exhausted` from its last checkpoint (see [Long steps and checkpoints](long-steps-and-checkpoints.md)). Size `maxInputTokens` for what one attempt may cost and `maxCredits` for how many attempts the run may make.
|
|
12
|
+
|
|
7
13
|
## Org pacing
|
|
8
14
|
|
|
9
15
|
`workflowPolicy.pacing` is an org-admin rate limit **per outbound host** shared by every run in the org. A paced step shows as `ready (pacing_deferred โฆ)` and counts against no per-run budget except `maxDurationSeconds`. Code-step `fetch` calls are **not** paced (B26, normative) โ pacing governs `tool`/`http` steps. Only `lua features` / the desktop **Workflows policy** page / R23 can change it.
|
|
@@ -5,9 +5,15 @@ _Source of truth: workflows-spec 04 ยง4.3 (D15-r2, Cluster J B12 โ WF-527, WB-
|
|
|
5
5
|
A script workflow is a plain JavaScript ES module โ no TypeScript, no `import`/`require` โ that begins with `export const meta = {โฆ}` and continues with a body run in an async context whose top-level `return` is the run output. The host API (`agent`, `tool`, `parallel`, `foreach`, `sleep`, `approval`, `waitForSignal`, `memo`, `step`, `shell`, `merge`, `log`, `phase`, โฆ) is injected as module-scope bindings; the same file is what the composer produces (`lua workflows export`) and what a template freezes.
|
|
6
6
|
|
|
7
7
|
```js
|
|
8
|
-
export const meta = {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
export const meta = {
|
|
9
|
+
name: 'judge-panel',
|
|
10
|
+
description: 'Three drafts, three judges, synthesize the winner',
|
|
11
|
+
phases: [{ title: 'Draft' }, { title: 'Judge' }, { title: 'Synthesize' }],
|
|
12
|
+
};
|
|
13
|
+
const drafts = await parallel([
|
|
14
|
+
agent('Draft, technical angle', { phase: 'Draft' }),
|
|
15
|
+
agent('Draft, market angle', { phase: 'Draft' }),
|
|
16
|
+
]);
|
|
11
17
|
const scores = await foreach(drafts, (d) => agent(`Score this draft: ${d}`, { phase: 'Judge' }));
|
|
12
18
|
const picked = await step('pick-winner', () => ({ winner: drafts[scores.indexOf(Math.max(...scores))] }));
|
|
13
19
|
return picked;
|
|
@@ -27,17 +33,21 @@ Run in this order โ the same order lua-api re-validates on push (one implement
|
|
|
27
33
|
|
|
28
34
|
`lua compile` discovers every `src/workflows/*.workflow.script.js` (outside the TypeScript project โ never bundled) and emits `ManifestWorkflow{ form:'script', script, scriptHash, meta, journalProtocolVersion: 1, scriptSurface, roleBindings }` โ the graph members are absent. `lua push` sends `{ form:'script', script, scriptHash, meta, journalProtocolVersion, roleBindings, schedule?, scheduleInput?, budget? }` and skips the bundle upload. The stored row is `WorkflowVersion{ form:'script', dynamic:false }` โ identical to a composed script minus `dynamic:true`. `WORKFLOW_SCRIPT_FORM_NOT_PUSHABLE` is struck (never emitted). An org whose `workflowPolicy.composeForm` is `'graph'` refuses the push with `compose-form-forbidden`, exactly as it refuses a composed script.
|
|
29
35
|
|
|
36
|
+
**The parent's form (LUA-717).** `lua push` creates a workflow the server does not have with `form` taken from the compiled definition (`POST /developer/workflows/:agentId { name, description, form:'script' }`). A workflow's form is fixed by its first version: a version whose form differs from the parent's is refused `400 WORKFLOW_FORM_MISMATCH { workflowForm, versionForm }` โ rename the script, or delete the workflow (`lua workflows delete`) and push again. The one exception is an **empty** parent (no version of any kind, sandbox included), which adopts the form of its first pushed version: a lua-cli older than the LUA-717 release sent no `form` on create, so its script workflows were left as graph shells, and the first script push is what makes such a shell usable.
|
|
37
|
+
|
|
38
|
+
**Script workflows are pushed from the CLI only.** Chat compose (`composeWorkflow`, `POST โฆ/compose`) composes **graph** workflows; a `form:'script'` compose is refused `script-validation-tick-unavailable`, because the WF-323 validation tick is not bound on the production server (by design โ a script is never persisted un-validated). A script that should exist on the server goes through `src/workflows/<name>.workflow.script.js` + `lua push workflow`.
|
|
39
|
+
|
|
30
40
|
## `lua test workflow <name>` on a script
|
|
31
41
|
|
|
32
42
|
Runs the file through the runner's own replay wrapper (parity context) in an offline tick loop: every tick re-runs the script against the journal so far, the wrapper parks with its async intents, the driver settles each with a **fake completion** (no API call) and re-ticks until `done`/`bailed`/`failed`. Steering:
|
|
33
43
|
|
|
34
|
-
| Flag
|
|
35
|
-
|
|
|
36
|
-
| `--input <json
|
|
37
|
-
| `--step-output <label
|
|
38
|
-
| `--max-ticks <n>`
|
|
39
|
-
| `--ledger-out <file>`
|
|
40
|
-
| `--json`
|
|
44
|
+
| Flag | Effect |
|
|
45
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `--input <json\|@file>` | the script's `args` (default `meta.sampleArgs ?? {}`) |
|
|
47
|
+
| `--step-output <label\|seq>=<json>` | the completion for the intent with that `narration.label` (an `agent()`'s `label`/`phase`) or journal seq |
|
|
48
|
+
| `--max-ticks <n>` | default 64 โ `SCRIPT_TICK_LIMIT` (exit 4) beyond it |
|
|
49
|
+
| `--ledger-out <file>` | the local journal (`{ form:'script', journal[] }`) |
|
|
50
|
+
| `--json` | the result envelope |
|
|
41
51
|
|
|
42
52
|
Exit 0 `done`/`bailed`; 4 `failed` (`SCRIPT_THREW`, `SCRIPT_DEADLOCK` โ parked with zero intents, `SCRIPT_TICK_LIMIT`); 3 when `@lua/sandbox-runtime` is not resolvable from the project (`REPLAY_RUNTIME_UNAVAILABLE`, see `replay-local.md`); 2 on flag grammar.
|
|
43
53
|
|
|
@@ -8,25 +8,26 @@ _Source of truth: workflows-spec 03 ยง3.9 (WF-204/WF-225/WF-332). This page is t
|
|
|
8
8
|
|
|
9
9
|
## Steering flags โ fully scriptable, no stdin
|
|
10
10
|
|
|
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>` |
|
|
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
|
|
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>` | The cap on every step wall, default 600 โ a node's own `timeoutSeconds` (or its step definition's) applies below it; a step past its wall fails `TIMEOUT` and its retry policy applies; the attempt's `ctx.signal` aborts at the wall โ the driver cannot kill in-process code, so a step that ignores the signal keeps running in the background until it settles (LUA-689) |
|
|
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 |
|
|
30
|
+
| `--workspace <dir>` | The checkout Job-tier **code** steps run in: gives them `ctx.workspace` and `ctx.exec` / `ctx.$` against that directory โ the pod's allowlist (`git`, `gh`, `npm`, โฆ, argv only, no shell) on your own PATH and git credentials. Without it a Job-tier step's `$` rejects `ExecError{code:'EXEC_UNAVAILABLE'}` naming the flag |
|
|
30
31
|
|
|
31
32
|
## Semantics worth knowing
|
|
32
33
|
|
|
@@ -34,7 +35,8 @@ _Source of truth: workflows-spec 03 ยง3.9 (WF-204/WF-225/WF-332). This page is t
|
|
|
34
35
|
- **Retry backoff** runs on the virtual clock: `backoffSeconds ยท 2^(attempt-1)` capped at `maxBackoffSeconds`, printed as `[retry:<id>] backoff <n>s`.
|
|
35
36
|
- **`foreach` `rateLimit`** is an in-memory token bucket โ delayed starts print `[throttle:<id>] resumeAt=<iso>`; loop `intervalSeconds` prints `kind=loop_interval`.
|
|
36
37
|
- **`suspend`** re-runs `execute` from the top with `resumeData` set โ the documented Mastra semantics, exercised offline.
|
|
37
|
-
- **`ctx.once`** settles against the in-memory effects map: a re-drive over the same ledger sends nothing twice.
|
|
38
|
+
- **`ctx.once`** settles against the in-memory effects map: a re-drive over the same ledger sends nothing twice. (On the platform it is served to worker-tier steps; the Job pod does not serve `ctx.once` yet โ LUA-706 โ so a Job-tier step dedupes on `occurrenceId` itself.)
|
|
39
|
+
- **`ctx.getInitData()`** is `null` on a run without `--input` and **`ctx.runtime`** is `{ trigger:'sdk', principalKind:'user' }` โ the same shapes the pod and the worker serve, so a step that branches on them behaves offline as it does on the platform.
|
|
38
40
|
- **Not emulated offline**: the org pacing policy (no org context โ `pacing_deferred` never appears locally), the runner mid-run kill (Ctrl-C is the local analogue), `backfillOnEnable`, and the `startWorkflowRunBatch` tool.
|
|
39
41
|
|
|
40
42
|
## Example
|
|
@@ -6,7 +6,7 @@ _Source of truth: workflows-spec 05 ยง5.17 (D19-r1/r2). This page is the develop
|
|
|
6
6
|
|
|
7
7
|
## Workspaces
|
|
8
8
|
|
|
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 = {
|
|
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 = { root, mount, branch, headSha, isolation, baseSha?, arm?, backend? }` โ `root` is the absolute directory of the checkout (`/workspace`); `baseSha` is the commit the run's base ref resolved to at provision (`headSha` is where this step's checkout is), `arm` the worktree arm id under `isolation:'worktree'`, `backend` the volume backend โ the three are present when the pod hands the run's stamps to the step and absent otherwise (an `empty` workspace, a shared mount). The pre-3.33 name `path` is served by the Job pod as a deprecated alias for one minor (it warns once per step) and then removed; new code reads `root`.
|
|
10
10
|
|
|
11
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
12
|
|
|
@@ -14,6 +14,40 @@ _Source of truth: workflows-spec 05 ยง5.17 (D19-r1/r2). This page is the develop
|
|
|
14
14
|
|
|
15
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.
|
|
16
16
|
|
|
17
|
+
## Commands from a code step โ `ctx.exec` / `ctx.$`
|
|
18
|
+
|
|
19
|
+
A Job-tier **code** step has no `child_process` โ an import of it fails `lua compile` with `node-capability-unavailable` โ and it builds no code from strings and compiles no WebAssembly (see the note at the end of this section). It runs commands through `ctx.exec` / `ctx.$`, which the Job pod executes on its behalf:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const runTests = createStep({
|
|
23
|
+
id: 'runTests',
|
|
24
|
+
tier: 'job',
|
|
25
|
+
workspace: { mount: 'rw' },
|
|
26
|
+
inputSchema: z.any(),
|
|
27
|
+
outputSchema: z.object({ passed: z.boolean() }),
|
|
28
|
+
async execute({ $, exec, inputData, log }) {
|
|
29
|
+
await $!.strict`npm ci`; // `.strict` throws ExecError on a non-zero exit
|
|
30
|
+
const r = await $!`npm test -- --maxWorkers=2`; // { code, stdout, stderr, durationMs, truncated, timedOut }
|
|
31
|
+
const sha = (await exec!(['git', 'rev-parse', 'HEAD'], { cwd: 'packages/app' })).stdout.trim();
|
|
32
|
+
await $!.strict`git add -- ${'CHANGELOG.md'}`; // each ${โฆ} is ONE argument; `--` keeps a data value out of git's options
|
|
33
|
+
log(`tests ${r.code === 0 ? 'passed' : 'failed'} at ${sha}`);
|
|
34
|
+
return { passed: r.code === 0 };
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **Argv only, never a shell.** `$` splits its literal text on whitespace (with `'โฆ'` / `"โฆ"` quoting) and passes every `${value}` as exactly one argument, never re-parsed โ a title with spaces or a body with newlines is one argument. There is no `cd`, `&&`, `|`, glob or `$VAR`: a second command is a second call, and the cwd is the workspace root (`{ cwd: 'packages/app' }` for a directory inside it).
|
|
40
|
+
- **Allowlist.** `git`, `gh`, `pnpm`, `npm`, `npx`, `node`, `yarn`, `python3`, `pytest`, `make` โ bare names, resolved from the pod image's own PATH. `sh -c`, `bash`, `curl` or a path is refused before anything runs (`ExecError{code:'EXEC_REFUSED'}`, `reason` says why). **The allowlist is not the image's inventory:** today's Job image ships `git`, `gh`, `node`, `npm`, `npx` (plus `corepack`, `gitleaks`, `claude`, `curl` for the harness); `pnpm`, `yarn`, `python3`, `pytest` and `make` are on the list for the local driver and a future image but fail `EXEC_REFUSED/binary_not_found` in the pod today โ run them offline, or use `npx` / `npm exec` for what npm can fetch.
|
|
41
|
+
- **git rules.** The same as a coding turn's git tool: no `-C`, `--git-dir`, `--work-tree`, `--exec-path` (the repository is the workspace and the binary is the host's โ use `{ cwd }`); no `-c` over what git would execute or route through (`core.hooksPath`, `core.fsmonitor`, `protocol.*`, `credential.*`, `url.*`, proxies, editors, filters); no `git config` (the commit identity and every setting a step needs come from the host); no `credential*`, `push-mirror`, `daemon`. **`git push`** is refused on an `ro` mount and with `--force` / `-f` / `--force-with-lease` / `--delete` / `--mirror` / `--prune` / `--all` / `--tags`, and every push first runs the same pre-push secret scan the harness's own push runs (05 ยง5.17.6) โ a finding refuses it (`EXEC_REFUSED/git_push_refused`). Put `--` before positional values that come from data: `$\`git add -- ${file}\``, `$\`git log -- ${path}\``.
|
|
42
|
+
- **Budget.** `timeoutMs` (default 10 min) is capped by the step's remaining wall; on expiry the command's whole process tree is killed and the call rejects `EXEC_TIMEOUT`. stdout and stderr are each kept to 1 MiB (`truncated`). One command runs at a time.
|
|
43
|
+
- **Env.** The command sees a scrubbed env โ no token, no `LUA_WF_*`, no cloud credential. git runs exactly as the harness's own git does: through the credential-proxy remote, hooks and other repo-configured commands off, `GIT_TERMINAL_PROMPT=0`, the run's commit identity; `gh` and the package managers run non-interactive. A step that declares `jobTools: ['gh']` gives its `gh` (and only `gh`) the sidecar's gh proxy env, exactly what a coding turn's `gh` tool gets โ the proxy admits `gh pr create` / `gh pr edit` / `gh pr comment` on the pinned repository and nothing else: **no `gh pr merge`** (a merge stays with a person), no `/graphql`. `env: { NODE_ENV: 'test' }` adds variables; `PATH`, `HOME`, `GIT_*`, `GH_*`, `LUA_*`, `NODE_OPTIONS` cannot be overridden.
|
|
44
|
+
- **Errors.** A non-zero exit is data (`result.code`). `exec.strict` / `$.strict` throw an `ExecError` โ `name === 'ExecError'`, `code` one of `EXEC_FAILED` | `EXEC_TIMEOUT` | `EXEC_REFUSED` | `EXEC_UNAVAILABLE`, with `exitCode`, `stdout`, `stderr`. Check `name` / `code`, not `instanceof` (the pod runs a step in its own realm). `undefined` interpolated into `$` is refused, never stringified.
|
|
45
|
+
- **Tier.** Worker-tier steps have neither `exec` nor `$`. Shell work with a model in the loop is a Job-tier `agentStep` (its `shell` tool, below). The command line, exit code and duration are logged to the run's job-logs; the output is not โ `log()` what matters.
|
|
46
|
+
|
|
47
|
+
An `ro` mount is read-only at the volume (the PVC is mounted `readOnly`): a command that writes fails `EROFS`, and `git push` is refused on it.
|
|
48
|
+
|
|
49
|
+
**No code from strings, no WebAssembly โ Job tier only.** Inside a Job-tier code step, `eval`, `new Function` (the `Function` constructor reached any way, `Buffer.constructor` included) and `new WebAssembly.Module` / `WebAssembly.compile` / `WebAssembly.instantiate` throw โ `EvalError` / `CompileError`, surfaced as `USER_CODE_ERROR` with the reason named โ because the pod runs the step with code generation off in both realms (`--disallow-code-generation-from-strings` on the child process, `codeGeneration: { strings: false, wasm: false }` on the vm context). `WebAssembly.validate` still answers (it compiles nothing). A library that builds code from strings at import time fails the same way. The worker tier has neither restriction: a step that needs either stays `tier: 'worker'`.
|
|
50
|
+
|
|
17
51
|
## Coding turns
|
|
18
52
|
|
|
19
53
|
A Job-tier `agentStep` is a coding turn (Claude Code or the generic harness โ see [Coding harness](./coding-harness.md)) with the `WORKFLOW_JOB_TOOLS` set (`shell`, `read`, `write`, `edit`, `glob`, `grep`, `git`, โฆ). MCP connections mount via `toolScope.connectionIds`: your connections are mounted through a local proxy; the model never sees a token, and a tool that needs approval is refused inside the turn โ hand it to a worker-tier step.
|
|
@@ -26,4 +60,4 @@ GitHub review loops: the PR body carries `<!-- lua-run:<runId> -->`; the GitHub
|
|
|
26
60
|
|
|
27
61
|
## Offline
|
|
28
62
|
|
|
29
|
-
The local driver emulates the tier: `--workspace <dir
|
|
63
|
+
The local driver emulates the tier: `--workspace <dir>` gives Job-tier code steps `ctx.workspace` and `ctx.exec` / `ctx.$` against that directory (the pod's allowlist, your own PATH and git credentials; without the flag a Job-tier step's `$` rejects `EXEC_UNAVAILABLE`), `--job-wall <s>` rehearses the segment wall for agent steps. `credentialsRef` is printed and never resolved offline.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lua-cli",
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.2",
|
|
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/workflow-graph": "0.0.1",
|
|
118
117
|
"@lua/shared-source-sync": "0.0.1",
|
|
119
|
-
"@lua/shared-types": "0.0.1"
|
|
118
|
+
"@lua/shared-types": "0.0.1",
|
|
119
|
+
"@lua/workflow-graph": "0.0.1"
|
|
120
120
|
},
|
|
121
121
|
"scripts": {
|
|
122
122
|
"clean": "rm -rf dist temp",
|
|
@@ -13,9 +13,13 @@ const pushFix = createStep({
|
|
|
13
13
|
tier: 'job',
|
|
14
14
|
workspace: { mount: 'rw' },
|
|
15
15
|
timeoutSeconds: 600,
|
|
16
|
-
async execute({ workspace }) {
|
|
17
|
-
//
|
|
18
|
-
|
|
16
|
+
async execute({ $, workspace, log }) {
|
|
17
|
+
// `ctx.$` (LUA-682) runs an allow-listed binary in the checkout โ argv only, no shell โ with git's remote on the
|
|
18
|
+
// credential proxy: no token in this container (05 ยง5.17.3). `child_process` is NOT available to a code step
|
|
19
|
+
// (`lua compile` refuses it: node-capability-unavailable); a coding turn's `shell` tool is the other way to run
|
|
20
|
+
// commands. The harness pushes the branch at the checkpoint / terminal; this step reports what it pushed.
|
|
21
|
+
const sha = (await $!.strict`git rev-parse HEAD`).stdout.trim();
|
|
22
|
+
log(`pushed ${sha} on ${workspace!.branch ?? 'the run branch'}`);
|
|
19
23
|
return { headSha: sha };
|
|
20
24
|
},
|
|
21
25
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Linear label -> clone -> parallel worktree arms -> merge -> tests -> PR -> review loop -> approval -> merge.
|
|
1
|
+
// Linear label -> clone -> parallel worktree arms -> merge -> tests -> PR (coding turn) -> review loop -> approval -> the decision (the merge stays human).
|
|
2
2
|
// From workflows-spec 03 ยง3.2 (f) (WF-215 / WF-223 โ the spec is normative). LUA-635: `openPr` is placed by `.then(openPr)` on
|
|
3
3
|
// both test outcomes โ the listing's `otherwise: 'openPr'` named a `.then(createStep)` placement, which never declares an id
|
|
4
4
|
// (03 ยง3.2.0); a string arm names an agentStep / specialistStep / toolStep / map / workflow declaration. `mergeGate.title` is a
|
|
@@ -16,49 +16,43 @@ const runTests = createStep({
|
|
|
16
16
|
workspace: { mount: 'rw' },
|
|
17
17
|
timeoutSeconds: 3600,
|
|
18
18
|
jobResources: 'large', // a 15โ40 min monorepo suite fits one 4 h segment (D19-r1); up to 86 400 s is legal since D19-r2 โ see ยง3.2 (h) for a step that crosses the segment boundary
|
|
19
|
-
async execute({
|
|
20
|
-
|
|
19
|
+
async execute({ $, log }) {
|
|
20
|
+
// `ctx.$` runs ONE allow-listed binary per call, argv only, in the checkout (LUA-682): no `cd`, no `&&` โ the
|
|
21
|
+
// cwd is the workspace root and a second command is a second call. `.strict` throws on a non-zero exit; the
|
|
22
|
+
// plain form returns it, which is what a test run wants.
|
|
23
|
+
await $!.strict`npm ci`; // untracked node_modules were rebuilt on restore (05 ยง5.17.5, E12)
|
|
24
|
+
const r = await $!`npm test -- --maxWorkers=2`; // `--maxWorkers=2` keeps a mongodb-memory-server-per-file suite inside `large`'s 8 GiB โ over it the container is OOM-killed and the step fails `job_oom_killed` (customer fault, 05 ยง5.17.4; ยง3.12 note)
|
|
21
25
|
log(r.stdout.slice(-4000));
|
|
22
|
-
return { passed: r.
|
|
26
|
+
return { passed: r.code === 0, summary: r.stdout.slice(-2000) };
|
|
23
27
|
},
|
|
24
28
|
});
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
tier: 'job',
|
|
31
|
-
workspace: { mount: 'ro' },
|
|
32
|
-
sideEffects: 'external',
|
|
33
|
-
onError: 'park', // a PR is an external effect: a platform fault PARKS (06 ยง6.3.5); `gh` is idempotent on the branch anyway
|
|
34
|
-
async execute({ workspace, inputData, runId }) {
|
|
35
|
-
const out =
|
|
36
|
-
await $`cd ${workspace!.path} && gh pr create --fill --title ${inputData.title} --body ${`Closes ${inputData.ticketId}\n\n<!-- lua-run:${runId} -->`}`; // the run id in the PR body is what the GitHub webhook uses to route review signals (ยง3.7)
|
|
37
|
-
const url = out.stdout.trim();
|
|
38
|
-
return { prNumber: Number(url.split('/').pop()), url };
|
|
39
|
-
},
|
|
40
|
-
});
|
|
30
|
+
// `openPr` is a CODING TURN (05 ยง5.17.6) โ the pilot's real pattern: the harness's own `gh` tool talks to the sidecar's
|
|
31
|
+
// gh proxy. A Job-tier code step can run `gh` too (`ctx.$`, with `jobTools:['gh']` on the step), but the proxy admits
|
|
32
|
+
// `gh pr create` / `gh pr edit` / `gh pr comment` on the pinned repo only โ no merge, no /graphql (T17-D-SE25) โ and the
|
|
33
|
+
// end-to-end path is the sidecar's to prove; the coding turn is what ships PRs today.
|
|
41
34
|
|
|
42
|
-
const
|
|
43
|
-
id: '
|
|
44
|
-
inputSchema: z.object({ approved: z.boolean(),
|
|
45
|
-
outputSchema: z.object({ merged: z.boolean() }),
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
const recordDecision = createStep({
|
|
36
|
+
id: 'recordDecision',
|
|
37
|
+
inputSchema: z.object({ approved: z.boolean(), prNumber: z.number() }), // projected by the `mergeInput` map below โ an approval's output carries `approved` (and `decidedBy`, `editRevision`โฆ), never the payload it was shown (docs/workflows/approvals.md), so the PR number is read back from `openPr`
|
|
38
|
+
outputSchema: z.object({ approved: z.boolean(), prNumber: z.number(), merged: z.boolean() }),
|
|
39
|
+
async execute({ inputData, log }) {
|
|
40
|
+
// The merge itself stays with a person: `gh pr merge` is refused by the sidecar BY DESIGN (a contents write; human-only-merge
|
|
41
|
+
// orgs keep contents:write off the App, 11 T21), so the workflow ends at the decision โ `merged` is always false here and
|
|
42
|
+
// the approver merges when ready.
|
|
43
|
+
log(
|
|
44
|
+
inputData.approved
|
|
45
|
+
? `PR #${inputData.prNumber} approved โ merge when ready`
|
|
46
|
+
: `PR #${inputData.prNumber} not approved`
|
|
47
|
+
);
|
|
48
|
+
return { approved: inputData.approved, prNumber: inputData.prNumber, merged: false };
|
|
55
49
|
},
|
|
56
50
|
});
|
|
57
51
|
|
|
58
52
|
export const ticketToPr = createWorkflow({
|
|
59
53
|
name: 'ticket-to-pr',
|
|
60
54
|
description:
|
|
61
|
-
'Linear label โ implement/tests/docs in parallel worktrees โ merge โ test suite โ PR โ review rounds โ approval โ
|
|
55
|
+
'Linear label โ implement/tests/docs in parallel worktrees โ merge โ test suite โ PR โ review rounds โ approval โ decision (a human merges)',
|
|
62
56
|
inputSchema: z.object({
|
|
63
57
|
ticketId: z.string(),
|
|
64
58
|
title: z.string(),
|
|
@@ -66,7 +60,7 @@ export const ticketToPr = createWorkflow({
|
|
|
66
60
|
repo: z.string(),
|
|
67
61
|
baseRef: z.string().default('main'),
|
|
68
62
|
}),
|
|
69
|
-
outputSchema: z.object({
|
|
63
|
+
outputSchema: z.object({ approved: z.boolean(), prNumber: z.number(), merged: z.boolean() }), // the decision; the merge is a human act (see `recordDecision`)
|
|
70
64
|
connections: [{ key: 'github', integrationType: 'github', required: true }], // the GitHub connection this workflow acts through, declared ONCE by key โ resolved on the owner agent at run time (an agent-scoped GitHub connection first, then org-scoped; LUA-623), so the same definition runs on any agent: no frozen connection id in source, no per-agent build
|
|
71
65
|
workspace: {
|
|
72
66
|
kind: 'git',
|
|
@@ -117,7 +111,19 @@ export const ticketToPr = createWorkflow({
|
|
|
117
111
|
'The test suite failed:\n${stepResults.runTests.summary}\nFix the code (not the tests unless they are wrong) and re-run `npm test` until green.'
|
|
118
112
|
),
|
|
119
113
|
})
|
|
120
|
-
.
|
|
114
|
+
.agentStep('openPr', {
|
|
115
|
+
// a CODING TURN with `gh` (05 ยง5.17.6): opens the PR from the run branch and reports it as typed output
|
|
116
|
+
agentId: 'swe-implementer',
|
|
117
|
+
tier: 'job',
|
|
118
|
+
workspace: { mount: 'ro' },
|
|
119
|
+
timeoutSeconds: 600,
|
|
120
|
+
jobResources: 'small',
|
|
121
|
+
prompt: template(
|
|
122
|
+
'Open a pull request from the current branch against ${initData.baseRef} for ticket ${initData.ticketId}, titled "${initData.title}". Put the marker the harness gives you for this run in the PR body (the GitHub webhook routes review signals by it). Reply with the PR number and URL.'
|
|
123
|
+
),
|
|
124
|
+
toolScope: { jobTools: ['gh', 'git', 'read', 'glob', 'grep'] }, // `gh` here is the harness's tool through the sidecar's gh proxy (create / edit / comment on this repo only)
|
|
125
|
+
outputSchema: z.object({ prNumber: z.number(), url: z.string() }),
|
|
126
|
+
})
|
|
121
127
|
.dowhile('reviewRound', eq(step('reviewRound').path('state'), lit('changes_requested')), { maxIterations: 8 }) // โค 8 review rounds (the SWE_REVIEW_MAX_ROUNDS lesson)
|
|
122
128
|
.workflow(
|
|
123
129
|
'reviewRound',
|
|
@@ -133,5 +139,6 @@ export const ticketToPr = createWorkflow({
|
|
|
133
139
|
timeoutHours: 72,
|
|
134
140
|
onTimeout: 'deny',
|
|
135
141
|
})
|
|
136
|
-
.then(
|
|
142
|
+
.map({ approved: fromStep('mergeGate', 'approved'), prNumber: fromStep('openPr', 'prNumber') }, { id: 'mergeInput' }) // a `.then(step)` receives the PREVIOUS node's output โ the approval's, which has `approved` but not the PR number โ so the fields `recordDecision` declares are projected explicitly; without this map the row fails `input_schema_invalid` before dispatch (LUA-679, found by the LUA-669 builder)
|
|
143
|
+
.then(recordDecision)
|
|
137
144
|
.commit();
|