lua-cli 3.32.2 → 3.32.4

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.
@@ -1,22 +1,35 @@
1
1
  // Maker-checker refund with escalation, business-hours deadlines and a recoverable external step.
2
- // Verbatim from workflows-spec 03 §3.2 (e) (WF-215 / WF-223 — the spec is normative).
2
+ // From workflows-spec 03 §3.2 (e) (WF-215 / WF-223 — the spec is normative). LUA-751: the step after the approval reads the
3
+ // approval node's REAL output — `WorkflowApprovalOutput` (@lua/shared-types): `{ approved, decision, text, note?, editedPayload?,
4
+ // editRevision?, decidedBy?, timedOut?, escalations? }`, the ONE shape the engine emits and `lua workflows run --approve` emits
5
+ // offline. The listing's `input: <preceding output>` pass-through (04 §4.2.3) was never implemented — the original payload is not
6
+ // echoed back; the step reads the request back with `getStepResult('refundRequest')` (docs/workflows/approvals.md). The `refundRequest`
7
+ // map ahead of the approval makes the payload the approver sees and edits exactly what `editedPayloadSchema` describes
8
+ // (`{ ticketId, amount }`, not the whole run input): an edit is judged on the pointers that changed against that payload.
3
9
  import { z } from 'zod';
4
- import { createStep, createWorkflow, template, Integrations } from 'lua-cli';
10
+ import { createStep, createWorkflow, fromInit, template, Integrations } from 'lua-cli';
11
+
12
+ const refundRequest = z.object({ ticketId: z.string(), amount: z.number() });
5
13
 
