lua-cli 3.31.0 → 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.
Files changed (33) hide show
  1. package/dist/api-exports.d.ts +416 -103
  2. package/dist/api-exports.js +1992 -299
  3. package/dist/api-exports.js.map +1 -1
  4. package/dist/index.js +5262 -1914
  5. package/dist/index.js.map +1 -1
  6. package/dist/voice/test/index.d.ts +54 -54
  7. package/dist/workflow-builder.d.ts +257 -44
  8. package/dist/workflow-builder.js +1382 -265
  9. package/dist/workflow-builder.js.map +1 -1
  10. package/docs/README.md +2 -2
  11. package/docs/api/LuaWorkflow.md +44 -28
  12. package/docs/api/Workflows.md +12 -1
  13. package/docs/workflows/approvals.md +14 -1
  14. package/docs/workflows/connections-in-coding-turns.md +1 -0
  15. package/docs/workflows/correlation-keys.md +1 -0
  16. package/docs/workflows/git-credentials.md +22 -1
  17. package/docs/workflows/goals.md +46 -0
  18. package/docs/workflows/limits.md +6 -0
  19. package/docs/workflows/recovery.md +6 -2
  20. package/docs/workflows/replay-local.md +10 -10
  21. package/docs/workflows/schedules.md +15 -0
  22. package/docs/workflows/script-form.md +20 -10
  23. package/docs/workflows/testing-offline.md +25 -20
  24. package/docs/workflows/workspaces-and-long-steps.md +38 -2
  25. package/package.json +2 -2
  26. package/template/examples/workflows/CLAUDE.md +16 -13
  27. package/template/examples/workflows/pr-review-round.ts +61 -20
  28. package/template/examples/workflows/provision-tenant.ts +25 -8
  29. package/template/examples/workflows/refund-approval.ts +30 -17
  30. package/template/examples/workflows/support-triage.ts +59 -22
  31. package/template/examples/workflows/ticket-to-pr.ts +125 -46
  32. package/template/examples/workflows/vendor-invoices.ts +69 -16
  33. package/template/package.json +1 -1