6
14
  const postRefund = createStep({
7
15
  id: 'postRefund',
8
- inputSchema: z.object({
9
- approved: z.boolean(),
10
- editedPayload: z.object({ ticketId: z.string(), amount: z.number() }).optional(),
11
- input: z.object({ ticketId: z.string(), amount: z.number() }),
12
- }), // the approval node's output shape (04 §4.2.3)
16
+ // The approval node's output (`WorkflowApprovalOutput`): the members this step reads; `.passthrough()` admits the rest of the
17
+ // envelope (`decision`, `text`, `note`, `editRevision`, `decidedBy`, `timedOut`, `escalations`) — the engine validates a step's
18
+ // input with `additionalProperties:false` otherwise.
19
+ inputSchema: z
20
+ .object({
21
+ approved: z.boolean(),
22
+ editedPayload: refundRequest.optional(), // only when the approver edited the amount (`editablePaths` below)
23
+ })
24
+ .passthrough(),
13
25
  outputSchema: z.object({ refundId: z.string().nullable() }),
14
26
  sideEffects: 'external', // a platform-fault reclaim PARKS this step (never auto-retried); the run gates `exception` (§06 §6.3.5)
15
27
  onError: 'park', // a final customer-fault failure parks too — the operator retries / skips / supplies the refundId / fails it
16
28
  requiredConnections: ['stripe'], // the declared key below — resolved on the owner agent at run time (LUA-623); no connection of that type ⇒ failed{credentials_unresolved}, unmountable ⇒ failed{credentials_revoked, reason:'required_connection_unmountable'} → parked via onError:'park' (§11 §11.5.5)
17
- async execute({ inputData, occurrenceId }) {
18
- if (!inputData.approved) return { refundId: null };
19
- const r = inputData.editedPayload ?? inputData.input;
29
+ async execute({ inputData, getStepResult, occurrenceId }) {
30
+ if (!inputData.approved) return { refundId: null }; // denied, or timed out under the chain's terminal 'deny' — data, never an exception (§06 §6.4.11)
31
+ // The edited details when the approver lowered the amount, else the request the approver saw (the `refundRequest` map). The output never echoes it.
32
+ const r = inputData.editedPayload ?? getStepResult<z.infer<typeof refundRequest>>('refundRequest');
20
33
  // `occurrenceId` is `${lineageId}:${stepId}`: the same key on retry-step, on resume and on a REPAIR RUN — the refund collapses to one (§06 §6.3.3 (e)).
21
34
  // A key that must hold across INDEPENDENT runs (two runs for the same ticket) is the caller's: `refund:${r.ticketId}` (§06 §6.3.3 (f)).
22
35
  const res = await Integrations.passthrough('stripe', {
@@ -36,6 +49,7 @@ export const refund = createWorkflow({
36
49
  budget: { maxDurationSeconds: 14 * 24 * 3600 }, // long enough for two business-hours escalation hops (the compiler warns `deadline-clamped` otherwise)
37
50
  connections: [{ key: 'stripe', integrationType: 'stripe', required: true }], // the Stripe connection the refund step acts through, declared once by key — never a frozen connection id (LUA-623)
38
51
  })
52
+ .map({ ticketId: fromInit('ticketId'), amount: fromInit('amount') }, { id: 'refundRequest' }) // the payload the approver reviews — and may edit — is the refund request, not the whole run input
39
53
  .approval('approveRefund', {
40
54
  title: 'Refund request',
41
55
  details: template('Refund ${initData.amount} for ticket ${initData.ticketId}'),
@@ -48,10 +62,10 @@ export const refund = createWorkflow({
48
62
  { escalateTo: { role: 'finance-controller' }, timeoutHours: 16 }, // …hop 1: finance, 16 business hours, then…
49
63
  { escalateTo: 'org-admins', timeoutHours: 24 }, // …hop 2: org admins, then…
50
64
  'deny',
51
- ], // …the node completes {approved:false, timedOut:true, escalations:2} — branchable data (§06 §6.4.11)
65
+ ], // …the node completes {approved:false, decision:'timed_out', text:'timed_out', timedOut:true, escalations:2} — branchable data (§06 §6.4.11)
52
66
  editable: true,
53
67
  editablePaths: ['amount'], // the approver may lower the amount; the approve call echoes the fingerprint of the revision it saw (§06 §6.4.9)
54
68
  editedPayloadSchema: z.object({ ticketId: z.string(), amount: z.number().positive().max(500) }),
55
69
  })
56
- .then(postRefund)
70
+ .then(postRefund) // its input IS the approval's output (the default previous-output rule, §05 §5.5.1.1)
57
71
  .commit();
@@ -1,5 +1,6 @@
1
1
  // Sequential + parallel research, typed end to end.
2
- // Verbatim from workflows-spec 03 §3.2 (a) (WF-215 / WF-223 — the spec is normative).
2
+ // Verbatim from workflows-spec 03 §3.2 (a) (WF-215 / WF-223 — the spec is normative), with the workflow EXPORTED
3
+ // like every other example — a non-exported `const` cannot be registered on a LuaAgent without editing the file.
3
4
  import { z } from 'zod';
4
5
  import { createStep, createWorkflow, stepOf, fromStep, template, gt, lit } from 'lua-cli';
5
6
 
@@ -19,24 +20,36 @@ const fetchSources = createStep({
19
20
 
20
21
  const angle = z.object({ summary: z.string(), confidence: z.number() });
21
22
 
22
- const wf = createWorkflow({
23
+ export const researchBrief = createWorkflow({
23
24
  name: 'research-brief',
24
25
  description: 'Fetch sources, summarise from two angles in parallel, merge — or fall back when confidence is low.',
25
26
  inputSchema: z.object({ topic: z.string() }),
26
27
  outputSchema: z.object({ brief: z.string() }),
27
28
  budget: { maxCredits: 40 },
28
29
  })
29
- .then(fetchSources) // entry 1: step
30
- .parallel(['techAngle', 'marketAngle']) // entry 2: parallel — 2 arms, both declared BELOW (§3.2.0: "declare here, inside me")
31
- .agentStep('techAngle', { agentId: 'analyst', prompt: template('Summarise the technical angle of ${initData.topic} using ${stepResults.fetchSources.urls}'),
32
- outputSchema: angle }) // no top-level entry — placed by the parallel above
33
- .agentStep('marketAngle', { agentId: 'analyst', prompt: template('Summarise the market angle of ${initData.topic} using ${stepResults.fetchSources.urls}'),
34
- outputSchema: angle }) // same
35
- .switch( // entry 3: conditional{exclusive}
36
- [[gt(stepOf<typeof angle>('techAngle').path('confidence'), lit(0.6)), 'merge']], // TypedRef<number> vs Literal<number> — `lit('a')` would not compile
37
- 'lowConfidence')
38
- .map({ brief: fromStep('techAngle', 'summary'), market: fromStep('marketAngle', 'summary') }, { id: 'merge' }) // placed INSIDE the switch arm (string ref above) — one entry, not two
39
- .agentStep('lowConfidence', { agentId: 'analyst', // placed as the switch's `otherwise`
40
- prompt: template('Confidence was low. Write a cautious brief on ${initData.topic} from ${stepResults.techAngle.summary} and ${stepResults.marketAngle.summary}'),
41
- outputSchema: z.object({ brief: z.string() }) })
42
- .commit(); // the conditional is the last entry: the taken arm's output IS the run output
30
+ .then(fetchSources) // entry 1: step
31
+ .parallel(['techAngle', 'marketAngle']) // entry 2: parallel — 2 arms, both declared BELOW (§3.2.0: "declare here, inside me")
32
+ .agentStep('techAngle', {
33
+ agentId: 'analyst',
34
+ prompt: template('Summarise the technical angle of ${initData.topic} using ${stepResults.fetchSources.urls}'),
35
+ outputSchema: angle,
36
+ }) // no top-level entry placed by the parallel above
37
+ .agentStep('marketAngle', {
38
+ agentId: 'analyst',
39
+ prompt: template('Summarise the market angle of ${initData.topic} using ${stepResults.fetchSources.urls}'),
40
+ outputSchema: angle,
41
+ }) // same
42
+ .switch(
43
+ // entry 3: conditional{exclusive}
44
+ [[gt(stepOf<typeof angle>('techAngle').path('confidence'), lit(0.6)), 'merge']], // TypedRef<number> vs Literal<number> — `lit('a')` would not compile
45
+ 'lowConfidence'
46
+ )
47
+ .map({ brief: fromStep('techAngle', 'summary'), market: fromStep('marketAngle', 'summary') }, { id: 'merge' }) // placed INSIDE the switch arm (string ref above) — one entry, not two
48
+ .agentStep('lowConfidence', {
49
+ agentId: 'analyst', // placed as the switch's `otherwise`
50
+ prompt: template(
51
+ 'Confidence was low. Write a cautious brief on ${initData.topic} from ${stepResults.techAngle.summary} and ${stepResults.marketAngle.summary}'
52
+ ),
53
+ outputSchema: z.object({ brief: z.string() }),
54
+ })
55
+ .commit(); // the conditional is the last entry: the taken arm's output IS the run output
@@ -20,7 +20,7 @@
20
20
  "inquirer": "^12.9.6",
21
21
  "stripe": "^17.5.0",
22
22
  "js-yaml": "^4.1.0",
23
- "lua-cli": "^3.32.2",
23
+ "lua-cli": "^3.32.4",
24
24
  "openai": "^5.23.0",
25
25
  "uuid": "^13.0.0",
26
26
  "zod": "^3.24.1"