@@ -4,9 +4,6 @@ var __name = (target, value3) => __defProp(target, "name", { value: value3, conf
4
4
  // src/types/workflow.ts
5
5
  import { zodToJsonSchema } from "zod-to-json-schema";
6
6
 
7
- // ../workflow-graph/dist/index.mjs
8
- import { createHash } from "crypto";
9
-
10
7
  // ../shared-types/dist/index.mjs
11
8
  import { z } from "zod";
12
9
  import { z as z2 } from "zod";
@@ -659,6 +656,12 @@ function isImplicitModelSelectionSource(source) {
659
656
  }
660
657
  __name(isImplicitModelSelectionSource, "isImplicitModelSelectionSource");
661
658
  __name2(isImplicitModelSelectionSource, "isImplicitModelSelectionSource");
659
+ var PLATFORM_FALLBACK_MODEL_SOURCE = "platform-fallback";
660
+ function isPlatformFallbackModelSource(source) {
661
+ return source === PLATFORM_FALLBACK_MODEL_SOURCE;
662
+ }
663
+ __name(isPlatformFallbackModelSource, "isPlatformFallbackModelSource");
664
+ __name2(isPlatformFallbackModelSource, "isPlatformFallbackModelSource");
662
665
  function resolveRequireToolApproval(rules) {
663
666
  const raw = rules?.requireToolApproval ?? rules?.requireApproval;
664
667
  if (raw === void 0 || raw === null) return void 0;
@@ -1348,7 +1351,6 @@ function luaClientMetricLabels(client) {
1348
1351
  const canonical2 = parseLuaClientHeader(serializeLuaClientHeader(client));
1349
1352
  return {
1350
1353
  client_family: canonical2?.app ?? "unknown",
1351
- client_version: canonical2?.version ?? "unknown",
1352
1354
  client_attribution: canonical2?.attribution ?? "unknown"
1353
1355
  };
1354
1356
  }
@@ -1490,6 +1492,87 @@ function hasCapability(profiles, profileId, required) {
1490
1492
  }
1491
1493
  __name(hasCapability, "hasCapability");
1492
1494
  __name2(hasCapability, "hasCapability");
1495
+ function workflowJobId(id) {
1496
+ switch (id.kind) {
1497
+ case "start":
1498
+ return `wft_${id.runId}_0`;
1499
+ case "start-redrive":
1500
+ return `wft_${id.runId}_start_r${id.now}`;
1501
+ case "tick":
1502
+ return `wft_${id.runId}_${id.seq}`;
1503
+ case "timer":
1504
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}`;
1505
+ case "timer-remainder":
1506
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_r${id.now}`;
1507
+ case "timer-deferral":
1508
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_d${id.now}`;
1509
+ case "timer-sibling":
1510
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_w${id.now}`;
1511
+ case "retry":
1512
+ return `wft_${id.runId}_retry_${id.stepId}_${id.attempt}`;
1513
+ case "respawn":
1514
+ return `wft_${id.runId}_respawn_${id.stepId}_${id.attempt}_s${id.segment}_r${id.now}`;
1515
+ case "redrive":
1516
+ return `wft_${id.runId}_redrive_${id.stepId}_${id.attempt}_r${id.now}`;
1517
+ case "redispatch":
1518
+ return `wft_${id.runId}_redispatch_${id.stepId}_${id.attempt}_x${id.executionId ?? "none"}_r${id.now}`;
1519
+ case "expiry":
1520
+ return `wft_${id.runId}_expire_${id.stepId}_${id.attempt}_t${id.suspendedAt}${id.hop > 0 ? `_h${id.hop}` : ""}`;
1521
+ case "step-terminal":
1522
+ return `wft_${id.runId}_term_${id.stepId}_${id.attempt}_r${id.now}`;
1523
+ case "handback":
1524
+ return `wft_${id.runId}_handback_${id.stepId}_${id.attempt}_${id.now}`;
1525
+ case "foreach-rate":
1526
+ return `wft_${id.runId}_fe_${id.stepId}_${id.attempt}_${id.bucket}`;
1527
+ case "budget-expiry":
1528
+ return `wft_${id.runId}_expiry_budget_r${id.now}`;
1529
+ case "budget-resume":
1530
+ return `wft_${id.runId}_resume_budget_${id.now}`;
1531
+ case "deadline":
1532
+ return `wft_${id.runId}_deadline_${id.deadlineAt}`;
1533
+ case "deadline-remainder":
1534
+ return `wft_${id.runId}_deadline_${id.deadlineAt}_r${id.now}`;
1535
+ case "readmit":
1536
+ return `wft_${id.runId}_readmit_${id.now}`;
1537
+ case "cancel":
1538
+ return `wft_${id.runId}_cancel`;
1539
+ case "reconcile":
1540
+ return `wft_${id.runId}_reconcile_${id.now}`;
1541
+ case "replay":
1542
+ return `wft_${id.runId}_replay_g${id.leaseGeneration}`;
1543
+ case "migration":
1544
+ return `wfm_${id.migrationId}`;
1545
+ default:
1546
+ return assertNever(id);
1547
+ }
1548
+ }
1549
+ __name(workflowJobId, "workflowJobId");
1550
+ __name2(workflowJobId, "workflowJobId");
1551
+ function assertNever(id) {
1552
+ throw new Error(`workflowJobId: unknown wake kind ${JSON.stringify(id)}`);
1553
+ }
1554
+ __name(assertNever, "assertNever");
1555
+ __name2(assertNever, "assertNever");
1556
+ function agentStepJobId(msg) {
1557
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_x${msg.executionId}`;
1558
+ }
1559
+ __name(agentStepJobId, "agentStepJobId");
1560
+ __name2(agentStepJobId, "agentStepJobId");
1561
+ function heavyStepJobId(msg) {
1562
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_x${msg.executionId}`;
1563
+ }
1564
+ __name(heavyStepJobId, "heavyStepJobId");
1565
+ __name2(heavyStepJobId, "heavyStepJobId");
1566
+ function jobSpawnJobId(msg) {
1567
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_s${msg.segment}_${msg.executionId}`;
1568
+ }
1569
+ __name(jobSpawnJobId, "jobSpawnJobId");
1570
+ __name2(jobSpawnJobId, "jobSpawnJobId");
1571
+ function exportAutoJobId(exportId, suffix) {
1572
+ return suffix ? `${exportId}:${suffix}` : exportId;
1573
+ }
1574
+ __name(exportAutoJobId, "exportAutoJobId");
1575
+ __name2(exportAutoJobId, "exportAutoJobId");
1493
1576
  var SYSTEM_USER_PREFIX = "system:";
1494
1577
  function isSystemRun(identity) {
1495
1578
  return identity.userId.startsWith(SYSTEM_USER_PREFIX);
@@ -1528,6 +1611,27 @@ var WORKFLOW_RUN_STATUSES = [
1528
1611
  ...WORKFLOW_RUN_IDLE,
1529
1612
  ...WORKFLOW_RUN_TERMINAL
1530
1613
  ];
1614
+ var WORKFLOW_STEP_STATUSES = [
1615
+ "pending",
1616
+ "ready",
1617
+ "waiting",
1618
+ "dispatched",
1619
+ "claimed",
1620
+ "running",
1621
+ "suspended",
1622
+ "cancellation_requested",
1623
+ "completed",
1624
+ "failed",
1625
+ "skipped",
1626
+ "cancelled",
1627
+ "timeout",
1628
+ "reaped"
1629
+ ];
1630
+ var WORKFLOW_STEP_IN_FLIGHT = [
1631
+ "claimed",
1632
+ "running",
1633
+ "cancellation_requested"
1634
+ ];
1531
1635
  var ARCHIVE_WINDOW_MARGIN_DAYS = 7;
1532
1636
  function shouldSkipArchive(run, manifestSha256, sinkSha256) {
1533
1637
  if (!run.completedAt || !run.exportedAt || run.exportedAt < run.completedAt) return false;
@@ -1588,6 +1692,7 @@ function workflowOrgSlotKey(orgId, kind) {
1588
1692
  }
1589
1693
  __name(workflowOrgSlotKey, "workflowOrgSlotKey");
1590
1694
  __name2(workflowOrgSlotKey, "workflowOrgSlotKey");
1695
+ var WORKFLOW_CONNECTION_KEY_RE = /^[a-z][a-z0-9_-]{0,63}$/;
1591
1696
  function scheduledTimeKey(scheduledTime) {
1592
1697
  return scheduledTime.replace(/[^0-9TZ]/g, "");
1593
1698
  }
@@ -1599,6 +1704,310 @@ function scheduledWorkflowRunIdForTime(jobId, scheduledTime) {
1599
1704
  __name(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1600
1705
  __name2(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1601
1706
  var WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES = 64 * 1024;
1707
+ var WORKFLOW_RESOLVE_OUTPUT_MAX_BYTES = 256 * 1024;
1708
+ var WORKFLOW_RETRY_BACKOFFS = [
1709
+ "fixed",
1710
+ "exponential"
1711
+ ];
1712
+ var WORKFLOW_JOB_RESOURCES = [
1713
+ "small",
1714
+ "medium",
1715
+ "large"
1716
+ ];
1717
+ var WORKFLOW_SIDE_EFFECTS = [
1718
+ "none",
1719
+ "external"
1720
+ ];
1721
+ var WORKFLOW_JOB_RANGES = Object.freeze({
1722
+ /** Coding-turn cap (`claude -p --max-turns`); absent ⇒ the cluster default (LUA_WF_JOB_MAX_TURNS). */
1723
+ maxTurns: Object.freeze({
1724
+ min: 1,
1725
+ max: 500
1726
+ }),
1727
+ /** Harness messages per attempt (one per content block); absent ⇒ the cluster default (400). */
1728
+ maxMessages: Object.freeze({
1729
+ min: 1,
1730
+ max: 5e3
1731
+ }),
1732
+ /** Input-side tokens per attempt (prompt + cache); absent ⇒ the cluster default (4M — LUA-708, was 30M). */
1733
+ maxInputTokens: Object.freeze({
1734
+ min: 1e6,
1735
+ max: 5e8
1736
+ })
1737
+ });
1738
+ var WORKFLOW_JOB_RANGE_MEMBERS = Object.keys(WORKFLOW_JOB_RANGES);
1739
+ function isWithinWorkflowJobRange(member, value3) {
1740
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
1741
+ return typeof value3 === "number" && Number.isInteger(value3) && value3 >= min && value3 <= max;
1742
+ }
1743
+ __name(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1744
+ __name2(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1745
+ function workflowJobRangeMessage(member) {
1746
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
1747
+ return `\`${member}\` must be an integer ${min}..${max}`;
1748
+ }
1749
+ __name(workflowJobRangeMessage, "workflowJobRangeMessage");
1750
+ __name2(workflowJobRangeMessage, "workflowJobRangeMessage");
1751
+ var WORKFLOW_SINGLE_STEP_TYPES = [
1752
+ "agent",
1753
+ "tool",
1754
+ "workflow",
1755
+ "step"
1756
+ ];
1757
+ var WORKFLOW_HITL_ENTRY_TYPES = [
1758
+ "approval",
1759
+ "waitForSignal"
1760
+ ];
1761
+ var WORKFLOW_ARM_ENTRY_TYPES = [
1762
+ ...WORKFLOW_SINGLE_STEP_TYPES,
1763
+ ...WORKFLOW_HITL_ENTRY_TYPES
1764
+ ];
1765
+ var WORKFLOW_HITL_ARM_CONTAINERS = [
1766
+ "parallel",
1767
+ "conditional",
1768
+ "foreach"
1769
+ ];
1770
+ function isWorkflowSingleStepType(type) {
1771
+ return typeof type === "string" && WORKFLOW_SINGLE_STEP_TYPES.includes(type);
1772
+ }
1773
+ __name(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1774
+ __name2(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1775
+ function isWorkflowHitlEntryType(type) {
1776
+ return typeof type === "string" && WORKFLOW_HITL_ENTRY_TYPES.includes(type);
1777
+ }
1778
+ __name(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1779
+ __name2(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1780
+ function isWorkflowArmEntryType(type) {
1781
+ return typeof type === "string" && WORKFLOW_ARM_ENTRY_TYPES.includes(type);
1782
+ }
1783
+ __name(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1784
+ __name2(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1785
+ function workflowContainerRunsHitlArm(container) {
1786
+ return typeof container === "string" && WORKFLOW_HITL_ARM_CONTAINERS.includes(container);
1787
+ }
1788
+ __name(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1789
+ __name2(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1790
+ function workflowHitlArmUnsupportedMessage(type, id, container) {
1791
+ const legal = WORKFLOW_HITL_ARM_CONTAINERS.map((c) => `\`${c}\``).join(" / ");
1792
+ return `the engine does not run \`${type}\` inside a \`${container}\` yet (node "${id}") \u2014 place it at the top level, as a ${legal} arm, or inside a child \`workflow\``;
1793
+ }
1794
+ __name(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1795
+ __name2(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1796
+ function workflowHitlArmShapeMessage(type, id, shape) {
1797
+ return shape === "mapped-arm" ? `\`${type}\` arm "${id}" takes the previous output as its payload \u2014 it cannot head a [mapping, step] chain; map before the container instead` : `a chunked foreach hands each child a slice of items, not one \u2014 \`${type}\` body "${id}" takes one item; drop \`chunk\``;
1798
+ }
1799
+ __name(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1800
+ __name2(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1801
+ var WORKFLOW_GRAPH_ENTRY_STEP_KINDS = Object.freeze({
1802
+ agent: "agent",
1803
+ tool: "tool",
1804
+ workflow: "subrun",
1805
+ step: "code",
1806
+ mapping: "map",
1807
+ sleep: "sleep",
1808
+ sleepUntil: "sleepUntil",
1809
+ parallel: null,
1810
+ conditional: "branch",
1811
+ foreach: "foreach",
1812
+ loop: "loop",
1813
+ approval: "approval",
1814
+ waitForSignal: "signal"
1815
+ });
1816
+ var WORKFLOW_ARM_ENTRY_STEP_KINDS = Object.freeze(Object.fromEntries(WORKFLOW_ARM_ENTRY_TYPES.map((t) => [
1817
+ t,
1818
+ WORKFLOW_GRAPH_ENTRY_STEP_KINDS[t]
1819
+ ])));
1820
+ var WORKFLOW_BUDGET_MAX_DURATION_SECONDS = Object.freeze({
1821
+ min: 60,
1822
+ max: 2592e3
1823
+ });
1824
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
1825
+ var PROVIDER_MESSAGE_MAX_CHARS = 300;
1826
+ var ERROR_MESSAGE_MAX_CHARS = 2e3;
1827
+ var SECRET_LITERAL_PATTERNS = [
1828
+ {
1829
+ re: /\b(github_pat_)[A-Za-z0-9_]{16,}/g
1830
+ },
1831
+ {
1832
+ re: /\b(gh[pousr]_)[A-Za-z0-9]{16,}/g
1833
+ },
1834
+ {
1835
+ re: /\b(glpat-)[A-Za-z0-9_-]{16,}/g
1836
+ },
1837
+ // LUA-696 review (3): the npm granular / classic token (`npm_` + 36 alphanumerics).
1838
+ {
1839
+ re: /\b(npm_)[A-Za-z0-9]{36}\b/g
1840
+ },
1841
+ {
1842
+ re: /\b(sk-ant-)[A-Za-z0-9_-]{16,}/g
1843
+ },
1844
+ {
1845
+ re: /\b(sk-)(?!ant-)[A-Za-z0-9_-]{20,}/g
1846
+ },
1847
+ {
1848
+ re: /\b(AKIA)[A-Z0-9]{16}\b/g
1849
+ },
1850
+ {
1851
+ re: /\b(xox[abprs]-)[A-Za-z0-9-]{10,}/g
1852
+ },
1853
+ {
1854
+ re: /\b(AIza)[0-9A-Za-z_-]{35}/g
1855
+ },
1856
+ {
1857
+ re: /\b(ya29\.)[A-Za-z0-9_-]{20,}/g
1858
+ },
1859
+ {
1860
+ re: /\b(eyJ)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
1861
+ },
1862
+ {
1863
+ re: /(-----BEGIN [A-Z ]*PRIVATE KEY-----)[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
1864
+ },
1865
+ // `https://x-access-token:<token>@github.com/…` (the git credential shape the pod scrubbed already)
1866
+ {
1867
+ re: /\b(x-access-token:)[^@\s]+(@)/g,
1868
+ suffix: true
1869
+ }
1870
+ ];
1871
+ var SECRET_NAME = "(?:authorization|proxy-authorization|x-api-key|x-wf-[a-z-]*key|x-internal-auth|api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|secret|password|passwd|pwd|passphrase|token|credentials?)";
1872
+ var SECRET_PAIR_PATTERNS = [
1873
+ // `Authorization: Bearer x`, `x-api-key: x`, `AWS_SECRET_ACCESS_KEY=x`, `SVC_JOB_KEY=x`, `"apiKey": "x"`,
1874
+ // `FOO_TOKEN=x`, `secretKey=x`, `password=x`. Groups: the char before the name, the name, the separator
1875
+ // (with its quotes), the scheme word — all kept; the value goes. A value a literal rule already replaced
1876
+ // (`x-access-token:[REDACTED]@host`) is left alone so the host after it survives.
1877
+ // LUA-696: the second lookahead keeps a scrubbed `Authorization: Bearer [REDACTED]` as it is — without it the
1878
+ // optional scheme group backtracks to empty and `Bearer` itself becomes the value (`[REDACTED] [REDACTED]`).
1879
+ // The scrub is applied more than once on purpose (the Job site at its copy, the outcome table at the row
1880
+ // write, lua-api on the way out), so it must be idempotent.
1881
+ // LUA-696 review (HIGH): the identifier prefix is BOUNDED (`{1,64}`) — unbounded, `[A-Za-z0-9-]+` made the
1882
+ // scan quadratic on `-`-heavy text (100 KB of `-` took 12.7 s on the lua-core event loop; a worker-tier step
1883
+ // can throw that). No identifier that names a credential is longer; the probe list is unchanged.
1884
+ {
1885
+ re: new RegExp(`(^|[^A-Za-z0-9])((?:[A-Za-z0-9-]{1,64}[_-])?${SECRET_NAME}|[A-Za-z0-9-]{1,64}_key)(["']?\\s*[:=]\\s*["']?)((?:basic\\s+|bearer\\s+|token\\s+)?)(?!\\[REDACTED\\]|(?:basic|bearer|token)\\s+\\[REDACTED\\])[^\\s"',;)}&]{4,}`, "gi")
1886
+ },
1887
+ // `?token=x`, `&key=x`, `&X-Amz-Signature=x`, `&sig=x`
1888
+ {
1889
+ re: /([?&](?:token|key|api[_-]?key|apikey|access[_-]?token|id[_-]?token|auth|sig|signature|secret|password|pwd|x-amz-signature|x-amz-credential|x-amz-security-token)=)[^&\s"'#]+/gi
1890
+ },
1891
+ // `mongodb+srv://user:pass@host`, `postgres://user:pass@host`, `https://user:pass@host`
1892
+ {
1893
+ re: /(\/\/[^\s/:@]+:)[^\s/@]+(@)/g,
1894
+ suffix: true
1895
+ },
1896
+ // `Basic <base64>` / `bearer <short token>` (the literal list needs ≥ 16 chars; any case)
1897
+ {
1898
+ re: /\b((?:Basic|Bearer)\s+)(?!\[REDACTED\])[A-Za-z0-9+/=_.-]{8,}/gi
1899
+ }
1900
+ ];
1901
+ var GROUP_COUNT = /* @__PURE__ */ new WeakMap();
1902
+ function groupCount(re) {
1903
+ let n2 = GROUP_COUNT.get(re);
1904
+ if (n2 === void 0) {
1905
+ n2 = new RegExp(`${re.source}|`, re.flags.replace("g", "")).exec("").length - 1;
1906
+ GROUP_COUNT.set(re, n2);
1907
+ }
1908
+ return n2;
1909
+ }
1910
+ __name(groupCount, "groupCount");
1911
+ __name2(groupCount, "groupCount");
1912
+ var WORKFLOW_SECRET_KEY_RE = /(^|[_\-.\s])(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(s)?)([_\-.\s]|$)|^(secret|token|password|passwd|pwd|passphrase|apikey|authorization|credentials?)$/i;
1913
+ var WORKFLOW_RESERVED_SECRET_KEYS = Object.freeze([
1914
+ "secret",
1915
+ "token",
1916
+ "password",
1917
+ "passwd",
1918
+ "pwd",
1919
+ "passphrase",
1920
+ "api_key",
1921
+ "apikey",
1922
+ "access_key",
1923
+ "secret_key",
1924
+ "private_key",
1925
+ "client_secret",
1926
+ "authorization",
1927
+ "auth_token",
1928
+ "access_token",
1929
+ "id_token",
1930
+ "refresh_token",
1931
+ "session_key",
1932
+ "credential",
1933
+ "credentials"
1934
+ ]);
1935
+ function isWorkflowSecretKey(key) {
1936
+ return WORKFLOW_SECRET_KEY_RE.test(key);
1937
+ }
1938
+ __name(isWorkflowSecretKey, "isWorkflowSecretKey");
1939
+ __name2(isWorkflowSecretKey, "isWorkflowSecretKey");
1940
+ function applyPatterns(text, patterns) {
1941
+ let out = text;
1942
+ for (const { re, suffix } of patterns) {
1943
+ re.lastIndex = 0;
1944
+ if (!re.test(out)) continue;
1945
+ re.lastIndex = 0;
1946
+ const groups = groupCount(re);
1947
+ out = out.replace(re, (...args) => {
1948
+ const kept = args.slice(1, 1 + groups).map((g) => typeof g === "string" ? g : "");
1949
+ if (suffix && kept.length > 0) {
1950
+ const tail = kept[kept.length - 1];
1951
+ return `${kept.slice(0, -1).join("")}${REDACTED_PLACEHOLDER}${tail}`;
1952
+ }
1953
+ return `${kept.join("")}${REDACTED_PLACEHOLDER}`;
1954
+ });
1955
+ }
1956
+ return out;
1957
+ }
1958
+ __name(applyPatterns, "applyPatterns");
1959
+ __name2(applyPatterns, "applyPatterns");
1960
+ function scrubSecretText(text) {
1961
+ if (typeof text !== "string" || text.length < 4) return text;
1962
+ return applyPatterns(applyPatterns(text, SECRET_LITERAL_PATTERNS), SECRET_PAIR_PATTERNS);
1963
+ }
1964
+ __name(scrubSecretText, "scrubSecretText");
1965
+ __name2(scrubSecretText, "scrubSecretText");
1966
+ var SCRUB_INPUT_MAX_CHARS = 16 * 1024;
1967
+ var SCRUB_CUT_BACKOFF_CHARS = 256;
1968
+ function boundScrubInput(text, max = SCRUB_INPUT_MAX_CHARS) {
1969
+ if (typeof text !== "string" || text.length <= max) return text;
1970
+ const window = text.slice(Math.max(0, max - SCRUB_CUT_BACKOFF_CHARS), max);
1971
+ const ws = window.search(/\s\S*$/);
1972
+ const cut = ws >= 0 ? max - window.length + ws : max;
1973
+ return `${text.slice(0, cut)}\u2026`;
1974
+ }
1975
+ __name(boundScrubInput, "boundScrubInput");
1976
+ __name2(boundScrubInput, "boundScrubInput");
1977
+ function scrubSecretLines(lines) {
1978
+ return lines.map((l) => typeof l === "string" ? scrubSecretText(boundScrubInput(l)) : l);
1979
+ }
1980
+ __name(scrubSecretLines, "scrubSecretLines");
1981
+ __name2(scrubSecretLines, "scrubSecretLines");
1982
+ function messageText(value3) {
1983
+ if (typeof value3 === "string") return value3;
1984
+ if (value3 instanceof Error) return value3.message;
1985
+ if (value3 && typeof value3 === "object") {
1986
+ const m = value3.message;
1987
+ if (typeof m === "string") return m;
1988
+ try {
1989
+ return JSON.stringify(value3);
1990
+ } catch {
1991
+ return "";
1992
+ }
1993
+ }
1994
+ return value3 === void 0 || value3 === null ? "" : String(value3);
1995
+ }
1996
+ __name(messageText, "messageText");
1997
+ __name2(messageText, "messageText");
1998
+ function scrubProviderMessage(raw, max = PROVIDER_MESSAGE_MAX_CHARS) {
1999
+ const text = boundScrubInput(messageText(raw)).replace(/\s+/g, " ").trim();
2000
+ if (!text) return void 0;
2001
+ const out = scrubSecretText(text);
2002
+ return out.length > max ? `${out.slice(0, max - 1)}\u2026` : out;
2003
+ }
2004
+ __name(scrubProviderMessage, "scrubProviderMessage");
2005
+ __name2(scrubProviderMessage, "scrubProviderMessage");
2006
+ function scrubStepErrorMessage(raw) {
2007
+ return scrubProviderMessage(raw, ERROR_MESSAGE_MAX_CHARS);
2008
+ }
2009
+ __name(scrubStepErrorMessage, "scrubStepErrorMessage");
2010
+ __name2(scrubStepErrorMessage, "scrubStepErrorMessage");
1602
2011
  var WORKFLOW_AUDIT_EVENTS = [
1603
2012
  // --- definitions / versions / templates (13, 11 §11.11.5) ---
1604
2013
  "workflow.published",
@@ -1810,11 +2219,42 @@ __name(renderTargetsBlock, "renderTargetsBlock");
1810
2219
  __name2(renderTargetsBlock, "renderTargetsBlock");
1811
2220
 
1812
2221
  // ../workflow-graph/dist/index.mjs
2222
+ import { createHash } from "crypto";
1813
2223
  import { z as z4 } from "zod";
2224
+ import { z as z22 } from "zod";
1814
2225
  import { createHash as createHash2 } from "crypto";
1815
2226
  var __defProp3 = Object.defineProperty;
1816
2227
  var __name3 = /* @__PURE__ */ __name((target, value22) => __defProp3(target, "name", { value: value22, configurable: true }), "__name");
2228
+ var SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
2229
+ var JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
1817
2230
  var WORKFLOW_ARM_SUBRUN_ID = "$arm";
2231
+ var WORKSPACE_TEMPLATE_EXPR_RE = /^\$\{\s*(?:initData|input)\.([^}]+?)\s*\}$/;
2232
+ function workspaceTemplatePath(template22) {
2233
+ const key = template22.trim();
2234
+ const expr = WORKSPACE_TEMPLATE_EXPR_RE.exec(key);
2235
+ if (expr) return expr[1].split(".");
2236
+ if (key.includes("${")) return void 0;
2237
+ return key.replace(/^(?:input|initData)\./, "").split(".");
2238
+ }
2239
+ __name(workspaceTemplatePath, "workspaceTemplatePath");
2240
+ __name3(workspaceTemplatePath, "workspaceTemplatePath");
2241
+ function retryBackoffs() {
2242
+ if (!Array.isArray(WORKFLOW_RETRY_BACKOFFS)) {
2243
+ throw new Error("@lua/shared-types.WORKFLOW_RETRY_BACKOFFS is not a tuple \u2014 a jest.mock('@lua/shared-types') must spread jest.requireActual('@lua/shared-types')");
2244
+ }
2245
+ return WORKFLOW_RETRY_BACKOFFS;
2246
+ }
2247
+ __name(retryBackoffs, "retryBackoffs");
2248
+ __name3(retryBackoffs, "retryBackoffs");
2249
+ var SLEEP_UNTIL_REPLACEMENT = Object.freeze({
2250
+ type: "sleep",
2251
+ duration: 6e4
2252
+ });
2253
+ function sleepUntilUnsupportedMessage(id) {
2254
+ return `the engine does not execute \`sleepUntil\` yet (node "${id}") \u2014 replace it with a \`sleep\` node with a \`duration\` in ms, e.g. { type: 'sleep', id: '${id}', duration: ${SLEEP_UNTIL_REPLACEMENT.duration} }`;
2255
+ }
2256
+ __name(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
2257
+ __name3(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
1818
2258
  var WORKFLOW_CAPS_DEFAULT = Object.freeze({
1819
2259
  maxParallelArms: 16,
1820
2260
  maxForeachConcurrency: 16,
@@ -1873,8 +2313,30 @@ function fillSingle(node) {
1873
2313
  }
1874
2314
  __name(fillSingle, "fillSingle");
1875
2315
  __name3(fillSingle, "fillSingle");
2316
+ function fillHitl(node) {
2317
+ if (node.type === "approval") {
2318
+ const a = node;
2319
+ if (a.approver === void 0) a.approver = "creator";
2320
+ if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2321
+ if (a.onTimeout === void 0) a.onTimeout = "deny";
2322
+ if (a.onDeny === void 0) a.onDeny = "continue";
2323
+ if (a.excludeInitiator === void 0) a.excludeInitiator = false;
2324
+ if (a.editable === void 0) a.editable = false;
2325
+ return;
2326
+ }
2327
+ const w = node;
2328
+ if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2329
+ if (w.onTimeout === void 0) w.onTimeout = "fail";
2330
+ if (w.acceptedSources === void 0) w.acceptedSources = [
2331
+ ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
2332
+ ];
2333
+ }
2334
+ __name(fillHitl, "fillHitl");
2335
+ __name3(fillHitl, "fillHitl");
1876
2336
  function fillArm(arm) {
1877
- if (arm.type !== "mapping") fillSingle(arm);
2337
+ if (arm.type === "mapping") return;
2338
+ if (isHitlNode(arm)) fillHitl(arm);
2339
+ else fillSingle(arm);
1878
2340
  }
1879
2341
  __name(fillArm, "fillArm");
1880
2342
  __name3(fillArm, "fillArm");
@@ -1887,7 +2349,7 @@ function fillEntry(entry) {
1887
2349
  fillSingle(entry);
1888
2350
  return;
1889
2351
  case "parallel":
1890
- entry.steps.forEach(fillSingle);
2352
+ entry.steps.forEach(fillArm);
1891
2353
  return;
1892
2354
  case "conditional": {
1893
2355
  const c = entry;
@@ -1901,34 +2363,19 @@ function fillEntry(entry) {
1901
2363
  f.opts = f.opts ?? {};
1902
2364
  if (f.opts.concurrency === void 0) f.opts.concurrency = WORKFLOW_FOREACH_DEFAULT_CONCURRENCY;
1903
2365
  if (f.opts.maxItems === void 0) f.opts.maxItems = WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
1904
- fillSingle(f.step);
2366
+ fillArm(f.step);
1905
2367
  return;
1906
2368
  }
1907
2369
  case "loop": {
1908
2370
  const l = entry;
1909
2371
  if (l.maxIterations === void 0) l.maxIterations = WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
1910
- fillSingle(l.step);
2372
+ fillArm(l.step);
1911
2373
  return;
1912
2374
  }
1913
- case "approval": {
1914
- const a = entry;
1915
- if (a.approver === void 0) a.approver = "creator";
1916
- if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
1917
- if (a.onTimeout === void 0) a.onTimeout = "deny";
1918
- if (a.onDeny === void 0) a.onDeny = "continue";
1919
- if (a.excludeInitiator === void 0) a.excludeInitiator = false;
1920
- if (a.editable === void 0) a.editable = false;
1921
- return;
1922
- }
1923
- case "waitForSignal": {
1924
- const w = entry;
1925
- if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
1926
- if (w.onTimeout === void 0) w.onTimeout = "fail";
1927
- if (w.acceptedSources === void 0) w.acceptedSources = [
1928
- ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
1929
- ];
2375
+ case "approval":
2376
+ case "waitForSignal":
2377
+ fillHitl(entry);
1930
2378
  return;
1931
- }
1932
2379
  case "mapping":
1933
2380
  case "sleep":
1934
2381
  case "sleepUntil":
@@ -1944,6 +2391,17 @@ function withDefaultsFilled(g) {
1944
2391
  }
1945
2392
  __name(withDefaultsFilled, "withDefaultsFilled");
1946
2393
  __name3(withDefaultsFilled, "withDefaultsFilled");
2394
+ var CONNECTION_ID_HEX_RE = /^[0-9a-f]{24}$/;
2395
+ function isConnectionKeyShaped(value22) {
2396
+ return WORKFLOW_CONNECTION_KEY_RE.test(value22) && !CONNECTION_ID_HEX_RE.test(value22);
2397
+ }
2398
+ __name(isConnectionKeyShaped, "isConnectionKeyShaped");
2399
+ __name3(isConnectionKeyShaped, "isConnectionKeyShaped");
2400
+ function connectionKeyUndeclaredMessage(path, key) {
2401
+ return `${path} '${key}' is neither a connection id nor a declared connections[].key \u2014 declare it: connections: [{ key: '${key}', integrationType: '<catalog slug, e.g. github>' }] and it resolves on any agent`;
2402
+ }
2403
+ __name(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
2404
+ __name3(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
1947
2405
  var WORKFLOW_JOB_TOOLS = [
1948
2406
  "shell",
1949
2407
  "read",
@@ -1995,6 +2453,8 @@ var schemaIsArray = /* @__PURE__ */ __name3((schema) => {
1995
2453
  if (t === void 0) return void 0;
1996
2454
  return Array.isArray(t) ? t.includes("array") : t === "array";
1997
2455
  }, "schemaIsArray");
2456
+ var isHitlNode = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
2457
+ var isSingleStep = /* @__PURE__ */ __name3((n2) => !isHitlNode(n2), "isSingleStep");
1998
2458
  var singleId = /* @__PURE__ */ __name3((s) => s.type === "step" ? s.step.id : s.id, "singleId");
1999
2459
  var armId = /* @__PURE__ */ __name3((a) => a.type === "mapping" ? a.id : singleId(a), "armId");
2000
2460
  var TEMPLATE_STEP_REF = /\$\{\s*stepResults\.([A-Za-z0-9_\-]+)/g;
@@ -2005,14 +2465,21 @@ function templateStepRefs(text) {
2005
2465
  }
2006
2466
  __name(templateStepRefs, "templateStepRefs");
2007
2467
  __name3(templateStepRefs, "templateStepRefs");
2008
- function mapConfigStepRefs(raw) {
2009
- if (!raw) return [];
2010
- let cfg;
2468
+ function readMapConfig(raw) {
2469
+ if (!raw) return void 0;
2470
+ if (typeof raw !== "string") return raw;
2011
2471
  try {
2012
- cfg = typeof raw === "string" ? JSON.parse(raw) : raw;
2472
+ const cfg = JSON.parse(raw);
2473
+ return cfg && typeof cfg === "object" && !Array.isArray(cfg) ? cfg : void 0;
2013
2474
  } catch {
2014
- return [];
2475
+ return void 0;
2015
2476
  }
2477
+ }
2478
+ __name(readMapConfig, "readMapConfig");
2479
+ __name3(readMapConfig, "readMapConfig");
2480
+ function mapConfigStepRefs(raw) {
2481
+ const cfg = readMapConfig(raw);
2482
+ if (!cfg) return [];
2016
2483
  const ids = [];
2017
2484
  for (const d of Object.values(cfg)) {
2018
2485
  if (!d || typeof d !== "object") continue;
@@ -2087,6 +2554,43 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2087
2554
  if (envelopeWorkspace?.backend && envelopeWorkspace.backend !== "ebs" && opts.policy?.workspaceBackends && !opts.policy.workspaceBackends.includes(envelopeWorkspace.backend)) {
2088
2555
  err("workspace-backend-unavailable", `workspace.backend '${envelopeWorkspace.backend}' is not enabled (LUA_WF_WORKSPACE_BACKENDS = ${opts.policy.workspaceBackends.join(",")}) \u2014 'ebs' is the normative fallback`, "workspace.backend");
2089
2556
  }
2557
+ for (const member of [
2558
+ "repo",
2559
+ "ref"
2560
+ ]) {
2561
+ const v = envelopeWorkspace?.[member];
2562
+ const tpl = v && typeof v === "object" && typeof v.template === "string" ? v.template : void 0;
2563
+ if (tpl !== void 0 && workspaceTemplatePath(tpl) === void 0) {
2564
+ warn("workspace-template-composite", `workspace.${member} template ${JSON.stringify(tpl)} is a composite \u2014 the engine binds one whole-string \${initData.<path>} (or an input.<path> key) and every start would fail workspace_provision_failed{binding_unresolved}; put the assembled value in the run input and bind that path`, `workspace.${member}`);
2565
+ }
2566
+ }
2567
+ const declaredKeys = new Set(opts.connectionKeys ?? []);
2568
+ if (g.connections !== void 0 && !Array.isArray(g.connections)) {
2569
+ err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
2570
+ }
2571
+ (Array.isArray(g.connections) ? g.connections : []).forEach((c, i) => {
2572
+ const path = `connections.${i}`;
2573
+ const key = c?.key;
2574
+ const integrationType = c?.integrationType;
2575
+ if (typeof key !== "string" || !WORKFLOW_CONNECTION_KEY_RE.test(key)) {
2576
+ err("connection-declaration-invalid", `connections[${i}].key must match ${WORKFLOW_CONNECTION_KEY_RE}`, `${path}.key`);
2577
+ return;
2578
+ }
2579
+ if (declaredKeys.has(key)) {
2580
+ err("connection-declaration-invalid", `connections[${i}].key "${key}" is declared twice`, `${path}.key`);
2581
+ return;
2582
+ }
2583
+ if (typeof integrationType !== "string" || !integrationType.trim()) {
2584
+ err("connection-declaration-invalid", `connections[${i}] ("${key}") needs an integrationType (the catalog slug, e.g. 'github')`, `${path}.integrationType`);
2585
+ return;
2586
+ }
2587
+ declaredKeys.add(key);
2588
+ });
2589
+ const undeclaredKey = /* @__PURE__ */ __name3((ref) => typeof ref === "string" && !declaredKeys.has(ref) && isConnectionKeyShaped(ref) && opts.connectionIds?.has(ref) !== true, "undeclaredKey");
2590
+ const credentialsRef = envelopeWorkspace?.credentialsRef;
2591
+ if (undeclaredKey(credentialsRef)) {
2592
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage("workspace.credentialsRef", credentialsRef), "workspace.credentialsRef");
2593
+ }
2090
2594
  const seen = /* @__PURE__ */ new Map();
2091
2595
  let nodeCount = 0;
2092
2596
  const upstream = /* @__PURE__ */ new Set();
@@ -2098,12 +2602,27 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2098
2602
  seen.set(id, path);
2099
2603
  }
2100
2604
  }, "checkId");
2605
+ const checkPolicyEnums = /* @__PURE__ */ __name3((node, path) => {
2606
+ const id = singleId(node);
2607
+ const check = /* @__PURE__ */ __name3((member, allowed) => {
2608
+ const value22 = node[member];
2609
+ if (value22 === void 0 || typeof value22 === "string" && allowed.includes(value22)) return;
2610
+ err("invalid-envelope", `\`${member}\` must be ${allowed.map((a) => `'${a}'`).join(" | ")} (got ${JSON.stringify(value22)})`, `${path}.${member}`, id);
2611
+ }, "check");
2612
+ check("sideEffects", WORKFLOW_SIDE_EFFECTS);
2613
+ check("jobResources", WORKFLOW_JOB_RESOURCES);
2614
+ }, "checkPolicyEnums");
2101
2615
  const checkRetry = /* @__PURE__ */ __name3((node, path) => {
2102
2616
  const r = node.retry;
2103
2617
  if (!r) return;
2104
2618
  const id = singleId(node);
2105
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") {
2106
- err("backoff-invalid", `retry.backoff must be 'fixed' | 'exponential'`, `${path}.retry.backoff`, id);
2619
+ const backoffs = retryBackoffs();
2620
+ if (r.backoff !== void 0 && !backoffs.includes(r.backoff)) {
2621
+ const list = backoffs.map((b) => `'${b}'`).join(" | ");
2622
+ err("backoff-invalid", `retry.backoff must be ${list}`, `${path}.retry.backoff`, id);
2623
+ }
2624
+ if (r.backoffSeconds !== void 0 && r.backoffSeconds < 0) {
2625
+ err("backoff-invalid", "retry.backoffSeconds must be \u2265 0", `${path}.retry.backoffSeconds`, id);
2107
2626
  }
2108
2627
  if (r.maxBackoffSeconds !== void 0) {
2109
2628
  if (r.backoff !== "exponential") {
@@ -2166,10 +2685,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2166
2685
  }, "checkSpecialistRole");
2167
2686
  const checkRequiredConnections = /* @__PURE__ */ __name3((node, path) => {
2168
2687
  const required = node.requiredConnections;
2169
- if (!Array.isArray(required) || !opts.connectionIds) return;
2170
- const unknown = required.filter((c) => typeof c !== "string" || !opts.connectionIds.has(c));
2688
+ if (!Array.isArray(required)) return;
2689
+ const undeclared = required.filter(undeclaredKey);
2690
+ if (undeclared.length) {
2691
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage(`${path}.requiredConnections`, undeclared[0]) + (undeclared.length > 1 ? ` (also undeclared: ${JSON.stringify(undeclared.slice(1))})` : ""), `${path}.requiredConnections`, singleId(node));
2692
+ }
2693
+ if (!opts.connectionIds) return;
2694
+ const unknown = required.filter((c) => typeof c !== "string" || !declaredKeys.has(c) && !opts.connectionIds.has(c));
2171
2695
  if (unknown.length) {
2172
- err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are not connections the owner can mount`, `${path}.requiredConnections`, singleId(node));
2696
+ err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are neither declared connections[].key values nor connections the owner can mount`, `${path}.requiredConnections`, singleId(node));
2173
2697
  }
2174
2698
  }, "checkRequiredConnections");
2175
2699
  const checkTier = /* @__PURE__ */ __name3((node, path) => {
@@ -2247,14 +2771,20 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2247
2771
  err("container-arm-empty", "a bare mapping arm has nothing to run", `${path}.graph.1`, node.id);
2248
2772
  return;
2249
2773
  }
2774
+ const inner = body[1];
2775
+ if (isHitlNode(inner)) {
2776
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(inner.type, inner.id, "mapped-arm"), `${path}.graph.1`, inner.id);
2777
+ return;
2778
+ }
2250
2779
  upstream.add(singleId(body[1]));
2251
- checkArm(body[0], `${path}.graph.0`, depth);
2780
+ checkArm(body[0], `${path}.graph.0`, depth, "parallel");
2252
2781
  checkSingle(body[1], `${path}.graph.1`, depth);
2253
2782
  upstream.add(body[0].id);
2254
2783
  upstream.add(singleId(body[1]));
2255
2784
  return;
2256
2785
  }
2257
2786
  checkId(singleId(node), path);
2787
+ checkPolicyEnums(node, path);
2258
2788
  checkTimeout(node, path);
2259
2789
  checkTier(node, path);
2260
2790
  checkRetry(node, path);
@@ -2279,13 +2809,60 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2279
2809
  if (node.type === "workflow" && node.kind === "subrun" && depth > caps.maxNestingDepth) {
2280
2810
  err("cap-exceeded", `nesting depth ${depth} exceeds ${caps.maxNestingDepth}`, path, node.id);
2281
2811
  }
2812
+ if (node.type === "workflow" && node.workflowId !== WORKFLOW_ARM_SUBRUN_ID && typeof g.definition?.id === "string" && node.workflowId === g.definition.id) {
2813
+ err("subrun-cycle", `"${node.id}" starts "${node.workflowId}", which is this workflow itself`, path, node.id);
2814
+ }
2282
2815
  for (const ref of nodeStepRefs(node)) {
2283
2816
  if (!upstream.has(ref)) {
2284
2817
  err("template-reference-unresolved", `"${singleId(node)}" references stepResults.${ref}, which is not upstream`, path, singleId(node));
2285
2818
  }
2286
2819
  }
2287
2820
  }, "checkSingle");
2288
- const checkArm = /* @__PURE__ */ __name3((arm, path, depth) => {
2821
+ const checkHitl = /* @__PURE__ */ __name3((node, path) => {
2822
+ if (node.type === "waitForSignal") {
2823
+ const w = node;
2824
+ checkId(w.id, path);
2825
+ if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path}.signal`, w.id);
2826
+ return;
2827
+ }
2828
+ const a = node;
2829
+ checkId(a.id, path);
2830
+ if (a.approver === "creator" && a.excludeInitiator === true) {
2831
+ err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path, a.id);
2832
+ }
2833
+ if (a.fourEyes !== void 0 && a.editable !== true) {
2834
+ err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path}.fourEyes`, a.id);
2835
+ }
2836
+ if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2837
+ err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path}.editablePaths`, a.id);
2838
+ }
2839
+ for (const p of a.editablePaths ?? []) {
2840
+ if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path}.editablePaths`, a.id);
2841
+ }
2842
+ if (Array.isArray(a.onTimeout)) {
2843
+ const chain = a.onTimeout;
2844
+ const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2845
+ if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path}.onTimeout`, a.id);
2846
+ const last = chain[chain.length - 1];
2847
+ if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2848
+ err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path}.onTimeout`, a.id);
2849
+ }
2850
+ }
2851
+ if (typeof a.details === "string") {
2852
+ for (const ref of templateStepRefs(a.details)) {
2853
+ if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path, a.id);
2854
+ }
2855
+ }
2856
+ }, "checkHitl");
2857
+ const checkHitlArm = /* @__PURE__ */ __name3((node, path, container) => {
2858
+ if (!workflowContainerRunsHitlArm(container)) {
2859
+ checkId(node.id, path);
2860
+ err("node-type-unsupported-in-container", workflowHitlArmUnsupportedMessage(node.type, node.id, container), path, node.id);
2861
+ return;
2862
+ }
2863
+ checkHitl(node, path);
2864
+ }, "checkHitlArm");
2865
+ const checkArm = /* @__PURE__ */ __name3((arm, path, depth, container) => {
2289
2866
  if (arm.type === "mapping") {
2290
2867
  checkId(arm.id, path);
2291
2868
  for (const ref of nodeStepRefs(arm)) {
@@ -2293,6 +2870,10 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2293
2870
  }
2294
2871
  return;
2295
2872
  }
2873
+ if (isHitlNode(arm)) {
2874
+ checkHitlArm(arm, path, container);
2875
+ return;
2876
+ }
2296
2877
  checkSingle(arm, path, depth);
2297
2878
  }, "checkArm");
2298
2879
  graph.forEach((entry, i) => {
@@ -2321,6 +2902,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2321
2902
  case "sleepUntil": {
2322
2903
  const s = entry;
2323
2904
  checkId(s.id, path);
2905
+ err("node-type-unsupported-by-engine", sleepUntilUnsupportedMessage(s.id), path, s.id);
2324
2906
  if (s.date === void 0 === (s.dateFrom === void 0)) {
2325
2907
  err("invalid-envelope", "sleepUntil needs exactly one of `date` | `dateFrom`", path, s.id);
2326
2908
  }
@@ -2341,18 +2923,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2341
2923
  err("mapping-placement", "a bare mapping cannot be a parallel arm \u2014 chain it as [map, step]", armPath);
2342
2924
  return;
2343
2925
  }
2344
- if (arm.type === "approval" || arm.type === "waitForSignal") {
2345
- err("approval-inside-container", "approval / waitForSignal are top-level only in v1", armPath);
2346
- return;
2347
- }
2348
- checkSingle(arm, armPath, 1);
2926
+ checkArm(arm, armPath, 1, "parallel");
2349
2927
  declared.push(singleId(arm));
2350
2928
  });
2351
- const worktreeArms = p.steps.filter((a) => a.workspace?.isolation === "worktree");
2929
+ const executableArms = p.steps.filter(isSingleStep);
2930
+ const worktreeArms = executableArms.filter((a) => a.workspace?.isolation === "worktree");
2352
2931
  if (worktreeArms.length > 0 && !p.merge) err("worktree-arms-require-merge", "worktree arms need a `merge` policy", path);
2353
2932
  if (worktreeArms.length === 0 && p.merge) err("merge-requires-worktree-arms", "`merge` needs at least one worktree arm", path);
2354
2933
  if (worktreeArms.length > WORKFLOW_JOB_MAX_WORKTREE_ARMS) err("worktree-arms-exceed-cap", `at most ${WORKFLOW_JOB_MAX_WORKTREE_ARMS} worktree arms per parallel`, path);
2355
- const sharedArms = p.steps.filter((a) => {
2934
+ const sharedArms = executableArms.filter((a) => {
2356
2935
  const w = workspaceOf(a);
2357
2936
  if (!w || w === "inherit" || w.isolation === "worktree") return false;
2358
2937
  return !(envelopeWorkspace?.backend === "efs" && w.mount === "ro");
@@ -2366,14 +2945,14 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2366
2945
  err("invalid-envelope", "conditional.predicates must match conditional.steps one-to-one", path);
2367
2946
  }
2368
2947
  (c.predicates ?? []).forEach((p, j) => {
2369
- if (!isPredicate(p)) err("closure-predicate", "a conditional predicate must be a LuaPredicate object, not a function", `${path}.predicates.${j}`);
2948
+ if (!isWellFormedPredicate(p)) err("closure-predicate", "a conditional predicate must be a well-formed LuaPredicate object ({op, left/right | value | path | args | arg}), not a function or an expression string", `${path}.predicates.${j}`);
2370
2949
  });
2371
2950
  c.steps.forEach((arm, j) => {
2372
- checkArm(arm, `${path}.steps.${j}`, 1);
2951
+ checkArm(arm, `${path}.steps.${j}`, 1, "conditional");
2373
2952
  declared.push(armId(arm));
2374
2953
  });
2375
2954
  if (c.otherwise) {
2376
- checkArm(c.otherwise, `${path}.otherwise`, 1);
2955
+ checkArm(c.otherwise, `${path}.otherwise`, 1, "conditional");
2377
2956
  declared.push(armId(c.otherwise));
2378
2957
  }
2379
2958
  break;
@@ -2396,8 +2975,9 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2396
2975
  }
2397
2976
  if (o.chunk !== void 0) {
2398
2977
  const max = o.maxItems ?? WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2399
- if (!Number.isInteger(o.chunk.size) || o.chunk.size < 1 || o.chunk.size > max) {
2400
- err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, `${path}.opts.chunk.size`);
2978
+ const size = typeof o.chunk === "number" ? o.chunk : o.chunk.size;
2979
+ if (!Number.isInteger(size) || size < 1 || size > max) {
2980
+ err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, typeof o.chunk === "number" ? `${path}.opts.chunk` : `${path}.opts.chunk.size`);
2401
2981
  }
2402
2982
  }
2403
2983
  if (o.rateLimit !== void 0) {
@@ -2414,12 +2994,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2414
2994
  const bodyType = f.step.type;
2415
2995
  const prevEntry = i > 0 ? graph[i - 1] : void 0;
2416
2996
  if (bodyType !== "mapping" && prevEntry?.type === "mapping" && prevEntry.id === `${singleId(f.step)}_items`) {
2417
- let cfg;
2418
- try {
2419
- cfg = JSON.parse(prevEntry.mapConfig);
2420
- } catch {
2421
- cfg = void 0;
2422
- }
2997
+ const cfg = readMapConfig(prevEntry.mapConfig);
2423
2998
  const d = cfg?.[""];
2424
2999
  let arrayLike;
2425
3000
  if (d && "value" in d) arrayLike = Array.isArray(d.value);
@@ -2431,8 +3006,13 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2431
3006
  if (arrayLike === false) err("foreach-items-not-array", `foreach({items}) for "${singleId(f.step)}" binds a value that is not an array`, `${path}.step`, singleId(f.step));
2432
3007
  }
2433
3008
  if (bodyType === "mapping") err("container-arm-empty", "a foreach body needs a step, not a bare mapping", `${path}.step`);
2434
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
2435
- else {
3009
+ else if (isHitlNode(f.step)) {
3010
+ if (o.chunk !== void 0) {
3011
+ checkId(f.step.id, `${path}.step`);
3012
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(f.step.type, f.step.id, "chunked-foreach"), `${path}.step`, f.step.id);
3013
+ } else checkHitlArm(f.step, `${path}.step`, "foreach");
3014
+ declared.push(f.step.id);
3015
+ } else {
2436
3016
  checkSingle(f.step, `${path}.step`, o.chunk ? 2 : 1);
2437
3017
  declared.push(singleId(f.step));
2438
3018
  }
@@ -2446,55 +3026,23 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2446
3026
  if (l.intervalSeconds !== void 0 && (!Number.isInteger(l.intervalSeconds) || l.intervalSeconds < 1 || l.intervalSeconds > caps.maxLoopIntervalSeconds)) {
2447
3027
  err("loop-interval-out-of-range", `loop.intervalSeconds must be an integer in 1..${caps.maxLoopIntervalSeconds}`, `${path}.intervalSeconds`);
2448
3028
  }
2449
- if (!isPredicate(l.predicate)) err("closure-predicate", "a loop predicate must be a LuaPredicate object, not a function", `${path}.predicate`);
3029
+ if (!isWellFormedPredicate(l.predicate)) err("closure-predicate", "a loop predicate must be a well-formed LuaPredicate object ({op, left/right | value | path | args | arg}), not a function or an expression string", `${path}.predicate`);
2450
3030
  const bodyType = l.step.type;
2451
3031
  if (bodyType === "mapping") err("container-arm-empty", "a loop body needs a step, not a bare mapping", `${path}.step`);
2452
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
2453
- else {
3032
+ else if (isHitlNode(l.step)) {
3033
+ checkHitlArm(l.step, `${path}.step`, "loop");
3034
+ declared.push(l.step.id);
3035
+ } else {
2454
3036
  checkSingle(l.step, `${path}.step`, 1);
2455
3037
  declared.push(singleId(l.step));
2456
3038
  }
2457
3039
  break;
2458
3040
  }
2459
- case "approval": {
2460
- const a = entry;
2461
- checkId(a.id, path);
2462
- if (a.approver === "creator" && a.excludeInitiator === true) {
2463
- err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path, a.id);
2464
- }
2465
- if (a.fourEyes !== void 0 && a.editable !== true) {
2466
- err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path}.fourEyes`, a.id);
2467
- }
2468
- if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2469
- err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path}.editablePaths`, a.id);
2470
- }
2471
- for (const p of a.editablePaths ?? []) {
2472
- if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path}.editablePaths`, a.id);
2473
- }
2474
- if (Array.isArray(a.onTimeout)) {
2475
- const chain = a.onTimeout;
2476
- const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2477
- if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path}.onTimeout`, a.id);
2478
- const last = chain[chain.length - 1];
2479
- if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2480
- err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path}.onTimeout`, a.id);
2481
- }
2482
- }
2483
- if (typeof a.details === "string") {
2484
- for (const ref of templateStepRefs(a.details)) {
2485
- if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path, a.id);
2486
- }
2487
- }
2488
- declared.push(a.id);
2489
- break;
2490
- }
2491
- case "waitForSignal": {
2492
- const w = entry;
2493
- checkId(w.id, path);
2494
- if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path}.signal`, w.id);
2495
- declared.push(w.id);
3041
+ case "approval":
3042
+ case "waitForSignal":
3043
+ checkHitl(entry, path);
3044
+ declared.push(entry.id);
2496
3045
  break;
2497
- }
2498
3046
  default:
2499
3047
  err("invalid-envelope", `unknown entry type "${entry.type}"`, path);
2500
3048
  }
@@ -2531,6 +3079,46 @@ function isPredicate(p) {
2531
3079
  }
2532
3080
  __name(isPredicate, "isPredicate");
2533
3081
  __name3(isPredicate, "isPredicate");
3082
+ var isPredicateScalar = /* @__PURE__ */ __name3((v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean", "isPredicateScalar");
3083
+ function isPathOrLiteral(v) {
3084
+ if (typeof v !== "object" || v === null) return false;
3085
+ const r = v;
3086
+ if ("path" in r) return typeof r.path === "string" && r.path.length > 0;
3087
+ return "literal" in r && isPredicateScalar(r.literal);
3088
+ }
3089
+ __name(isPathOrLiteral, "isPathOrLiteral");
3090
+ __name3(isPathOrLiteral, "isPathOrLiteral");
3091
+ function isWellFormedPredicate(p) {
3092
+ if (!isPredicate(p)) return false;
3093
+ const r = p;
3094
+ switch (r.op) {
3095
+ case "eq":
3096
+ case "ne":
3097
+ case "lt":
3098
+ case "lte":
3099
+ case "gt":
3100
+ case "gte":
3101
+ return isPathOrLiteral(r.left) && isPathOrLiteral(r.right);
3102
+ case "in":
3103
+ case "notIn":
3104
+ return isPathOrLiteral(r.value) && Array.isArray(r.set) && r.set.every(isPredicateScalar);
3105
+ case "exists":
3106
+ case "notExists":
3107
+ return typeof r.path === "string" && r.path.length > 0;
3108
+ case "truthy":
3109
+ case "falsy":
3110
+ return isPathOrLiteral(r.value);
3111
+ case "and":
3112
+ case "or":
3113
+ return Array.isArray(r.args) && r.args.every(isWellFormedPredicate);
3114
+ case "not":
3115
+ return isWellFormedPredicate(r.arg);
3116
+ default:
3117
+ return false;
3118
+ }
3119
+ }
3120
+ __name(isWellFormedPredicate, "isWellFormedPredicate");
3121
+ __name3(isWellFormedPredicate, "isWellFormedPredicate");
2534
3122
  var GRAPH_HASH_PREFIX = "sha256-cj1:";
2535
3123
  function canonicalJson(value22) {
2536
3124
  const seen = /* @__PURE__ */ new WeakSet();
@@ -2575,14 +3163,9 @@ var WorkflowPlanError = class extends Error {
2575
3163
  this.name = "WorkflowPlanError";
2576
3164
  }
2577
3165
  };
2578
- var SINGLE_STEP_KINDS = {
2579
- step: "code",
2580
- agent: "agent",
2581
- tool: "tool",
2582
- workflow: "subrun"
2583
- };
2584
- var isSingleStep = /* @__PURE__ */ __name3((e) => e.type === "step" || e.type === "agent" || e.type === "tool" || e.type === "workflow", "isSingleStep");
2585
- var singleStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "singleStepId");
3166
+ var isArmStep = /* @__PURE__ */ __name3((e) => isWorkflowArmEntryType(e.type), "isArmStep");
3167
+ var armStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "armStepId");
3168
+ var armStepKind = /* @__PURE__ */ __name3((e) => WORKFLOW_ARM_ENTRY_STEP_KINDS[e.type], "armStepKind");
2586
3169
  var joinIdOf = /* @__PURE__ */ __name3((entryId) => `${entryId}.join`, "joinIdOf");
2587
3170
  var containerIdOf = /* @__PURE__ */ __name3((type, entryIndex) => `${type}@${entryIndex}`, "containerIdOf");
2588
3171
  function compilePlan(g) {
@@ -2598,10 +3181,10 @@ function compilePlan(g) {
2598
3181
  let prevTails = [];
2599
3182
  const foreachJoinToEntry = /* @__PURE__ */ new Map();
2600
3183
  g.definition.graph.forEach((entry, entryIndex) => {
2601
- if (isSingleStep(entry)) {
2602
- const id = singleStepId(entry);
3184
+ if (isArmStep(entry)) {
3185
+ const id = armStepId(entry);
2603
3186
  addNode(id, {
2604
- kind: SINGLE_STEP_KINDS[entry.type],
3187
+ kind: armStepKind(entry),
2605
3188
  dependsOn: prevTails,
2606
3189
  downstream: [],
2607
3190
  unsatisfiedDeps: prevTails.length,
@@ -2626,9 +3209,8 @@ function compilePlan(g) {
2626
3209
  ];
2627
3210
  return;
2628
3211
  case "sleep":
2629
- case "sleepUntil":
2630
- case "approval": {
2631
- const kind = entry.type === "approval" ? "approval" : entry.type;
3212
+ case "sleepUntil": {
3213
+ const kind = entry.type;
2632
3214
  addNode(entry.id, {
2633
3215
  kind,
2634
3216
  dependsOn: prevTails,
@@ -2641,24 +3223,12 @@ function compilePlan(g) {
2641
3223
  ];
2642
3224
  return;
2643
3225
  }
2644
- case "waitForSignal":
2645
- addNode(entry.id, {
2646
- kind: "signal",
2647
- dependsOn: prevTails,
2648
- downstream: [],
2649
- unsatisfiedDeps: prevTails.length,
2650
- entry
2651
- });
2652
- prevTails = [
2653
- entry.id
2654
- ];
2655
- return;
2656
3226
  case "parallel": {
2657
3227
  const entryId = containerIdOf("parallel", entryIndex);
2658
3228
  const childIds = entry.steps.map((arm) => {
2659
- const childId = singleStepId(arm);
3229
+ const childId = armStepId(arm);
2660
3230
  addNode(childId, {
2661
- kind: SINGLE_STEP_KINDS[arm.type],
3231
+ kind: armStepKind(arm),
2662
3232
  dependsOn: prevTails,
2663
3233
  downstream: [],
2664
3234
  unsatisfiedDeps: prevTails.length,
@@ -2696,9 +3266,9 @@ function compilePlan(g) {
2696
3266
  node.otherwise
2697
3267
  ] : node.steps;
2698
3268
  const childIds = arms.map((arm) => {
2699
- const childId = arm.type === "mapping" ? arm.id : singleStepId(arm);
3269
+ const childId = arm.type === "mapping" ? arm.id : armStepId(arm);
2700
3270
  addNode(childId, {
2701
- kind: arm.type === "mapping" ? "map" : SINGLE_STEP_KINDS[arm.type],
3271
+ kind: arm.type === "mapping" ? "map" : armStepKind(arm),
2702
3272
  dependsOn: [
2703
3273
  entryId
2704
3274
  ],
@@ -3072,7 +3642,16 @@ var WorkflowTemplateError = class extends Error {
3072
3642
  this.name = "WorkflowTemplateError";
3073
3643
  }
3074
3644
  };
3645
+ function isMapConfigObject(v) {
3646
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3647
+ }
3648
+ __name(isMapConfigObject, "isMapConfigObject");
3649
+ __name3(isMapConfigObject, "isMapConfigObject");
3075
3650
  function parseMapConfig(raw, stepId) {
3651
+ if (isMapConfigObject(raw)) return raw;
3652
+ if (typeof raw !== "string") {
3653
+ throw new Error(`Stored mapping step "${stepId}" has a mapConfig that is neither a JSON string nor an object.`);
3654
+ }
3076
3655
  try {
3077
3656
  return JSON.parse(raw);
3078
3657
  } catch (e) {
@@ -3081,6 +3660,13 @@ function parseMapConfig(raw, stepId) {
3081
3660
  }
3082
3661
  __name(parseMapConfig, "parseMapConfig");
3083
3662
  __name3(parseMapConfig, "parseMapConfig");
3663
+ function mapConfigWire(raw) {
3664
+ if (typeof raw === "string") return raw;
3665
+ if (isMapConfigObject(raw)) return canonicalJson(raw);
3666
+ return void 0;
3667
+ }
3668
+ __name(mapConfigWire, "mapConfigWire");
3669
+ __name3(mapConfigWire, "mapConfigWire");
3084
3670
  var TEMPLATE_PLACEHOLDER = /\$\{([^}]*)\}/g;
3085
3671
  var TEMPLATE_NAMESPACES = [
3086
3672
  "initData",
@@ -3281,6 +3867,77 @@ var rows = /* @__PURE__ */ __name3((s, path, page) => ({
3281
3867
  var fromKnowledge = /* @__PURE__ */ __name3((k) => ({
3282
3868
  knowledge: k
3283
3869
  }), "fromKnowledge");
3870
+ var CONTINUED_FAILURE_TAG = "continued_failure";
3871
+ var CONTINUED_FAILURE_DEFAULT_CODE = "step_failed";
3872
+ var CONTINUED_FAILURE_OUTPUT_SCHEMA = Object.freeze({
3873
+ type: "object",
3874
+ properties: {
3875
+ __lua_workflow: {
3876
+ type: "string",
3877
+ const: CONTINUED_FAILURE_TAG
3878
+ },
3879
+ failed: {
3880
+ type: "boolean",
3881
+ const: true
3882
+ },
3883
+ error: {
3884
+ type: "object",
3885
+ properties: {
3886
+ code: {
3887
+ type: "string"
3888
+ },
3889
+ message: {
3890
+ type: "string"
3891
+ }
3892
+ },
3893
+ required: [
3894
+ "code",
3895
+ "message"
3896
+ ]
3897
+ },
3898
+ text: {
3899
+ type: "string",
3900
+ const: ""
3901
+ }
3902
+ },
3903
+ required: [
3904
+ "__lua_workflow",
3905
+ "failed",
3906
+ "error",
3907
+ "text"
3908
+ ]
3909
+ });
3910
+ var CONTINUED_FAILURE_LEAF_PATHS = Object.freeze([
3911
+ "failed",
3912
+ "error",
3913
+ "error.code",
3914
+ "error.message",
3915
+ "text"
3916
+ ]);
3917
+ function continuedFailureValue(error, killReason) {
3918
+ const code = typeof error?.code === "string" && error.code || typeof killReason === "string" && killReason || CONTINUED_FAILURE_DEFAULT_CODE;
3919
+ const message = typeof error?.message === "string" && error.message ? error.message : code;
3920
+ return {
3921
+ __lua_workflow: CONTINUED_FAILURE_TAG,
3922
+ failed: true,
3923
+ error: {
3924
+ code,
3925
+ message
3926
+ },
3927
+ text: ""
3928
+ };
3929
+ }
3930
+ __name(continuedFailureValue, "continuedFailureValue");
3931
+ __name3(continuedFailureValue, "continuedFailureValue");
3932
+ function isContinuedFailureValue(v) {
3933
+ if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
3934
+ const o = v;
3935
+ const err = o.error;
3936
+ return o.__lua_workflow === CONTINUED_FAILURE_TAG && o.failed === true && o.text === "" && err !== null && typeof err === "object" && typeof err.code === "string" && typeof err.message === "string";
3937
+ }
3938
+ __name(isContinuedFailureValue, "isContinuedFailureValue");
3939
+ __name3(isContinuedFailureValue, "isContinuedFailureValue");
3940
+ var isHitlNode2 = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
3284
3941
  function lowerContainerArm(mapping, step22) {
3285
3942
  const stepId = nodeIdOf(step22);
3286
3943
  return {
@@ -3296,7 +3953,7 @@ function lowerContainerArm(mapping, step22) {
3296
3953
  }
3297
3954
  __name(lowerContainerArm, "lowerContainerArm");
3298
3955
  __name3(lowerContainerArm, "lowerContainerArm");
3299
- var nodeIdOf = /* @__PURE__ */ __name3((n) => n.type === "step" ? n.step.id : n.id, "nodeIdOf");
3956
+ var nodeIdOf = /* @__PURE__ */ __name3((n2) => n2.type === "step" ? n2.step.id : n2.id, "nodeIdOf");
3300
3957
  function entryIds(entry) {
3301
3958
  switch (entry.type) {
3302
3959
  case "parallel":
@@ -3366,7 +4023,28 @@ function resolvePlacements(calls) {
3366
4023
  break;
3367
4024
  }
3368
4025
  });
3369
- const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping) => {
4026
+ const hitlPlacementIssue = /* @__PURE__ */ __name3((node, ref, i, container) => {
4027
+ if (!isHitlNode2(node)) return void 0;
4028
+ const id = node.id;
4029
+ if (ref.armMap) {
4030
+ return {
4031
+ code: "node-type-unsupported-in-container",
4032
+ message: workflowHitlArmShapeMessage(node.type, id, "mapped-arm"),
4033
+ callIndex: i,
4034
+ stepId: id
4035
+ };
4036
+ }
4037
+ if (container !== "place" && !workflowContainerRunsHitlArm(container)) {
4038
+ return {
4039
+ code: "node-type-unsupported-in-container",
4040
+ message: workflowHitlArmUnsupportedMessage(node.type, id, container),
4041
+ callIndex: i,
4042
+ stepId: id
4043
+ };
4044
+ }
4045
+ return void 0;
4046
+ }, "hitlPlacementIssue");
4047
+ const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
3370
4048
  if ("node" in ref) {
3371
4049
  if (ref.node.type === "mapping" && !allowMapping) {
3372
4050
  issues.push({
@@ -3383,7 +4061,7 @@ function resolvePlacements(calls) {
3383
4061
  if (!d) {
3384
4062
  issues.push({
3385
4063
  code: "unknown-step-ref",
3386
- message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })`,
4064
+ message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)/approval(\u2026)/waitForSignal(\u2026)`,
3387
4065
  callIndex: i,
3388
4066
  stepId: ref.ref
3389
4067
  });
@@ -3398,6 +4076,11 @@ function resolvePlacements(calls) {
3398
4076
  });
3399
4077
  return void 0;
3400
4078
  }
4079
+ const hitl = hitlPlacementIssue(d.node, ref, i, container);
4080
+ if (hitl) {
4081
+ issues.push(hitl);
4082
+ return void 0;
4083
+ }
3401
4084
  const prior = placedBy.get(ref.ref);
3402
4085
  if (prior !== void 0 && prior !== i) {
3403
4086
  issues.push({
@@ -3411,23 +4094,31 @@ function resolvePlacements(calls) {
3411
4094
  placedBy.set(ref.ref, i);
3412
4095
  return d.node;
3413
4096
  }, "resolve");
4097
+ const claim = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
4098
+ if ("ref" in ref) {
4099
+ resolve(ref, i, allowMapping, container);
4100
+ return;
4101
+ }
4102
+ const hitl = hitlPlacementIssue(ref.node, ref, i, container);
4103
+ if (hitl) issues.push(hitl);
4104
+ }, "claim");
3414
4105
  calls.forEach((call, i) => {
3415
4106
  switch (call.kind) {
3416
4107
  case "place":
3417
- resolve({
4108
+ claim({
3418
4109
  ref: call.ref
3419
- }, i, false);
4110
+ }, i, false, "place");
3420
4111
  break;
3421
4112
  case "parallel":
3422
- for (const arm of call.arms) if ("ref" in arm) resolve(arm, i, false);
4113
+ for (const arm of call.arms) claim(arm, i, false, "parallel");
3423
4114
  break;
3424
4115
  case "conditional":
3425
- for (const a of call.arms) if ("ref" in a.target) resolve(a.target, i, true);
3426
- if (call.otherwise && "ref" in call.otherwise) resolve(call.otherwise, i, true);
4116
+ for (const a of call.arms) claim(a.target, i, true, "conditional");
4117
+ if (call.otherwise) claim(call.otherwise, i, true, "conditional");
3427
4118
  break;
3428
4119
  case "foreach":
3429
4120
  case "loop":
3430
- if ("ref" in call.body) resolve(call.body, i, false);
4121
+ claim(call.body, i, false, call.kind);
3431
4122
  break;
3432
4123
  default:
3433
4124
  break;
@@ -3435,9 +4126,9 @@ function resolvePlacements(calls) {
3435
4126
  });
3436
4127
  const graph = [];
3437
4128
  const lookup = /* @__PURE__ */ __name3((ref) => {
3438
- const n = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3439
- if (!n || !ref.armMap || n.type === "mapping") return n;
3440
- return lowerContainerArm(ref.armMap, n);
4129
+ const n2 = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
4130
+ if (!n2 || !ref.armMap || n2.type === "mapping" || isHitlNode2(n2)) return n2;
4131
+ return lowerContainerArm(ref.armMap, n2);
3441
4132
  }, "lookup");
3442
4133
  calls.forEach((call, i) => {
3443
4134
  switch (call.kind) {
@@ -3456,7 +4147,7 @@ function resolvePlacements(calls) {
3456
4147
  return;
3457
4148
  }
3458
4149
  case "parallel": {
3459
- const steps = call.arms.map(lookup).filter((n) => !!n && n.type !== "mapping");
4150
+ const steps = call.arms.map(lookup).filter((n2) => !!n2 && n2.type !== "mapping");
3460
4151
  const node = {
3461
4152
  type: "parallel",
3462
4153
  steps
@@ -3469,9 +4160,9 @@ function resolvePlacements(calls) {
3469
4160
  const steps = [];
3470
4161
  const predicates = [];
3471
4162
  for (const a of call.arms) {
3472
- const n = lookup(a.target);
3473
- if (!n) continue;
3474
- steps.push(n);
4163
+ const n2 = lookup(a.target);
4164
+ if (!n2) continue;
4165
+ steps.push(n2);
3475
4166
  predicates.push(a.predicate);
3476
4167
  }
3477
4168
  const node = {
@@ -3518,6 +4209,75 @@ function resolvePlacements(calls) {
3518
4209
  }
3519
4210
  __name(resolvePlacements, "resolvePlacements");
3520
4211
  __name3(resolvePlacements, "resolvePlacements");
4212
+ var GOAL_JUDGE_STEP_ID = "__goal_judge";
4213
+ var NON_LEAF_KINDS = /* @__PURE__ */ new Set([
4214
+ "foreach",
4215
+ "branch"
4216
+ ]);
4217
+ var CONDITIONAL_JOIN_ID = /^conditional@\d+\.join$/;
4218
+ function isConditionalJoinId(stepId) {
4219
+ return CONDITIONAL_JOIN_ID.test(stepId);
4220
+ }
4221
+ __name(isConditionalJoinId, "isConditionalJoinId");
4222
+ __name3(isConditionalJoinId, "isConditionalJoinId");
4223
+ function isPlainObject(v) {
4224
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4225
+ }
4226
+ __name(isPlainObject, "isPlainObject");
4227
+ __name3(isPlainObject, "isPlainObject");
4228
+ function leafValue(row) {
4229
+ if (row.status === "completed") return row.output === void 0 ? null : row.output;
4230
+ return continuedFailureValue(row.error, row.killReason);
4231
+ }
4232
+ __name(leafValue, "leafValue");
4233
+ __name3(leafValue, "leafValue");
4234
+ function runOutputLeaves(steps) {
4235
+ const dependedOn = /* @__PURE__ */ new Set();
4236
+ for (const s of steps) {
4237
+ if (s.stepId === GOAL_JUDGE_STEP_ID) continue;
4238
+ for (const d of s.dependsOn ?? []) dependedOn.add(d);
4239
+ }
4240
+ return steps.filter((s) => s.stepId !== GOAL_JUDGE_STEP_ID && !NON_LEAF_KINDS.has(s.kind ?? "") && s.foreachIndex === void 0 && s.loopParentId === void 0 && s.status !== "skipped" && !dependedOn.has(s.stepId));
4241
+ }
4242
+ __name(runOutputLeaves, "runOutputLeaves");
4243
+ __name3(runOutputLeaves, "runOutputLeaves");
4244
+ function deriveRunOutput(steps) {
4245
+ const leaves = runOutputLeaves(steps);
4246
+ if (leaves.length === 0) return void 0;
4247
+ if (leaves.length === 1) {
4248
+ const leaf = leaves[0];
4249
+ const value22 = leafValue(leaf);
4250
+ if (isConditionalJoinId(leaf.stepId) && isPlainObject(value22)) {
4251
+ const keys = Object.keys(value22);
4252
+ if (keys.length === 1) return {
4253
+ output: value22[keys[0]],
4254
+ leafIds: [
4255
+ keys[0]
4256
+ ]
4257
+ };
4258
+ }
4259
+ return {
4260
+ output: value22,
4261
+ leafIds: [
4262
+ leaf.stepId
4263
+ ]
4264
+ };
4265
+ }
4266
+ const output = {};
4267
+ for (const leaf of leaves) output[leaf.stepId] = leafValue(leaf);
4268
+ return {
4269
+ output,
4270
+ leafIds: leaves.map((l) => l.stepId)
4271
+ };
4272
+ }
4273
+ __name(deriveRunOutput, "deriveRunOutput");
4274
+ __name3(deriveRunOutput, "deriveRunOutput");
4275
+ function subrunSettledOutput(child) {
4276
+ if (child.output !== void 0) return child.output;
4277
+ return deriveRunOutput(child.steps ?? [])?.output ?? null;
4278
+ }
4279
+ __name(subrunSettledOutput, "subrunSettledOutput");
4280
+ __name3(subrunSettledOutput, "subrunSettledOutput");
3521
4281
  function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3522
4282
  const byId = /* @__PURE__ */ new Map();
3523
4283
  for (const s of steps) {
@@ -3579,7 +4339,35 @@ function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3579
4339
  }
3580
4340
  __name(seedLedgerFromRun, "seedLedgerFromRun");
3581
4341
  __name3(seedLedgerFromRun, "seedLedgerFromRun");
3582
- var armId2 = /* @__PURE__ */ __name3((arm) => arm.type === "step" ? arm.step.id : arm.id, "armId");
4342
+ var branchArmId = /* @__PURE__ */ __name3((arm) => arm.type === "step" ? arm.step.id : arm.id, "branchArmId");
4343
+ function branchSpecFromConditional(entry) {
4344
+ return {
4345
+ arms: entry.steps.map((arm, i) => ({
4346
+ stepId: branchArmId(arm),
4347
+ predicate: entry.predicates[i]
4348
+ })),
4349
+ ...entry.exclusive === true ? {
4350
+ exclusive: true
4351
+ } : {},
4352
+ ...entry.otherwise ? {
4353
+ otherwise: branchArmId(entry.otherwise)
4354
+ } : {}
4355
+ };
4356
+ }
4357
+ __name(branchSpecFromConditional, "branchSpecFromConditional");
4358
+ __name3(branchSpecFromConditional, "branchSpecFromConditional");
4359
+ function selectBranchArms(spec, ctx) {
4360
+ const taken = [];
4361
+ for (const arm of spec.arms) {
4362
+ if (spec.exclusive && taken.length > 0) break;
4363
+ const hit = arm.predicate ? evaluatePredicate(arm.predicate, ctx) : false;
4364
+ if (hit) taken.push(arm.stepId);
4365
+ }
4366
+ if (taken.length === 0 && spec.otherwise) taken.push(spec.otherwise);
4367
+ return taken;
4368
+ }
4369
+ __name(selectBranchArms, "selectBranchArms");
4370
+ __name3(selectBranchArms, "selectBranchArms");
3583
4371
  var canonical = /* @__PURE__ */ __name3((v) => JSON.stringify(sortKeys(v)), "canonical");
3584
4372
  var sortKeys = /* @__PURE__ */ __name3((v) => {
3585
4373
  if (Array.isArray(v)) return v.map(sortKeys);
@@ -3597,25 +4385,24 @@ function replayLedger(g, ledger) {
3597
4385
  r.stepId,
3598
4386
  r
3599
4387
  ]));
3600
- const stepResults = {};
3601
- for (const r of ledger.steps) if (r.status === "completed") stepResults[r.stepId] = r.output;
3602
- const ctx = {
4388
+ const requestContext = {
4389
+ runId: "replay",
4390
+ workflowId: g.definition.id,
4391
+ orgId: "",
4392
+ agentId: "",
4393
+ userId: "",
4394
+ trigger: "sdk",
4395
+ threadId: "replay",
4396
+ depth: 0,
4397
+ startedAt: 0,
4398
+ ...ledger.requestContext
4399
+ };
4400
+ const ctxFor = /* @__PURE__ */ __name3((id) => ({
3603
4401
  initData: ledger.initData,
3604
- stepResults,
4402
+ stepResults: ancestorResults(plan, id, rows22),
3605
4403
  state: ledger.state ?? {},
3606
- requestContext: {
3607
- runId: "replay",
3608
- workflowId: g.definition.id,
3609
- orgId: "",
3610
- agentId: "",
3611
- userId: "",
3612
- trigger: "sdk",
3613
- threadId: "replay",
3614
- depth: 0,
3615
- startedAt: 0,
3616
- ...ledger.requestContext
3617
- }
3618
- };
4404
+ requestContext
4405
+ }), "ctxFor");
3619
4406
  const verdicts = [];
3620
4407
  for (const id of plan.order) {
3621
4408
  const node = plan.steps[id];
@@ -3623,17 +4410,7 @@ function replayLedger(g, ledger) {
3623
4410
  if (!recorded || recorded.status === "pending" || recorded.status === "skipped") continue;
3624
4411
  if (node.kind === "branch") {
3625
4412
  const entry = node.entry;
3626
- const taken = [];
3627
- entry.steps.forEach((arm, i) => {
3628
- const pred = entry.predicates[i];
3629
- const hit = pred ? evaluatePredicate(pred, {
3630
- initData: ledger.initData,
3631
- stepResults,
3632
- state: ledger.state
3633
- }) : false;
3634
- if (hit && (!entry.exclusive || taken.length === 0)) taken.push(armId2(arm));
3635
- });
3636
- if (taken.length === 0 && entry.otherwise) taken.push(armId2(entry.otherwise));
4413
+ const taken = selectBranchArms(branchSpecFromConditional(entry), ctxFor(id));
3637
4414
  const recordedTaken = recorded.taken ?? inferTaken(entry, rows22);
3638
4415
  verdicts.push({
3639
4416
  stepId: id,
@@ -3644,7 +4421,7 @@ function replayLedger(g, ledger) {
3644
4421
  });
3645
4422
  } else if (node.kind === "map" && !id.endsWith(".join") && recorded.status === "completed") {
3646
4423
  const entry = node.entry;
3647
- const resolved = resolveMapping(parseMapConfig(entry.mapConfig, id), ctx);
4424
+ const resolved = resolveMapping(parseMapConfig(entry.mapConfig, id), ctxFor(id));
3648
4425
  const local = "error" in resolved ? {
3649
4426
  error: resolved.error,
3650
4427
  key: resolved.key
@@ -3659,7 +4436,7 @@ function replayLedger(g, ledger) {
3659
4436
  } else if (node.kind === "foreach") {
3660
4437
  const entry = node.entry;
3661
4438
  const source = node.dependsOn[0];
3662
- const items = source ? stepResults[source] : void 0;
4439
+ const items = source ? ctxFor(id).stepResults[source] : void 0;
3663
4440
  const local = Array.isArray(items) ? items.length : void 0;
3664
4441
  const recordedCount = recorded.itemCount ?? countChildren(entry, rows22);
3665
4442
  verdicts.push({
@@ -3669,6 +4446,15 @@ function replayLedger(g, ledger) {
3669
4446
  local,
3670
4447
  diverged: recordedCount !== local
3671
4448
  });
4449
+ } else if (node.kind === "subrun" && recorded.status === "completed" && recorded.child) {
4450
+ const local = subrunSettledOutput(recorded.child);
4451
+ verdicts.push({
4452
+ stepId: id,
4453
+ kind: "subrun",
4454
+ recorded: recorded.output,
4455
+ local,
4456
+ diverged: canonical(recorded.output) !== canonical(local)
4457
+ });
3672
4458
  }
3673
4459
  }
3674
4460
  return {
@@ -3678,6 +4464,77 @@ function replayLedger(g, ledger) {
3678
4464
  }
3679
4465
  __name(replayLedger, "replayLedger");
3680
4466
  __name3(replayLedger, "replayLedger");
4467
+ var JOIN = ".join";
4468
+ var entryOfJoin = /* @__PURE__ */ __name3((id) => id.endsWith(JOIN) ? id.slice(0, -JOIN.length) : void 0, "entryOfJoin");
4469
+ function replayResultOf(row, node) {
4470
+ if (!row) return void 0;
4471
+ if (row.status === "completed") return {
4472
+ value: row.output
4473
+ };
4474
+ if (row.status === "failed") {
4475
+ const onError = row.onError ?? node?.entry?.onError;
4476
+ if (onError === "continue") return {
4477
+ value: continuedFailureValue(row.error, row.killReason)
4478
+ };
4479
+ }
4480
+ return void 0;
4481
+ }
4482
+ __name(replayResultOf, "replayResultOf");
4483
+ __name3(replayResultOf, "replayResultOf");
4484
+ function ancestorResults(plan, id, rows22) {
4485
+ const out = {};
4486
+ const joinAliased = /* @__PURE__ */ new Set();
4487
+ const seen = /* @__PURE__ */ new Set();
4488
+ const take = /* @__PURE__ */ __name3((rowId) => {
4489
+ const hit = replayResultOf(rows22.get(rowId), plan.steps[rowId]);
4490
+ if (!hit) return void 0;
4491
+ if (!joinAliased.has(rowId)) out[rowId] = hit.value;
4492
+ const entryId = entryOfJoin(rowId);
4493
+ if (entryId) {
4494
+ out[entryId] = hit.value;
4495
+ joinAliased.add(entryId);
4496
+ const entryNode = plan.steps[entryId];
4497
+ if (entryNode?.kind === "foreach") {
4498
+ const body = branchArmId(entryNode.entry.step);
4499
+ out[body] = hit.value;
4500
+ joinAliased.add(body);
4501
+ }
4502
+ }
4503
+ return hit;
4504
+ }, "take");
4505
+ const walk2 = /* @__PURE__ */ __name3((ids) => {
4506
+ for (const dep of ids) {
4507
+ if (seen.has(dep)) continue;
4508
+ seen.add(dep);
4509
+ take(dep);
4510
+ const node = plan.steps[dep] ?? plan.steps[entryOfJoin(dep) ?? ""];
4511
+ if (!node) continue;
4512
+ if (node.kind === "foreach") {
4513
+ const body = branchArmId(node.entry.step);
4514
+ for (const rowId of rows22.keys()) if (rowId.startsWith(`${body}[`)) take(rowId);
4515
+ } else if (node.kind === "loop") {
4516
+ const body = branchArmId(node.entry.step);
4517
+ let best = -1;
4518
+ for (const rowId of rows22.keys()) {
4519
+ if (!rowId.startsWith(`${body}#`)) continue;
4520
+ const n2 = Number(rowId.slice(body.length + 1));
4521
+ if (!Number.isInteger(n2)) continue;
4522
+ const hit = take(rowId);
4523
+ if (!hit) continue;
4524
+ if (n2 > best) {
4525
+ best = n2;
4526
+ out[body] = hit.value;
4527
+ }
4528
+ }
4529
+ }
4530
+ walk2(node.dependsOn);
4531
+ }
4532
+ }, "walk");
4533
+ walk2(plan.steps[id]?.dependsOn ?? []);
4534
+ return out;
4535
+ }
4536
+ __name(ancestorResults, "ancestorResults");
4537
+ __name3(ancestorResults, "ancestorResults");
3681
4538
  function inferTaken(entry, rows22) {
3682
4539
  const arms = [
3683
4540
  ...entry.steps,
@@ -3685,7 +4542,7 @@ function inferTaken(entry, rows22) {
3685
4542
  entry.otherwise
3686
4543
  ] : []
3687
4544
  ];
3688
- return arms.map(armId2).filter((id) => {
4545
+ return arms.map(branchArmId).filter((id) => {
3689
4546
  const r = rows22.get(id);
3690
4547
  return r !== void 0 && r.status !== "skipped" && r.status !== "pending";
3691
4548
  });
@@ -3693,10 +4550,10 @@ function inferTaken(entry, rows22) {
3693
4550
  __name(inferTaken, "inferTaken");
3694
4551
  __name3(inferTaken, "inferTaken");
3695
4552
  function countChildren(entry, rows22) {
3696
- const body = armId2(entry.step);
3697
- let n = 0;
3698
- for (const id of rows22.keys()) if (id.startsWith(`${body}[`)) n += 1;
3699
- return n;
4553
+ const body = branchArmId(entry.step);
4554
+ let n2 = 0;
4555
+ for (const id of rows22.keys()) if (id.startsWith(`${body}[`)) n2 += 1;
4556
+ return n2;
3700
4557
  }
3701
4558
  __name(countChildren, "countChildren");
3702
4559
  __name3(countChildren, "countChildren");
@@ -3713,21 +4570,58 @@ function pruneUndefined(o) {
3713
4570
  __name(pruneUndefined, "pruneUndefined");
3714
4571
  __name3(pruneUndefined, "pruneUndefined");
3715
4572
  function runNextAction(run) {
3716
- if (isTerminalRunStatus(run.status) || !run.cancel?.requestedAt) return "none";
4573
+ if (isTerminalRunStatus(run.status)) return "none";
4574
+ if (run.status === "suspended" && run.gate?.kind === "budget") return "raise_budget";
4575
+ if (!run.cancel?.requestedAt) return "none";
3717
4576
  const forceAt = run.cancel.forceAfter ?? run.cancel.requestedAt + FORCE_CANCEL_STALE_MS;
3718
4577
  return Date.now() >= forceAt ? "force" : "cancel_again";
3719
4578
  }
3720
4579
  __name(runNextAction, "runNextAction");
3721
4580
  __name3(runNextAction, "runNextAction");
4581
+ var IN_FLIGHT = new Set(WORKFLOW_STEP_IN_FLIGHT);
4582
+ function emptyRunCounts() {
4583
+ const out = {
4584
+ total: 0,
4585
+ inFlight: 0
4586
+ };
4587
+ for (const status of WORKFLOW_STEP_STATUSES) out[status] = 0;
4588
+ return out;
4589
+ }
4590
+ __name(emptyRunCounts, "emptyRunCounts");
4591
+ __name3(emptyRunCounts, "emptyRunCounts");
4592
+ function runCountsFromStatusTally(tally) {
4593
+ const out = emptyRunCounts();
4594
+ for (const [status, n2] of Object.entries(tally)) {
4595
+ if (!Number.isFinite(n2) || n2 <= 0) continue;
4596
+ out.total += n2;
4597
+ if (WORKFLOW_STEP_STATUSES.includes(status)) out[status] += n2;
4598
+ if (IN_FLIGHT.has(status)) out.inFlight += n2;
4599
+ }
4600
+ return out;
4601
+ }
4602
+ __name(runCountsFromStatusTally, "runCountsFromStatusTally");
4603
+ __name3(runCountsFromStatusTally, "runCountsFromStatusTally");
4604
+ function runCountsFromStepStatuses(statuses) {
4605
+ const tally = {};
4606
+ for (const s of statuses) tally[s] = (tally[s] ?? 0) + 1;
4607
+ return runCountsFromStatusTally(tally);
4608
+ }
4609
+ __name(runCountsFromStepStatuses, "runCountsFromStepStatuses");
4610
+ __name3(runCountsFromStepStatuses, "runCountsFromStepStatuses");
4611
+ var n = /* @__PURE__ */ __name3((v) => typeof v === "number" && Number.isFinite(v) ? v : 0, "n");
3722
4612
  function runCounts(counts) {
4613
+ const c = counts ?? {};
4614
+ const rawInFlight = c.dispatched !== void 0 || c.claimed !== void 0 || c.running !== void 0 || c.cancellation_requested !== void 0;
3723
4615
  return {
3724
- total: counts?.total ?? 0,
3725
- completed: counts?.completed ?? 0,
3726
- failed: counts?.failed ?? 0,
3727
- skipped: counts?.skipped ?? 0,
3728
- running: counts?.inFlight ?? 0,
3729
- suspended: counts?.suspended ?? 0,
3730
- pending: (counts?.pending ?? 0) + (counts?.ready ?? 0) + (counts?.waiting ?? 0)
4616
+ total: n(c.total),
4617
+ completed: n(c.completed),
4618
+ failed: n(c.failed) + n(c.timeout) + n(c.reaped),
4619
+ skipped: n(c.skipped),
4620
+ // LUA-664: cancelled rows never ran — their own wire bucket, never a failure (the detail's map, verbatim).
4621
+ cancelled: n(c.cancelled),
4622
+ running: rawInFlight ? n(c.dispatched) + n(c.claimed) + n(c.running) + n(c.cancellation_requested) : n(c.inFlight),
4623
+ suspended: n(c.suspended),
4624
+ pending: n(c.pending) + n(c.ready) + n(c.waiting)
3731
4625
  };
3732
4626
  }
3733
4627
  __name(runCounts, "runCounts");
@@ -3748,7 +4642,23 @@ function runCancelView(cancel) {
3748
4642
  return {
3749
4643
  requestedAt: cancel.requestedAt,
3750
4644
  requestedBy: cancel.requestedBy ?? "",
3751
- forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS
4645
+ forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS,
4646
+ // LUA-686: the wall's own request (LUA-683 stamps `wall:true` from a system actor only) rides the wire, so a
4647
+ // client can tell a wall-ended `timed_out` run's audit block from a cancel it should chip. Absent, never false.
4648
+ ...cancel.wall === true ? {
4649
+ wall: true
4650
+ } : {},
4651
+ // LUA-704: a forced terminal's audit — who forced it, when, and a human's own reason text (the run's `reason`
4652
+ // is the terminal's). Absent on an unforced terminal, never null.
4653
+ ...typeof cancel.forcedAt === "number" ? {
4654
+ forcedAt: cancel.forcedAt
4655
+ } : {},
4656
+ ...typeof cancel.forcedBy === "string" && cancel.forcedBy ? {
4657
+ forcedBy: cancel.forcedBy
4658
+ } : {},
4659
+ ...typeof cancel.forceReason === "string" && cancel.forceReason ? {
4660
+ forceReason: cancel.forceReason
4661
+ } : {}
3752
4662
  };
3753
4663
  }
3754
4664
  __name(runCancelView, "runCancelView");
@@ -3757,7 +4667,7 @@ function runWorkspaceView(ws) {
3757
4667
  if (!ws) return void 0;
3758
4668
  const w = ws;
3759
4669
  return pruneUndefined({
3760
- kind: w.kind ?? "empty",
4670
+ kind: w.spec?.kind ?? w.kind ?? "empty",
3761
4671
  backend: w.backend,
3762
4672
  status: String(w.status ?? ""),
3763
4673
  branch: w.branch,
@@ -3771,7 +4681,6 @@ __name3(runWorkspaceView, "runWorkspaceView");
3771
4681
  function toWorkflowRunSummary(run) {
3772
4682
  const status = run.status;
3773
4683
  const principal = run.principal?.principal;
3774
- const gated = status === "gated" || status === "suspended";
3775
4684
  return pruneUndefined({
3776
4685
  runId: run.id,
3777
4686
  workflowId: run.workflowId,
@@ -3783,7 +4692,14 @@ function toWorkflowRunSummary(run) {
3783
4692
  orgId: run.orgId,
3784
4693
  spaceAgentId: run.spaceAgentId,
3785
4694
  status,
3786
- gate: gated ? run.gate : void 0,
4695
+ // LUA-702 (pass-3B D6): the gate rides the wire whenever a NON-terminal doc carries one. In the LUA-681 window
4696
+ // (an approval resolved beside an open exception park) the run is `running` AND still holds `gate{exception}`
4697
+ // until the tick tail parks it back; projecting it only on gated|suspended hid that park from R4 / the CLI /
4698
+ // the desktop (prod 2026-09-05: `status: running, gate: null`). A terminal doc carries no gate by contract
4699
+ // (`terminalizeRun` drops `gate` / `suspendedFor` in every terminal write), but a row terminalized before that
4700
+ // held — pre-LUA-677/694 docs, a script-tier backstop that passed no `unset` — may still carry a stale one:
4701
+ // hidden here, so a `failed` run never reads "Needs a decision" on R3/R4 or the CLI (#2476 review).
4702
+ gate: run.gate && !isTerminalRunStatus(status) ? run.gate : void 0,
3787
4703
  batchId: run.batchId,
3788
4704
  goalId: run.goalId,
3789
4705
  foreachOverflow: run.foreachOverflow,
@@ -3806,9 +4722,11 @@ function toWorkflowRunSummary(run) {
3806
4722
  principalKind: run.principalKind ?? "user",
3807
4723
  cancel: runCancelView(run.cancel),
3808
4724
  usage: runUsage(run),
4725
+ // LUA-697: a row persisted before the write seams (#2406 / #2465 / the script tier) leaves scrubbed here too —
4726
+ // idempotent on a scrubbed message, bounded input; an empty message falls back to the code.
3809
4727
  error: run.error ? {
3810
4728
  code: run.error.code ?? "error",
3811
- message: run.error.message ?? "",
4729
+ message: scrubStepErrorMessage(run.error.message) ?? run.error.code ?? "error",
3812
4730
  stepId: run.error.stepId
3813
4731
  } : void 0,
3814
4732
  kind: "run",
@@ -3821,11 +4739,94 @@ function toWorkflowRunSummary(run) {
3821
4739
  eventSeq: run.eventSeq ?? 0,
3822
4740
  cancellable: !isTerminalRunStatus(status),
3823
4741
  nextAction: runNextAction(run),
3824
- workspace: runWorkspaceView(run.workspace)
4742
+ workspace: runWorkspaceView(run.workspace),
4743
+ // LUA-643: the runs list says a result exists; R4 `fields:'full'` (output-ACL gated, audited) serves it. The
4744
+ // stamped flag is what a list page carries (it projects `output` out); the payload members cover R4's full row.
4745
+ hasOutput: run.hasOutput === true || run.output !== void 0 || run.outputRef !== void 0 ? true : void 0
3825
4746
  });
3826
4747
  }
3827
4748
  __name(toWorkflowRunSummary, "toWorkflowRunSummary");
3828
4749
  __name3(toWorkflowRunSummary, "toWorkflowRunSummary");
4750
+ var STEP_ERROR_DETAIL_KEYS = [
4751
+ "reason",
4752
+ "key",
4753
+ "integrationType",
4754
+ "candidates",
4755
+ "connectionId",
4756
+ "status",
4757
+ "code",
4758
+ "providerStatus",
4759
+ "model",
4760
+ "turnIndex",
4761
+ "message",
4762
+ // LUA-655: `output_schema_invalid` — the Ajv issues ({path, message}, ≤ 20, scrubbed at the source too) and the
4763
+ // in-session repair rounds the attempt spent.
4764
+ "issues",
4765
+ "repairRounds",
4766
+ // LUA-669 (#2446 review 4): the subrun failures — `subrun_<status>` names the child run and how it ended
4767
+ // (`childStatus`, `childReason: 'max_duration'` under `subrun_timed_out`), `subrun_depth_exceeded` its `depth` /
4768
+ // `max`, and every refusal the target `workflowId`. Short scalars only: the child's whole `childError` and the
4769
+ // cycle walk's `ancestors` stay off the wire — the child run's own R4 carries its error.
4770
+ "childRunId",
4771
+ "childStatus",
4772
+ "childReason",
4773
+ "max",
4774
+ "depth",
4775
+ "workflowId",
4776
+ // LUA-696 (review 2): the `ctx.once` key of an `effect_in_doubt` park — the step site stamps it here (scrubbed)
4777
+ // beside `park.effectKey`; a key is user text and leaves scrubbed like every other string leaf.
4778
+ "effectKey"
4779
+ ];
4780
+ var STEP_ERROR_DETAIL_MAX_BYTES = 8 * 1024;
4781
+ var DETAIL_MAX_DEPTH = 4;
4782
+ var DETAIL_MAX_ITEMS = 100;
4783
+ function scrubDetailValue(value22, depth) {
4784
+ if (typeof value22 === "string") return scrubSecretText(value22);
4785
+ if (typeof value22 === "number" || typeof value22 === "boolean" || value22 === null) return value22;
4786
+ if (depth >= DETAIL_MAX_DEPTH) return void 0;
4787
+ if (Array.isArray(value22)) {
4788
+ return value22.slice(0, DETAIL_MAX_ITEMS).map((v) => scrubDetailValue(v, depth + 1)).filter((v) => v !== void 0);
4789
+ }
4790
+ if (typeof value22 === "object") {
4791
+ const out = {};
4792
+ for (const [k, v] of Object.entries(value22)) {
4793
+ const s = scrubDetailValue(v, depth + 1);
4794
+ if (s !== void 0) out[k] = s;
4795
+ }
4796
+ return out;
4797
+ }
4798
+ return void 0;
4799
+ }
4800
+ __name(scrubDetailValue, "scrubDetailValue");
4801
+ __name3(scrubDetailValue, "scrubDetailValue");
4802
+ function stepErrorDetail(error) {
4803
+ if (!error || typeof error !== "object") return void 0;
4804
+ const d = error.detail;
4805
+ if (!d || typeof d !== "object" || Array.isArray(d)) return void 0;
4806
+ const bag = d;
4807
+ const out = {};
4808
+ for (const k of STEP_ERROR_DETAIL_KEYS) {
4809
+ if (!(k in bag)) continue;
4810
+ const v = scrubDetailValue(bag[k], 1);
4811
+ if (v !== void 0) out[k] = v;
4812
+ }
4813
+ if (!Object.keys(out).length) return void 0;
4814
+ let bytes;
4815
+ try {
4816
+ bytes = new TextEncoder().encode(JSON.stringify(out)).length;
4817
+ } catch {
4818
+ return void 0;
4819
+ }
4820
+ if (bytes <= STEP_ERROR_DETAIL_MAX_BYTES) return out;
4821
+ const scalars = Object.fromEntries(Object.entries(out).filter(([, v]) => v === null || typeof v !== "object"));
4822
+ return {
4823
+ ...scalars,
4824
+ __truncated: true,
4825
+ bytes
4826
+ };
4827
+ }
4828
+ __name(stepErrorDetail, "stepErrorDetail");
4829
+ __name3(stepErrorDetail, "stepErrorDetail");
3829
4830
  var MAX_HOLIDAYS = 366;
3830
4831
  var MAX_WALK_DAYS = 400;
3831
4832
  var HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
@@ -4382,58 +5383,86 @@ __name(rebaseItemPointer, "rebaseItemPointer");
4382
5383
  __name3(rebaseItemPointer, "rebaseItemPointer");
4383
5384
  var APPROVER_SPEC_MAX_USERS = 20;
4384
5385
  var ESCALATION_MAX_HOPS = 3;
4385
- var TemplateBindingSchema = z4.object({
4386
- template: z4.string().min(1).max(2048)
5386
+ var TemplateBindingSchema = z22.object({
5387
+ template: z22.string().min(1).max(2048)
4387
5388
  }).strict();
4388
- var ApproverSpecSchema = z4.union([
4389
- z4.literal("creator"),
4390
- z4.literal("org-admins"),
4391
- z4.object({
4392
- users: z4.union([
4393
- z4.array(z4.string().min(1).max(128)).min(1).max(APPROVER_SPEC_MAX_USERS),
5389
+ var ApproverSpecSchema = z22.union([
5390
+ z22.literal("creator"),
5391
+ z22.literal("org-admins"),
5392
+ z22.object({
5393
+ users: z22.union([
5394
+ z22.array(z22.string().min(1).max(128)).min(1).max(APPROVER_SPEC_MAX_USERS),
4394
5395
  TemplateBindingSchema
4395
5396
  ])
4396
5397
  }).strict(),
4397
- z4.object({
4398
- role: z4.union([
4399
- z4.string().min(1).max(128),
5398
+ z22.object({
5399
+ role: z22.union([
5400
+ z22.string().min(1).max(128),
4400
5401
  TemplateBindingSchema
4401
5402
  ])
4402
5403
  }).strict(),
4403
- z4.object({
4404
- group: z4.union([
4405
- z4.string().min(1).max(128),
5404
+ z22.object({
5405
+ group: z22.union([
5406
+ z22.string().min(1).max(128),
4406
5407
  TemplateBindingSchema
4407
5408
  ])
4408
5409
  }).strict(),
4409
- z4.object({
4410
- governance: z4.object({
4411
- policyId: z4.string().min(1).max(128)
5410
+ z22.object({
5411
+ governance: z22.object({
5412
+ policyId: z22.string().min(1).max(128)
4412
5413
  }).strict()
4413
5414
  }).strict()
4414
5415
  ]);
4415
- var FourEyesSchema = z4.object({
5416
+ var FourEyesSchema = z22.object({
4416
5417
  edit: ApproverSpecSchema,
4417
5418
  approve: ApproverSpecSchema
4418
5419
  }).strict();
4419
- var EscalationHopSchema = z4.object({
5420
+ var EscalationHopSchema = z22.object({
4420
5421
  escalateTo: ApproverSpecSchema,
4421
- timeoutHours: z4.number().finite().min(1).max(720)
5422
+ timeoutHours: z22.number().finite().min(1).max(720)
4422
5423
  }).strict();
4423
- var TerminalOutcomeSchema = z4.enum([
5424
+ var TerminalOutcomeSchema = z22.enum([
4424
5425
  "deny",
4425
5426
  "cancel-run",
4426
5427
  "fail",
4427
5428
  "continue"
4428
5429
  ]);
4429
- var ApprovalOnTimeoutSchema = z4.union([
5430
+ var ApprovalOnTimeoutSchema = z22.union([
4430
5431
  TerminalOutcomeSchema,
4431
5432
  EscalationHopSchema,
4432
- z4.array(z4.union([
5433
+ z22.array(z22.union([
4433
5434
  TerminalOutcomeSchema,
4434
5435
  EscalationHopSchema
4435
5436
  ])).min(1).max(ESCALATION_MAX_HOPS + 1)
4436
5437
  ]);
5438
+ var APPROVER_SPEC_SHAPES = [
5439
+ "'creator'",
5440
+ "'org-admins'",
5441
+ "{users:[userId, \u2026]}",
5442
+ "{role:roleName}",
5443
+ "{group:groupName}",
5444
+ "{governance:{policyId}}"
5445
+ ];
5446
+ var APPROVER_WRITTEN_MAX = 120;
5447
+ var USER_ID_SHAPED_RE = /^[^\s@]{1,128}$/;
5448
+ function describeApproverSpecRefusal(spec) {
5449
+ const raw = spec === void 0 ? "undefined" : JSON.stringify(spec) ?? String(spec);
5450
+ const written = raw.length > APPROVER_WRITTEN_MAX ? `${raw.slice(0, APPROVER_WRITTEN_MAX - 1)}\u2026` : raw;
5451
+ const users = typeof spec === "object" && spec !== null ? spec.users : void 0;
5452
+ const approver = typeof users === "string" && USER_ID_SHAPED_RE.test(users) ? {
5453
+ users: [
5454
+ users
5455
+ ]
5456
+ } : "creator";
5457
+ const message = `approver ${written} is not an approver \u2014 legal: ${APPROVER_SPEC_SHAPES.join(" | ")}. 'creator' is the person who started the run: write approver:'creator' for "ask me" / "I approve"; {users:[\u2026]} takes user ids, never emails, names or {type:'user'}` + (approver === "creator" ? "" : `; here: approver:${JSON.stringify(approver)}`);
5458
+ return {
5459
+ approver,
5460
+ written,
5461
+ message
5462
+ };
5463
+ }
5464
+ __name(describeApproverSpecRefusal, "describeApproverSpecRefusal");
5465
+ __name3(describeApproverSpecRefusal, "describeApproverSpecRefusal");
4437
5466
  var BINDING_ROOTS = [
4438
5467
  "initData",
4439
5468
  "stepResults",
@@ -4468,7 +5497,19 @@ function validateApproverBlock(node, opts = {
4468
5497
  if (!r.success) {
4469
5498
  const users = spec?.users;
4470
5499
  if (Array.isArray(users) && users.length > APPROVER_SPEC_MAX_USERS) push("cap-exceeded", path, `at most ${APPROVER_SPEC_MAX_USERS} users`);
4471
- else push("approver-invalid", path, r.error.issues[0]?.message ?? "invalid approver");
5500
+ else {
5501
+ const refusal = describeApproverSpecRefusal(spec);
5502
+ issues.push({
5503
+ code: "approver-invalid",
5504
+ path,
5505
+ severity: "error",
5506
+ message: refusal.message,
5507
+ repair: {
5508
+ approver: refusal.approver,
5509
+ written: refusal.written
5510
+ }
5511
+ });
5512
+ }
4472
5513
  return;
4473
5514
  }
4474
5515
  const s = r.data;
@@ -4707,8 +5748,8 @@ __name(armEntry, "armEntry");
4707
5748
  __name3(armEntry, "armEntry");
4708
5749
  function ofEntry(e) {
4709
5750
  if (!e || typeof e !== "object") return ZERO;
4710
- const n = e;
4711
- switch (n.type) {
5751
+ const n2 = e;
5752
+ switch (n2.type) {
4712
5753
  case "agent":
4713
5754
  return {
4714
5755
  steps: {
@@ -4741,17 +5782,17 @@ function ofEntry(e) {
4741
5782
  agentCalls: 0
4742
5783
  };
4743
5784
  case "parallel": {
4744
- const arms = Array.isArray(n.steps) ? n.steps : [];
5785
+ const arms = Array.isArray(n2.steps) ? n2.steps : [];
4745
5786
  return arms.map(armEntry).map(ofEntry).reduce(add, ZERO);
4746
5787
  }
4747
5788
  case "conditional": {
4748
- const arms = (Array.isArray(n.steps) ? n.steps : []).map(armEntry).map(ofEntry);
5789
+ const arms = (Array.isArray(n2.steps) ? n2.steps : []).map(armEntry).map(ofEntry);
4749
5790
  if (arms.length === 0) return ZERO;
4750
- const hasOtherwise = n.otherwise !== void 0;
5791
+ const hasOtherwise = n2.otherwise !== void 0;
4751
5792
  const minArm = arms.reduce((a, b) => b.credits.min < a.credits.min ? b : a);
4752
5793
  const maxArm = arms.reduce((a, b) => b.credits.max > a.credits.max ? b : a);
4753
5794
  const summed = arms.reduce(add, ZERO);
4754
- const max = n.exclusive === true ? maxArm : summed;
5795
+ const max = n2.exclusive === true ? maxArm : summed;
4755
5796
  const min = hasOtherwise ? minArm : {
4756
5797
  ...ZERO
4757
5798
  };
@@ -4768,17 +5809,14 @@ function ofEntry(e) {
4768
5809
  };
4769
5810
  }
4770
5811
  case "foreach": {
4771
- const body = ofEntry(armEntry(n.step));
4772
- const opts = n.options ?? {};
4773
- const cap = typeof opts.maxItems === "number" && opts.maxItems > 0 ? opts.maxItems : 1;
5812
+ const body = ofEntry(armEntry(n2.step));
5813
+ const opts = n2.opts ?? {};
5814
+ const cap = typeof opts.maxItems === "number" && opts.maxItems > 0 ? opts.maxItems : WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
4774
5815
  return scale(body, 0, cap);
4775
5816
  }
4776
- case "loop":
4777
- case "dowhile":
4778
- case "dountil": {
4779
- const body = ofEntry(armEntry(n.step));
4780
- const opts = n.options ?? {};
4781
- const cap = typeof opts.maxIterations === "number" && opts.maxIterations > 0 ? opts.maxIterations : 1;
5817
+ case "loop": {
5818
+ const body = ofEntry(armEntry(n2.step));
5819
+ const cap = typeof n2.maxIterations === "number" && n2.maxIterations > 0 ? n2.maxIterations : WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
4782
5820
  return scale(body, 1, cap);
4783
5821
  }
4784
5822
  default:
@@ -4813,6 +5851,67 @@ function estimateGraph(envelopeOrGraph) {
4813
5851
  }
4814
5852
  __name(estimateGraph, "estimateGraph");
4815
5853
  __name3(estimateGraph, "estimateGraph");
5854
+ var isRecord2 = /* @__PURE__ */ __name3((v) => !!v && typeof v === "object" && !Array.isArray(v), "isRecord");
5855
+ function* singleStepsOf(entry) {
5856
+ if (!isRecord2(entry)) return;
5857
+ switch (entry.type) {
5858
+ case "step":
5859
+ case "agent":
5860
+ case "tool":
5861
+ yield entry;
5862
+ return;
5863
+ case "workflow":
5864
+ yield entry;
5865
+ if (Array.isArray(entry.graph)) yield* singleStepsOf(entry.graph[1]);
5866
+ return;
5867
+ case "parallel":
5868
+ case "conditional":
5869
+ if (Array.isArray(entry.steps)) for (const arm of entry.steps) yield* singleStepsOf(arm);
5870
+ yield* singleStepsOf(entry.otherwise);
5871
+ return;
5872
+ case "foreach":
5873
+ case "loop":
5874
+ yield* singleStepsOf(entry.step);
5875
+ return;
5876
+ default:
5877
+ return;
5878
+ }
5879
+ }
5880
+ __name(singleStepsOf, "singleStepsOf");
5881
+ __name3(singleStepsOf, "singleStepsOf");
5882
+ function entriesOf(graph) {
5883
+ const definition = isRecord2(graph) ? graph.definition : void 0;
5884
+ const entries = isRecord2(definition) ? definition.graph : void 0;
5885
+ return Array.isArray(entries) ? entries : [];
5886
+ }
5887
+ __name(entriesOf, "entriesOf");
5888
+ __name3(entriesOf, "entriesOf");
5889
+ function inheritTargets(graphs) {
5890
+ const targets = /* @__PURE__ */ new Set();
5891
+ for (const graph of graphs) {
5892
+ for (const entry of entriesOf(graph)) {
5893
+ for (const node of singleStepsOf(entry)) {
5894
+ if (node.type === "workflow" && node.workspace === "inherit" && typeof node.workflowId === "string" && node.workflowId.length > 0) {
5895
+ targets.add(node.workflowId);
5896
+ }
5897
+ }
5898
+ }
5899
+ }
5900
+ return targets;
5901
+ }
5902
+ __name(inheritTargets, "inheritTargets");
5903
+ __name3(inheritTargets, "inheritTargets");
5904
+ function needsInheritedWorkspace(graph) {
5905
+ if (!isRecord2(graph) || graph.workspace !== void 0) return false;
5906
+ for (const entry of entriesOf(graph)) {
5907
+ for (const node of singleStepsOf(entry)) {
5908
+ if (node.workspace !== void 0 && node.workspace !== "inherit") return true;
5909
+ }
5910
+ }
5911
+ return false;
5912
+ }
5913
+ __name(needsInheritedWorkspace, "needsInheritedWorkspace");
5914
+ __name3(needsInheritedWorkspace, "needsInheritedWorkspace");
4816
5915
 
4817
5916
  // src/types/workflow.ts
4818
5917
  function createStep(s) {
@@ -4922,7 +6021,7 @@ var assertPredicate = /* @__PURE__ */ __name((p, where) => {
4922
6021
  }, "assertPredicate");
4923
6022
  var assertRetry = /* @__PURE__ */ __name((r, id) => {
4924
6023
  if (!r) return;
4925
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.backoff must be 'fixed' | 'exponential'`);
6024
+ if (r.backoff !== void 0 && !WORKFLOW_RETRY_BACKOFFS.includes(r.backoff)) throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.backoff must be ${WORKFLOW_RETRY_BACKOFFS.map((b) => `'${b}'`).join(" | ")}`);
4926
6025
  if (r.maxBackoffSeconds !== void 0) {
4927
6026
  if (r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.maxBackoffSeconds is only meaningful with backoff:'exponential'`);
4928
6027
  if (r.maxBackoffSeconds <= 0 || r.backoffSeconds !== void 0 && r.maxBackoffSeconds < r.backoffSeconds) throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.maxBackoffSeconds must be > 0 and \u2265 backoffSeconds`);
@@ -5008,7 +6107,7 @@ var LuaWorkflow = class {
5008
6107
  getEnvTemplateKeys() {
5009
6108
  return this.built.envTemplateKeys;
5010
6109
  }
5011
- /** `.workflow(id, ref)` targets by name — `ManifestWorkflow.workflowRefs`. */
6110
+ /** `.workflow(id, ref)` targets by name — `ManifestWorkflow.workflowRefs`; `workspace:'inherit'` marks the inherit children the compiler defers `workspace-not-declared` for. */
5012
6111
  getNestedWorkflowRefs() {
5013
6112
  return this.built.nestedRefs;
5014
6113
  }
@@ -5038,6 +6137,7 @@ var LuaWorkflow = class {
5038
6137
  };
5039
6138
  if (cfg.concurrencyPolicy !== void 0) envelope.concurrencyPolicy = cfg.concurrencyPolicy;
5040
6139
  if (cfg.workspace !== void 0) envelope.workspace = cfg.workspace;
6140
+ if (cfg.connections !== void 0) envelope.connections = cfg.connections;
5041
6141
  return withDefaultsFilled(envelope);
5042
6142
  }
5043
6143
  };
@@ -5069,16 +6169,16 @@ function stepNodeOf(s) {
5069
6169
  }
5070
6170
  __name(stepNodeOf, "stepNodeOf");
5071
6171
  function materializeEntry(entry, steps) {
5072
- const single = /* @__PURE__ */ __name((n) => {
5073
- if (n.type === "step" && steps[n.step.id]) return stepNodeOf(steps[n.step.id]);
5074
- if (n.type === "workflow" && n.workflowId === WORKFLOW_ARM_SUBRUN_ID && n.graph) return {
5075
- ...n,
6172
+ const single = /* @__PURE__ */ __name((n2) => {
6173
+ if (n2.type === "step" && steps[n2.step.id]) return stepNodeOf(steps[n2.step.id]);
6174
+ if (n2.type === "workflow" && n2.workflowId === WORKFLOW_ARM_SUBRUN_ID && n2.graph) return {
6175
+ ...n2,
5076
6176
  graph: [
5077
- n.graph[0],
5078
- single(n.graph[1])
6177
+ n2.graph[0],
6178
+ single(n2.graph[1])
5079
6179
  ]
5080
6180
  };
5081
- return n;
6181
+ return n2;
5082
6182
  }, "single");
5083
6183
  switch (entry.type) {
5084
6184
  case "step":
@@ -5109,8 +6209,14 @@ function materializeEntry(entry, steps) {
5109
6209
  }
5110
6210
  }
5111
6211
  __name(materializeEntry, "materializeEntry");
6212
+ var isHitlEntry = /* @__PURE__ */ __name((n2) => isWorkflowHitlEntryType(n2?.type), "isHitlEntry");
5112
6213
  function graphHasHitl(graph, steps) {
5113
- if (graph.some((e) => e.type === "approval" || e.type === "waitForSignal")) return true;
6214
+ for (const e of graph) {
6215
+ if (isHitlEntry(e)) return true;
6216
+ if (e.type === "parallel" && e.steps.some(isHitlEntry)) return true;
6217
+ if (e.type === "conditional" && (e.steps.some(isHitlEntry) || isHitlEntry(e.otherwise))) return true;
6218
+ if ((e.type === "foreach" || e.type === "loop") && isHitlEntry(e.step)) return true;
6219
+ }
5114
6220
  return Object.values(steps).some((s) => s.suspendSchema !== void 0);
5115
6221
  }
5116
6222
  __name(graphHasHitl, "graphHasHitl");
@@ -5143,7 +6249,6 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5143
6249
  if (this.steps[s.id] && this.steps[s.id] !== s) throw new LuaWorkflowBuildError("duplicate-step-id", `step id "${s.id}" is declared twice`);
5144
6250
  const tier = s.tier ?? (s.workspace ? "job" : void 0);
5145
6251
  if (s.workspace && s.tier !== void 0 && s.tier !== "job") throw new LuaWorkflowBuildError("workspace-requires-job-tier", `"${s.id}": a step mounting a workspace must be tier:'job'`);
5146
- if (s.workspace && !this.config.workspace) throw new LuaWorkflowBuildError("workspace-not-declared", `"${s.id}" mounts a workspace but createWorkflow declares none`);
5147
6252
  if (s.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${s.id}": jobTools require tier:'job' (job-tools-require-job-tier)`);
5148
6253
  assertTimeout({
5149
6254
  id: s.id,
@@ -5166,7 +6271,6 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5166
6271
  const [mapping, target] = arm;
5167
6272
  if (target === void 0 || arm.length < 2) throw new LuaWorkflowBuildError("container-arm-empty", `${where}: a bare mapping arm has nothing to run`);
5168
6273
  if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) throw new LuaWorkflowBuildError("mapping-placement", `${where}: the arm head must be a map config object`);
5169
- if (mapping.type === "approval" || target.type === "approval") throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
5170
6274
  assertNoClosure(mapping, `${where} arm map`);
5171
6275
  this.recordEnvRefs(mapping);
5172
6276
  const inner = this.armRef(target, where);
@@ -5184,8 +6288,9 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5184
6288
  if (typeof arm === "string") return {
5185
6289
  ref: arm
5186
6290
  };
5187
- if (arm && typeof arm === "object" && arm.type === "approval") {
5188
- throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
6291
+ if (arm && typeof arm === "object" && isHitlEntry(arm)) {
6292
+ const id = arm.id;
6293
+ throw new LuaWorkflowBuildError("invalid-step", `${where}: an approval / waitForSignal arm is placed by id \u2014 declare it with .approval(${JSON.stringify(id ?? "id")}, \u2026) / .waitForSignal(\u2026) and pass the id string`);
5189
6294
  }
5190
6295
  return {
5191
6296
  node: this.registerStep(arm, where)
@@ -5384,12 +6489,15 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5384
6489
  }
5385
6490
  const tier = opts.tier ?? (opts.workspace ? "job" : void 0);
5386
6491
  if (opts.workspace && opts.tier !== void 0 && opts.tier !== "job") throw new LuaWorkflowBuildError("workspace-requires-job-tier", `"${id}": a step mounting a workspace must be tier:'job'`);
5387
- if (opts.workspace && !this.config.workspace) throw new LuaWorkflowBuildError("workspace-not-declared", `"${id}" mounts a workspace but createWorkflow declares none`);
5388
6492
  if (opts.harness !== void 0 && tier !== "job") throw new LuaWorkflowBuildError("harness-requires-job-tier", `"${id}": harness is only legal on a tier:'job' agent step`);
5389
6493
  if (opts.toolScope?.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${id}": toolScope.jobTools require tier:'job' (job-tools-require-job-tier)`);
5390
- if (opts.maxTurns !== void 0) {
5391
- if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxTurns is only legal on a tier:'job' agent step`);
5392
- if (!Number.isInteger(opts.maxTurns) || opts.maxTurns < 1 || opts.maxTurns > 500) throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": maxTurns must be an integer 1..500`);
6494
+ for (const m of WORKFLOW_JOB_RANGE_MEMBERS) {
6495
+ if (opts[m] === void 0) continue;
6496
+ if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": ${m} is only legal on a tier:'job' agent step`);
6497
+ if (!isWithinWorkflowJobRange(m, opts[m])) {
6498
+ const { min, max } = WORKFLOW_JOB_RANGES[m];
6499
+ throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": ${m} must be an integer ${min}..${max}`);
6500
+ }
5393
6501
  }
5394
6502
  assertTimeout({
5395
6503
  id,
@@ -5417,7 +6525,9 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5417
6525
  workspace: opts.workspace,
5418
6526
  jobResources: opts.jobResources,
5419
6527
  harness: opts.harness,
5420
- maxTurns: opts.maxTurns
6528
+ maxTurns: opts.maxTurns,
6529
+ maxMessages: opts.maxMessages,
6530
+ maxInputTokens: opts.maxInputTokens
5421
6531
  });
5422
6532
  return this.push({
5423
6533
  kind: "declare",
@@ -5539,8 +6649,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5539
6649
  itemTimeout: opts.itemTimeout
5540
6650
  });
5541
6651
  return this.push({
5542
- kind: "entry",
5543
- entry: node
6652
+ kind: "declare",
6653
+ node
5544
6654
  });
5545
6655
  }
5546
6656
  waitForSignal(id, opts) {
@@ -5558,12 +6668,13 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5558
6668
  acceptedSources: opts.acceptedSources
5559
6669
  });
5560
6670
  return this.push({
5561
- kind: "entry",
5562
- entry: node
6671
+ kind: "declare",
6672
+ node
5563
6673
  });
5564
6674
  }
5565
6675
  workflow(id, ref, input, opts) {
5566
6676
  this.assertId(id, "workflow()");
6677
+ assertRetry(opts?.retry, id);
5567
6678
  const name = typeof ref === "string" ? ref : ref instanceof LuaWorkflow ? ref.getName() : void 0;
5568
6679
  if (!name) throw new LuaWorkflowBuildError("invalid-envelope", `workflow("${id}") needs a LuaWorkflow or a workflow name`);
5569
6680
  if (opts?.workspace === "inherit") {
@@ -5572,7 +6683,11 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5572
6683
  }
5573
6684
  assertNoClosure(input, `workflow("${id}").input`);
5574
6685
  this.recordEnvRefs(input);
5575
- this.nestedRefs.push({
6686
+ this.nestedRefs.push(opts?.workspace === "inherit" ? {
6687
+ id,
6688
+ name,
6689
+ workspace: "inherit"
6690
+ } : {
5576
6691
  id,
5577
6692
  name
5578
6693
  });
@@ -5581,11 +6696,12 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5581
6696
  id,
5582
6697
  workflowId: name,
5583
6698
  input,
5584
- workspace: opts?.workspace
6699
+ workspace: opts?.workspace,
6700
+ retry: opts?.retry
5585
6701
  });
5586
6702
  return this.push({
5587
- kind: "entry",
5588
- entry: node
6703
+ kind: "declare",
6704
+ node
5589
6705
  });
5590
6706
  }
5591
6707
  commit() {
@@ -5595,7 +6711,7 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5595
6711
  const { graph, issues } = resolvePlacements(this.calls);
5596
6712
  const fatal = issues[0];
5597
6713
  if (fatal) {
5598
- const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id }) somewhere in the chain \u2014 before OR after the reference" : void 0;
6714
+ const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)/approval(\u2026)/waitForSignal(\u2026) somewhere in the chain \u2014 before OR after the reference" : void 0;
5599
6715
  throw new LuaWorkflowBuildError(fatal.code, fatal.message, hint);
5600
6716
  }
5601
6717
  if (graph.length === 0) throw new LuaWorkflowBuildError("empty-graph", `workflow "${this.config.name}" has no entries`);
@@ -5608,7 +6724,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5608
6724
  if (graphHasHitl(graph, this.steps) && this.config.budget?.maxDurationSeconds === void 0) {
5609
6725
  this.warnings.push({
5610
6726
  code: "hitl-duration-defaulted",
5611
- message: `budget.maxDurationSeconds defaulted to ${WORKFLOW_HITL_MAX_DURATION_SECONDS} s (30 d) because the graph contains an approval / waitForSignal / suspend-capable step \u2014 set it explicitly to silence`
6727
+ // LUA-668: names the workflow two HITL workflows in one project printed two identical lines that read as a duplicate.
6728
+ message: `workflow "${this.config.name}": budget.maxDurationSeconds defaulted to ${WORKFLOW_HITL_MAX_DURATION_SECONDS} s (30 d) because the graph contains an approval / waitForSignal / suspend-capable step \u2014 set it explicitly to silence`
5612
6729
  });
5613
6730
  }
5614
6731
  this.checkScheduleInput();
@@ -5671,8 +6788,8 @@ function createWorkflow(cfg) {
5671
6788
  if (v.roles.length > 20 || (v.users?.length ?? 0) > 50) throw new LuaWorkflowBuildError("cap-exceeded", "outputVisibility allows \u2264 20 roles and \u2264 50 users");
5672
6789
  }
5673
6790
  if (cfg.backfillOnEnable?.maxOccurrences !== void 0) {
5674
- const n = cfg.backfillOnEnable.maxOccurrences;
5675
- if (!Number.isInteger(n) || n < 1 || n > 200) throw new LuaWorkflowBuildError("invalid-envelope", "backfillOnEnable.maxOccurrences must be an integer in 1..200 (backfill-max-occurrences-out-of-range; the org maxBatchItems twin is checked at publish/R56)");
6791
+ const n2 = cfg.backfillOnEnable.maxOccurrences;
6792
+ if (!Number.isInteger(n2) || n2 < 1 || n2 > 200) throw new LuaWorkflowBuildError("invalid-envelope", "backfillOnEnable.maxOccurrences must be an integer in 1..200 (backfill-max-occurrences-out-of-range; the org maxBatchItems twin is checked at publish/R56)");
5676
6793
  }
5677
6794
  const keys = /* @__PURE__ */ new Set();
5678
6795
  envRefKeys(cfg.schedule, keys);