lua-cli 3.32.1 → 3.32.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -1489,6 +1492,87 @@ function hasCapability(profiles, profileId, required) {
1489
1492
  }
1490
1493
  __name(hasCapability, "hasCapability");
1491
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");
1492
1576
  var SYSTEM_USER_PREFIX = "system:";
1493
1577
  function isSystemRun(identity) {
1494
1578
  return identity.userId.startsWith(SYSTEM_USER_PREFIX);
@@ -1620,8 +1704,199 @@ function scheduledWorkflowRunIdForTime(jobId, scheduledTime) {
1620
1704
  __name(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1621
1705
  __name2(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1622
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_RETRY_MIN_ATTEMPTS = 1;
1713
+ var WORKFLOW_RETRY_POLICY_KEYS = [
1714
+ "maxAttempts",
1715
+ "backoffSeconds",
1716
+ "backoff",
1717
+ "maxBackoffSeconds"
1718
+ ];
1719
+ var WORKFLOW_RETRY_MAX_ATTEMPTS = 20;
1720
+ function workflowRetryMaxAttemptsMessage(got) {
1721
+ const tail = got === void 0 ? "" : ` (got ${JSON.stringify(got)})`;
1722
+ return `\`retry.maxAttempts\` must be an integer ${WORKFLOW_RETRY_MIN_ATTEMPTS}..${WORKFLOW_RETRY_MAX_ATTEMPTS}${tail}`;
1723
+ }
1724
+ __name(workflowRetryMaxAttemptsMessage, "workflowRetryMaxAttemptsMessage");
1725
+ __name2(workflowRetryMaxAttemptsMessage, "workflowRetryMaxAttemptsMessage");
1726
+ function isWithinWorkflowRetryAttempts(value3) {
1727
+ return typeof value3 === "number" && Number.isInteger(value3) && value3 >= WORKFLOW_RETRY_MIN_ATTEMPTS && value3 <= WORKFLOW_RETRY_MAX_ATTEMPTS;
1728
+ }
1729
+ __name(isWithinWorkflowRetryAttempts, "isWithinWorkflowRetryAttempts");
1730
+ __name2(isWithinWorkflowRetryAttempts, "isWithinWorkflowRetryAttempts");
1731
+ var WORKFLOW_RETRY_ENGINE_KEYS = [
1732
+ "budgetBaseAttempt"
1733
+ ];
1734
+ function workflowRetryUnknownMembersMessage(keys) {
1735
+ const named = keys.map((k) => {
1736
+ const engine = WORKFLOW_RETRY_ENGINE_KEYS.includes(k);
1737
+ return `\`${k}\`${engine ? " (engine-owned \u2014 stamped by resetAttempts, never authored)" : ""}`;
1738
+ });
1739
+ return `\`retry\` has no member ${named.join(", ")}; members: ${WORKFLOW_RETRY_POLICY_KEYS.join(", ")}`;
1740
+ }
1741
+ __name(workflowRetryUnknownMembersMessage, "workflowRetryUnknownMembersMessage");
1742
+ __name2(workflowRetryUnknownMembersMessage, "workflowRetryUnknownMembersMessage");
1743
+ function unknownWorkflowRetryMembers(retry) {
1744
+ if (!retry || typeof retry !== "object" || Array.isArray(retry)) return [];
1745
+ return Object.keys(retry).filter((k) => !WORKFLOW_RETRY_POLICY_KEYS.includes(k));
1746
+ }
1747
+ __name(unknownWorkflowRetryMembers, "unknownWorkflowRetryMembers");
1748
+ __name2(unknownWorkflowRetryMembers, "unknownWorkflowRetryMembers");
1749
+ function authoredRetryPolicy(retry) {
1750
+ if (!retry || typeof retry !== "object" || Array.isArray(retry)) return void 0;
1751
+ const src = retry;
1752
+ const out = {};
1753
+ for (const k of WORKFLOW_RETRY_POLICY_KEYS) if (src[k] !== void 0) out[k] = src[k];
1754
+ return Object.keys(out).length ? out : void 0;
1755
+ }
1756
+ __name(authoredRetryPolicy, "authoredRetryPolicy");
1757
+ __name2(authoredRetryPolicy, "authoredRetryPolicy");
1758
+ function retryBudgetBaseAttempt(row) {
1759
+ const base = row.retry?.budgetBaseAttempt;
1760
+ return typeof base === "number" && Number.isSafeInteger(base) && base > 0 ? base : 0;
1761
+ }
1762
+ __name(retryBudgetBaseAttempt, "retryBudgetBaseAttempt");
1763
+ __name2(retryBudgetBaseAttempt, "retryBudgetBaseAttempt");
1764
+ function retryBudgetAttempt(row) {
1765
+ return Math.max(0, row.attempt - retryBudgetBaseAttempt(row));
1766
+ }
1767
+ __name(retryBudgetAttempt, "retryBudgetAttempt");
1768
+ __name2(retryBudgetAttempt, "retryBudgetAttempt");
1769
+ function retryBudgetMaxAttempts(row) {
1770
+ const max = row.retry?.maxAttempts;
1771
+ return typeof max === "number" && Number.isFinite(max) && max >= 1 ? max : 1;
1772
+ }
1773
+ __name(retryBudgetMaxAttempts, "retryBudgetMaxAttempts");
1774
+ __name2(retryBudgetMaxAttempts, "retryBudgetMaxAttempts");
1775
+ function retryBudgetRemaining(row) {
1776
+ return retryBudgetAttempt(row) < retryBudgetMaxAttempts(row);
1777
+ }
1778
+ __name(retryBudgetRemaining, "retryBudgetRemaining");
1779
+ __name2(retryBudgetRemaining, "retryBudgetRemaining");
1780
+ function retriesRemaining(row) {
1781
+ return Math.max(0, retryBudgetMaxAttempts(row) - retryBudgetAttempt(row));
1782
+ }
1783
+ __name(retriesRemaining, "retriesRemaining");
1784
+ __name2(retriesRemaining, "retriesRemaining");
1785
+ var WORKFLOW_JOB_RESOURCES = [
1786
+ "small",
1787
+ "medium",
1788
+ "large"
1789
+ ];
1790
+ var WORKFLOW_SIDE_EFFECTS = [
1791
+ "none",
1792
+ "external"
1793
+ ];
1794
+ var WORKFLOW_JOB_RANGES = Object.freeze({
1795
+ /** Coding-turn cap (`claude -p --max-turns`); absent ⇒ the cluster default (LUA_WF_JOB_MAX_TURNS). */
1796
+ maxTurns: Object.freeze({
1797
+ min: 1,
1798
+ max: 500
1799
+ }),
1800
+ /** Harness messages per attempt (one per content block); absent ⇒ the cluster default (400). */
1801
+ maxMessages: Object.freeze({
1802
+ min: 1,
1803
+ max: 5e3
1804
+ }),
1805
+ /** Input-side tokens per attempt (prompt + cache); absent ⇒ the cluster default (4M — LUA-708, was 30M). */
1806
+ maxInputTokens: Object.freeze({
1807
+ min: 1e6,
1808
+ max: 5e8
1809
+ })
1810
+ });
1811
+ var WORKFLOW_JOB_RANGE_MEMBERS = Object.keys(WORKFLOW_JOB_RANGES);
1812
+ function isWithinWorkflowJobRange(member, value3) {
1813
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
1814
+ return typeof value3 === "number" && Number.isInteger(value3) && value3 >= min && value3 <= max;
1815
+ }
1816
+ __name(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1817
+ __name2(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1818
+ function workflowJobRangeMessage(member) {
1819
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
1820
+ return `\`${member}\` must be an integer ${min}..${max}`;
1821
+ }
1822
+ __name(workflowJobRangeMessage, "workflowJobRangeMessage");
1823
+ __name2(workflowJobRangeMessage, "workflowJobRangeMessage");
1824
+ var WORKFLOW_SINGLE_STEP_TYPES = [
1825
+ "agent",
1826
+ "tool",
1827
+ "workflow",
1828
+ "step"
1829
+ ];
1830
+ var WORKFLOW_HITL_ENTRY_TYPES = [
1831
+ "approval",
1832
+ "waitForSignal"
1833
+ ];
1834
+ var WORKFLOW_ARM_ENTRY_TYPES = [
1835
+ ...WORKFLOW_SINGLE_STEP_TYPES,
1836
+ ...WORKFLOW_HITL_ENTRY_TYPES
1837
+ ];
1838
+ var WORKFLOW_HITL_ARM_CONTAINERS = [
1839
+ "parallel",
1840
+ "conditional",
1841
+ "foreach"
1842
+ ];
1843
+ function isWorkflowSingleStepType(type) {
1844
+ return typeof type === "string" && WORKFLOW_SINGLE_STEP_TYPES.includes(type);
1845
+ }
1846
+ __name(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1847
+ __name2(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1848
+ function isWorkflowHitlEntryType(type) {
1849
+ return typeof type === "string" && WORKFLOW_HITL_ENTRY_TYPES.includes(type);
1850
+ }
1851
+ __name(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1852
+ __name2(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1853
+ function isWorkflowArmEntryType(type) {
1854
+ return typeof type === "string" && WORKFLOW_ARM_ENTRY_TYPES.includes(type);
1855
+ }
1856
+ __name(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1857
+ __name2(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1858
+ function workflowContainerRunsHitlArm(container) {
1859
+ return typeof container === "string" && WORKFLOW_HITL_ARM_CONTAINERS.includes(container);
1860
+ }
1861
+ __name(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1862
+ __name2(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1863
+ function workflowHitlArmUnsupportedMessage(type, id, container) {
1864
+ const legal = WORKFLOW_HITL_ARM_CONTAINERS.map((c) => `\`${c}\``).join(" / ");
1865
+ 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\``;
1866
+ }
1867
+ __name(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1868
+ __name2(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1869
+ function workflowHitlArmShapeMessage(type, id, shape) {
1870
+ 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\``;
1871
+ }
1872
+ __name(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1873
+ __name2(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1874
+ var WORKFLOW_GRAPH_ENTRY_STEP_KINDS = Object.freeze({
1875
+ agent: "agent",
1876
+ tool: "tool",
1877
+ workflow: "subrun",
1878
+ step: "code",
1879
+ mapping: "map",
1880
+ sleep: "sleep",
1881
+ sleepUntil: "sleepUntil",
1882
+ parallel: null,
1883
+ conditional: "branch",
1884
+ foreach: "foreach",
1885
+ loop: "loop",
1886
+ approval: "approval",
1887
+ waitForSignal: "signal"
1888
+ });
1889
+ var WORKFLOW_ARM_ENTRY_STEP_KINDS = Object.freeze(Object.fromEntries(WORKFLOW_ARM_ENTRY_TYPES.map((t) => [
1890
+ t,
1891
+ WORKFLOW_GRAPH_ENTRY_STEP_KINDS[t]
1892
+ ])));
1893
+ var WORKFLOW_BUDGET_MAX_DURATION_SECONDS = Object.freeze({
1894
+ min: 60,
1895
+ max: 2592e3
1896
+ });
1623
1897
  var REDACTED_PLACEHOLDER = "[REDACTED]";
1624
1898
  var PROVIDER_MESSAGE_MAX_CHARS = 300;
1899
+ var ERROR_MESSAGE_MAX_CHARS = 2e3;
1625
1900
  var SECRET_LITERAL_PATTERNS = [
1626
1901
  {
1627
1902
  re: /\b(github_pat_)[A-Za-z0-9_]{16,}/g
@@ -1632,6 +1907,10 @@ var SECRET_LITERAL_PATTERNS = [
1632
1907
  {
1633
1908
  re: /\b(glpat-)[A-Za-z0-9_-]{16,}/g
1634
1909
  },
1910
+ // LUA-696 review (3): the npm granular / classic token (`npm_` + 36 alphanumerics).
1911
+ {
1912
+ re: /\b(npm_)[A-Za-z0-9]{36}\b/g
1913
+ },
1635
1914
  {
1636
1915
  re: /\b(sk-ant-)[A-Za-z0-9_-]{16,}/g
1637
1916
  },
@@ -1668,8 +1947,15 @@ var SECRET_PAIR_PATTERNS = [
1668
1947
  // `FOO_TOKEN=x`, `secretKey=x`, `password=x`. Groups: the char before the name, the name, the separator
1669
1948
  // (with its quotes), the scheme word — all kept; the value goes. A value a literal rule already replaced
1670
1949
  // (`x-access-token:[REDACTED]@host`) is left alone so the host after it survives.
1950
+ // LUA-696: the second lookahead keeps a scrubbed `Authorization: Bearer [REDACTED]` as it is — without it the
1951
+ // optional scheme group backtracks to empty and `Bearer` itself becomes the value (`[REDACTED] [REDACTED]`).
1952
+ // The scrub is applied more than once on purpose (the Job site at its copy, the outcome table at the row
1953
+ // write, lua-api on the way out), so it must be idempotent.
1954
+ // LUA-696 review (HIGH): the identifier prefix is BOUNDED (`{1,64}`) — unbounded, `[A-Za-z0-9-]+` made the
1955
+ // scan quadratic on `-`-heavy text (100 KB of `-` took 12.7 s on the lua-core event loop; a worker-tier step
1956
+ // can throw that). No identifier that names a credential is longer; the probe list is unchanged.
1671
1957
  {
1672
- re: new RegExp(`(^|[^A-Za-z0-9])((?:[A-Za-z0-9-]+[_-])?${SECRET_NAME}|[A-Za-z0-9-]+_key)(["']?\\s*[:=]\\s*["']?)((?:basic\\s+|bearer\\s+|token\\s+)?)(?!\\[REDACTED\\])[^\\s"',;)}&]{4,}`, "gi")
1958
+ 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")
1673
1959
  },
1674
1960
  // `?token=x`, `&key=x`, `&X-Amz-Signature=x`, `&sig=x`
1675
1961
  {
@@ -1696,6 +1982,34 @@ function groupCount(re) {
1696
1982
  }
1697
1983
  __name(groupCount, "groupCount");
1698
1984
  __name2(groupCount, "groupCount");
1985
+ 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;
1986
+ var WORKFLOW_RESERVED_SECRET_KEYS = Object.freeze([
1987
+ "secret",
1988
+ "token",
1989
+ "password",
1990
+ "passwd",
1991
+ "pwd",
1992
+ "passphrase",
1993
+ "api_key",
1994
+ "apikey",
1995
+ "access_key",
1996
+ "secret_key",
1997
+ "private_key",
1998
+ "client_secret",
1999
+ "authorization",
2000
+ "auth_token",
2001
+ "access_token",
2002
+ "id_token",
2003
+ "refresh_token",
2004
+ "session_key",
2005
+ "credential",
2006
+ "credentials"
2007
+ ]);
2008
+ function isWorkflowSecretKey(key) {
2009
+ return WORKFLOW_SECRET_KEY_RE.test(key);
2010
+ }
2011
+ __name(isWorkflowSecretKey, "isWorkflowSecretKey");
2012
+ __name2(isWorkflowSecretKey, "isWorkflowSecretKey");
1699
2013
  function applyPatterns(text, patterns) {
1700
2014
  let out = text;
1701
2015
  for (const { re, suffix } of patterns) {
@@ -1722,8 +2036,19 @@ function scrubSecretText(text) {
1722
2036
  }
1723
2037
  __name(scrubSecretText, "scrubSecretText");
1724
2038
  __name2(scrubSecretText, "scrubSecretText");
2039
+ var SCRUB_INPUT_MAX_CHARS = 16 * 1024;
2040
+ var SCRUB_CUT_BACKOFF_CHARS = 256;
2041
+ function boundScrubInput(text, max = SCRUB_INPUT_MAX_CHARS) {
2042
+ if (typeof text !== "string" || text.length <= max) return text;
2043
+ const window = text.slice(Math.max(0, max - SCRUB_CUT_BACKOFF_CHARS), max);
2044
+ const ws = window.search(/\s\S*$/);
2045
+ const cut = ws >= 0 ? max - window.length + ws : max;
2046
+ return `${text.slice(0, cut)}\u2026`;
2047
+ }
2048
+ __name(boundScrubInput, "boundScrubInput");
2049
+ __name2(boundScrubInput, "boundScrubInput");
1725
2050
  function scrubSecretLines(lines) {
1726
- return lines.map((l) => typeof l === "string" ? scrubSecretText(l) : l);
2051
+ return lines.map((l) => typeof l === "string" ? scrubSecretText(boundScrubInput(l)) : l);
1727
2052
  }
1728
2053
  __name(scrubSecretLines, "scrubSecretLines");
1729
2054
  __name2(scrubSecretLines, "scrubSecretLines");
@@ -1744,13 +2069,18 @@ function messageText(value3) {
1744
2069
  __name(messageText, "messageText");
1745
2070
  __name2(messageText, "messageText");
1746
2071
  function scrubProviderMessage(raw, max = PROVIDER_MESSAGE_MAX_CHARS) {
1747
- const text = messageText(raw).replace(/\s+/g, " ").trim();
2072
+ const text = boundScrubInput(messageText(raw)).replace(/\s+/g, " ").trim();
1748
2073
  if (!text) return void 0;
1749
2074
  const out = scrubSecretText(text);
1750
2075
  return out.length > max ? `${out.slice(0, max - 1)}\u2026` : out;
1751
2076
  }
1752
2077
  __name(scrubProviderMessage, "scrubProviderMessage");
1753
2078
  __name2(scrubProviderMessage, "scrubProviderMessage");
2079
+ function scrubStepErrorMessage(raw) {
2080
+ return scrubProviderMessage(raw, ERROR_MESSAGE_MAX_CHARS);
2081
+ }
2082
+ __name(scrubStepErrorMessage, "scrubStepErrorMessage");
2083
+ __name2(scrubStepErrorMessage, "scrubStepErrorMessage");
1754
2084
  var WORKFLOW_AUDIT_EVENTS = [
1755
2085
  // --- definitions / versions / templates (13, 11 §11.11.5) ---
1756
2086
  "workflow.published",
@@ -1817,6 +2147,8 @@ var WORKFLOW_AUDIT_EVENTS = [
1817
2147
  "workflow.goal.resumed",
1818
2148
  "workflow.goal.done",
1819
2149
  "workflow.goal.closed",
2150
+ // LUA-760: an ended goal's cadence Job retired (deleted) — inline on done / closed, by R21 / R28, or by sweep #30
2151
+ "workflow.goal.job_retired",
1820
2152
  // --- org policy (02 §2.10 / 09 R23) ---
1821
2153
  "workflow.policy.retention_changed",
1822
2154
  "workflow.policy.pacing_changed",
@@ -1960,475 +2292,940 @@ ${items.join("\n\n")}`;
1960
2292
  }
1961
2293
  __name(renderTargetsBlock, "renderTargetsBlock");
1962
2294
  __name2(renderTargetsBlock, "renderTargetsBlock");
2295
+ var WORKFLOW_APPROVAL_OUTPUT_DECISIONS = [
2296
+ "approved",
2297
+ "denied",
2298
+ "timed_out"
2299
+ ];
2300
+ var WORKFLOW_APPROVAL_OUTPUT_SCHEMA = {
2301
+ type: "object",
2302
+ properties: {
2303
+ approved: {
2304
+ type: "boolean"
2305
+ },
2306
+ decision: {
2307
+ type: "string",
2308
+ enum: [
2309
+ ...WORKFLOW_APPROVAL_OUTPUT_DECISIONS
2310
+ ]
2311
+ },
2312
+ /** the approver's note when given, else the decision word — what `${stepResults.<id>.text}` reads */
2313
+ text: {
2314
+ type: "string"
2315
+ },
2316
+ note: {
2317
+ type: "string"
2318
+ },
2319
+ editedPayload: {},
2320
+ editRevision: {
2321
+ type: "integer"
2322
+ },
2323
+ decidedBy: {
2324
+ type: "object",
2325
+ properties: {
2326
+ id: {
2327
+ type: "string"
2328
+ },
2329
+ kind: {
2330
+ type: "string"
2331
+ }
2332
+ }
2333
+ },
2334
+ timedOut: {
2335
+ type: "boolean"
2336
+ },
2337
+ escalations: {
2338
+ type: "integer"
2339
+ },
2340
+ evidence: {
2341
+ type: "array",
2342
+ items: {
2343
+ type: "string"
2344
+ }
2345
+ },
2346
+ items: {
2347
+ type: "array",
2348
+ items: {
2349
+ type: "object"
2350
+ }
2351
+ }
2352
+ },
2353
+ required: [
2354
+ "approved",
2355
+ "decision",
2356
+ "text"
2357
+ ]
2358
+ };
2359
+ function workflowApprovalDecisionOf(resumeData) {
2360
+ if (resumeData.timedOut === true) return "timed_out";
2361
+ return resumeData.approved === true ? "approved" : "denied";
2362
+ }
2363
+ __name(workflowApprovalDecisionOf, "workflowApprovalDecisionOf");
2364
+ __name2(workflowApprovalDecisionOf, "workflowApprovalDecisionOf");
2365
+ function workflowApprovalOutput(resumeData) {
2366
+ const decision = typeof resumeData.decision === "string" ? resumeData.decision : workflowApprovalDecisionOf(resumeData);
2367
+ const note = typeof resumeData.note === "string" && resumeData.note.trim() !== "" ? resumeData.note : void 0;
2368
+ const text = typeof resumeData.text === "string" ? resumeData.text : note ?? decision;
2369
+ return {
2370
+ ...resumeData,
2371
+ decision,
2372
+ text
2373
+ };
2374
+ }
2375
+ __name(workflowApprovalOutput, "workflowApprovalOutput");
2376
+ __name2(workflowApprovalOutput, "workflowApprovalOutput");
2377
+ function isWorkflowApprovalOutput(value3) {
2378
+ if (typeof value3 !== "object" || value3 === null || Array.isArray(value3)) return false;
2379
+ const v = value3;
2380
+ return typeof v.approved === "boolean" && WORKFLOW_APPROVAL_OUTPUT_DECISIONS.includes(v.decision) && typeof v.text === "string";
2381
+ }
2382
+ __name(isWorkflowApprovalOutput, "isWorkflowApprovalOutput");
2383
+ __name2(isWorkflowApprovalOutput, "isWorkflowApprovalOutput");
2384
+ var JSON_FENCE_RE = /```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```/g;
2385
+ function extractSingleJsonValue(text) {
2386
+ const trimmed = (text ?? "").trim();
2387
+ if (!trimmed) return {
2388
+ reason: "reply is empty"
2389
+ };
2390
+ const fenced = [
2391
+ ...trimmed.matchAll(JSON_FENCE_RE)
2392
+ ];
2393
+ if (fenced.length > 1) return {
2394
+ reason: "reply carries more than one fenced block"
2395
+ };
2396
+ const candidate = fenced.length === 1 ? fenced[0][1].trim() : trimmed;
2397
+ try {
2398
+ return {
2399
+ value: JSON.parse(candidate)
2400
+ };
2401
+ } catch {
2402
+ if (fenced.length === 1) return {
2403
+ reason: "fenced block is not valid JSON"
2404
+ };
2405
+ }
2406
+ const opens = [
2407
+ trimmed.indexOf("{"),
2408
+ trimmed.indexOf("[")
2409
+ ].filter((i) => i !== -1);
2410
+ const start = opens.length ? Math.min(...opens) : -1;
2411
+ const end = Math.max(trimmed.lastIndexOf("}"), trimmed.lastIndexOf("]"));
2412
+ if (start === -1 || end <= start) return {
2413
+ reason: "reply is not JSON"
2414
+ };
2415
+ try {
2416
+ return {
2417
+ value: JSON.parse(trimmed.slice(start, end + 1))
2418
+ };
2419
+ } catch {
2420
+ return {
2421
+ reason: "reply does not contain a single JSON value"
2422
+ };
2423
+ }
2424
+ }
2425
+ __name(extractSingleJsonValue, "extractSingleJsonValue");
2426
+ __name2(extractSingleJsonValue, "extractSingleJsonValue");
1963
2427
 
1964
2428
  // ../workflow-graph/dist/index.mjs
2429
+ import { createHash } from "crypto";
1965
2430
  import { z as z4 } from "zod";
1966
2431
  import { z as z22 } from "zod";
1967
2432
  import { createHash as createHash2 } from "crypto";
1968
2433
  var __defProp3 = Object.defineProperty;
1969
2434
  var __name3 = /* @__PURE__ */ __name((target, value22) => __defProp3(target, "name", { value: value22, configurable: true }), "__name");
1970
- var WORKFLOW_SIDE_EFFECTS = [
1971
- "none",
1972
- "external"
1973
- ];
1974
- var WORKFLOW_JOB_RESOURCES = [
1975
- "small",
1976
- "medium",
1977
- "large"
1978
- ];
1979
- var SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
1980
- var JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
1981
- var WORKFLOW_ARM_SUBRUN_ID = "$arm";
1982
- var SLEEP_UNTIL_REPLACEMENT = Object.freeze({
1983
- type: "sleep",
1984
- duration: 6e4
1985
- });
1986
- function sleepUntilUnsupportedMessage(id) {
1987
- 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} }`;
2435
+ var WorkflowTemplateError = class extends Error {
2436
+ static {
2437
+ __name(this, "WorkflowTemplateError");
2438
+ }
2439
+ static {
2440
+ __name3(this, "WorkflowTemplateError");
2441
+ }
2442
+ placeholder;
2443
+ constructor(message, placeholder) {
2444
+ super(message), this.placeholder = placeholder;
2445
+ this.name = "WorkflowTemplateError";
2446
+ }
2447
+ };
2448
+ function isMapConfigObject(v) {
2449
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1988
2450
  }
1989
- __name(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
1990
- __name3(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
1991
- var WORKFLOW_CAPS_DEFAULT = Object.freeze({
1992
- maxParallelArms: 16,
1993
- maxForeachConcurrency: 16,
1994
- maxForeachItems: 2e4,
1995
- maxNodes: 200,
1996
- maxNestingDepth: 3,
1997
- maxForeachRatePerSecond: 50,
1998
- maxLoopIntervalSeconds: 86400,
1999
- maxWorkerTimeoutSeconds: 600,
2000
- maxJobTimeoutSeconds: 86400,
2001
- maxJobSegmentSeconds: 14400
2002
- });
2003
- var WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS = 300;
2004
- var WORKFLOW_AGENT_DEFAULT_TIMEOUT_SECONDS = 600;
2005
- var WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS = 3600;
2006
- var WORKFLOW_FOREACH_DEFAULT_CONCURRENCY = 4;
2007
- var WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS = 256;
2008
- var WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS = 100;
2009
- var WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS = 168;
2010
- var WORKFLOW_SIGNAL_DEFAULT_SOURCES = [
2011
- "webhook",
2012
- "api",
2013
- "user"
2451
+ __name(isMapConfigObject, "isMapConfigObject");
2452
+ __name3(isMapConfigObject, "isMapConfigObject");
2453
+ function parseMapConfig(raw, stepId) {
2454
+ if (isMapConfigObject(raw)) return raw;
2455
+ if (typeof raw !== "string") {
2456
+ throw new Error(`Stored mapping step "${stepId}" has a mapConfig that is neither a JSON string nor an object.`);
2457
+ }
2458
+ try {
2459
+ return JSON.parse(raw);
2460
+ } catch (e) {
2461
+ throw new Error(`Stored mapping step "${stepId}" has invalid JSON mapConfig: ${e.message}`);
2462
+ }
2463
+ }
2464
+ __name(parseMapConfig, "parseMapConfig");
2465
+ __name3(parseMapConfig, "parseMapConfig");
2466
+ function mapConfigWire(raw) {
2467
+ if (typeof raw === "string") return raw;
2468
+ if (isMapConfigObject(raw)) return canonicalJson(raw);
2469
+ return void 0;
2470
+ }
2471
+ __name(mapConfigWire, "mapConfigWire");
2472
+ __name3(mapConfigWire, "mapConfigWire");
2473
+ var TEMPLATE_PLACEHOLDER = /\$\{([^}]*)\}/g;
2474
+ var TEMPLATE_NAMESPACES = [
2475
+ "initData",
2476
+ "state",
2477
+ "requestContext",
2478
+ "stepResults"
2014
2479
  ];
2015
- var clone = /* @__PURE__ */ __name3((v) => JSON.parse(JSON.stringify(v)), "clone");
2016
- function fillPolicy(node, defaultTimeout) {
2017
- if (node.tier === void 0 && node.workspace !== void 0 && node.workspace !== "inherit") node.tier = "job";
2018
- if (node.timeoutSeconds === void 0) node.timeoutSeconds = node.tier === "job" ? WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS : defaultTimeout;
2019
- if (node.retry === void 0) node.retry = {
2020
- maxAttempts: 1
2480
+ function describeBadPlaceholder(template22, idx, rawExpr) {
2481
+ return `Template placeholder #${idx} (\${${rawExpr}}) in '${template22}'`;
2482
+ }
2483
+ __name(describeBadPlaceholder, "describeBadPlaceholder");
2484
+ __name3(describeBadPlaceholder, "describeBadPlaceholder");
2485
+ function parseTemplatePlaceholder(rawExpr) {
2486
+ const dot = rawExpr.indexOf(".");
2487
+ return {
2488
+ scope: dot === -1 ? rawExpr : rawExpr.slice(0, dot),
2489
+ rest: dot === -1 ? "" : rawExpr.slice(dot + 1)
2021
2490
  };
2022
- if (node.onError === void 0) node.onError = "fail";
2023
- if ((node.type === "step" || node.type === "tool") && node.sideEffects === void 0) node.sideEffects = "none";
2024
2491
  }
2025
- __name(fillPolicy, "fillPolicy");
2026
- __name3(fillPolicy, "fillPolicy");
2027
- function fillSingle(node) {
2028
- switch (node.type) {
2029
- case "step": {
2030
- fillPolicy(node, WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS);
2031
- const s = node;
2032
- if (s.resumeTimeoutHours === void 0) s.resumeTimeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2033
- if (s.onSuspendTimeout === void 0) s.onSuspendTimeout = "fail";
2034
- return;
2492
+ __name(parseTemplatePlaceholder, "parseTemplatePlaceholder");
2493
+ __name3(parseTemplatePlaceholder, "parseTemplatePlaceholder");
2494
+ function traverseMappingPath(root, path, errorLabel) {
2495
+ if (path === "" || path === ".") return root;
2496
+ const parts = path.split(".");
2497
+ let value22 = root;
2498
+ for (const part of parts) {
2499
+ if (typeof value22 === "object" && value22 !== null) value22 = value22[part];
2500
+ else throw new WorkflowTemplateError(`Invalid path ${path} in ${errorLabel}`, path);
2501
+ }
2502
+ return value22;
2503
+ }
2504
+ __name(traverseMappingPath, "traverseMappingPath");
2505
+ __name3(traverseMappingPath, "traverseMappingPath");
2506
+ function stringifyTemplateValue(v, template22, idx, rawExpr) {
2507
+ if (v === null || v === void 0) return "";
2508
+ if (typeof v === "object") {
2509
+ try {
2510
+ return JSON.stringify(v);
2511
+ } catch (err) {
2512
+ throw new WorkflowTemplateError(`${describeBadPlaceholder(template22, idx, rawExpr)} resolved to a value that could not be JSON-stringified (${err.message}).`, rawExpr);
2035
2513
  }
2036
- case "agent":
2037
- fillPolicy(node, WORKFLOW_AGENT_DEFAULT_TIMEOUT_SECONDS);
2038
- return;
2039
- case "tool":
2040
- fillPolicy(node, WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS);
2041
- return;
2042
- case "workflow":
2043
- if (node.workflowId === WORKFLOW_ARM_SUBRUN_ID && Array.isArray(node.graph) && node.graph[1]) fillSingle(node.graph[1]);
2044
- return;
2045
2514
  }
2515
+ return String(v);
2046
2516
  }
2047
- __name(fillSingle, "fillSingle");
2048
- __name3(fillSingle, "fillSingle");
2049
- function fillArm(arm) {
2050
- if (arm.type !== "mapping") fillSingle(arm);
2517
+ __name(stringifyTemplateValue, "stringifyTemplateValue");
2518
+ __name3(stringifyTemplateValue, "stringifyTemplateValue");
2519
+ function escapeFence(content) {
2520
+ return content.replace(/<\/lua-data/g, "<\\/lua-data");
2051
2521
  }
2052
- __name(fillArm, "fillArm");
2053
- __name3(fillArm, "fillArm");
2054
- function fillEntry(entry) {
2055
- switch (entry.type) {
2056
- case "step":
2057
- case "agent":
2058
- case "tool":
2059
- case "workflow":
2060
- fillSingle(entry);
2061
- return;
2062
- case "parallel":
2063
- entry.steps.forEach(fillSingle);
2064
- return;
2065
- case "conditional": {
2066
- const c = entry;
2067
- if (c.exclusive === void 0) c.exclusive = false;
2068
- c.steps.forEach(fillArm);
2069
- if (c.otherwise) fillArm(c.otherwise);
2070
- return;
2071
- }
2072
- case "foreach": {
2073
- const f = entry;
2074
- f.opts = f.opts ?? {};
2075
- if (f.opts.concurrency === void 0) f.opts.concurrency = WORKFLOW_FOREACH_DEFAULT_CONCURRENCY;
2076
- if (f.opts.maxItems === void 0) f.opts.maxItems = WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2077
- fillSingle(f.step);
2078
- return;
2079
- }
2080
- case "loop": {
2081
- const l = entry;
2082
- if (l.maxIterations === void 0) l.maxIterations = WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
2083
- fillSingle(l.step);
2084
- return;
2085
- }
2086
- case "approval": {
2087
- const a = entry;
2088
- if (a.approver === void 0) a.approver = "creator";
2089
- if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2090
- if (a.onTimeout === void 0) a.onTimeout = "deny";
2091
- if (a.onDeny === void 0) a.onDeny = "continue";
2092
- if (a.excludeInitiator === void 0) a.excludeInitiator = false;
2093
- if (a.editable === void 0) a.editable = false;
2094
- return;
2095
- }
2096
- case "waitForSignal": {
2097
- const w = entry;
2098
- if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2099
- if (w.onTimeout === void 0) w.onTimeout = "fail";
2100
- if (w.acceptedSources === void 0) w.acceptedSources = [
2101
- ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
2102
- ];
2103
- return;
2104
- }
2105
- case "mapping":
2106
- case "sleep":
2107
- case "sleepUntil":
2108
- return;
2109
- }
2110
- }
2111
- __name(fillEntry, "fillEntry");
2112
- __name3(fillEntry, "fillEntry");
2113
- function withDefaultsFilled(g) {
2114
- const out = clone(g);
2115
- out.definition.graph.forEach(fillEntry);
2116
- return out;
2522
+ __name(escapeFence, "escapeFence");
2523
+ __name3(escapeFence, "escapeFence");
2524
+ function fenceBlock(name, source, content) {
2525
+ return `<lua-data name="${name}" source="${source}" untrusted="true">${escapeFence(content)}</lua-data>`;
2117
2526
  }
2118
- __name(withDefaultsFilled, "withDefaultsFilled");
2119
- __name3(withDefaultsFilled, "withDefaultsFilled");
2120
- var CONNECTION_ID_HEX_RE = /^[0-9a-f]{24}$/;
2121
- function isConnectionKeyShaped(value22) {
2122
- return WORKFLOW_CONNECTION_KEY_RE.test(value22) && !CONNECTION_ID_HEX_RE.test(value22);
2527
+ __name(fenceBlock, "fenceBlock");
2528
+ __name3(fenceBlock, "fenceBlock");
2529
+ function renderTemplate(template22, ctx, opts) {
2530
+ let idx = 0;
2531
+ return template22.replace(TEMPLATE_PLACEHOLDER, (_match, rawExpr) => {
2532
+ idx += 1;
2533
+ const { scope, rest } = parseTemplatePlaceholder(rawExpr);
2534
+ const label = describeBadPlaceholder(template22, idx, rawExpr);
2535
+ let rendered;
2536
+ let source;
2537
+ switch (scope) {
2538
+ case "initData":
2539
+ rendered = stringifyTemplateValue(traverseMappingPath(ctx.initData, rest, label), template22, idx, rawExpr);
2540
+ source = "initData";
2541
+ break;
2542
+ case "state":
2543
+ rendered = stringifyTemplateValue(traverseMappingPath(ctx.state, rest, label), template22, idx, rawExpr);
2544
+ source = "state";
2545
+ break;
2546
+ case "requestContext":
2547
+ rendered = stringifyTemplateValue(traverseMappingPath(ctx.requestContext, rest, label), template22, idx, rawExpr);
2548
+ source = "requestContext";
2549
+ break;
2550
+ case "stepResults": {
2551
+ const innerDot = rest.indexOf(".");
2552
+ const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
2553
+ const subPath = innerDot === -1 ? "" : rest.slice(innerDot + 1);
2554
+ if (!stepId) throw new WorkflowTemplateError(`${label} must name a step: \${stepResults.<stepId>.<path>}.`, rawExpr);
2555
+ if (!(stepId in ctx.stepResults) || ctx.stepResults[stepId] == null) {
2556
+ throw new WorkflowTemplateError(`${label} references stepResults.${stepId} but step "${stepId}" has no resolvable output (not an ancestor, not run, failed, or produced no output).`, rawExpr);
2557
+ }
2558
+ rendered = stringifyTemplateValue(traverseMappingPath(ctx.stepResults[stepId], subPath, label), template22, idx, rawExpr);
2559
+ source = `step:${stepId}`;
2560
+ break;
2561
+ }
2562
+ default:
2563
+ throw new WorkflowTemplateError(`${label} references unknown namespace "${scope}". Use one of: ${TEMPLATE_NAMESPACES.join(", ")}.`, rawExpr);
2564
+ }
2565
+ return opts.fenced ? fenceBlock(rawExpr, source, rendered) : rendered;
2566
+ });
2123
2567
  }
2124
- __name(isConnectionKeyShaped, "isConnectionKeyShaped");
2125
- __name3(isConnectionKeyShaped, "isConnectionKeyShaped");
2126
- function connectionKeyUndeclaredMessage(path, key) {
2127
- 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`;
2568
+ __name(renderTemplate, "renderTemplate");
2569
+ __name3(renderTemplate, "renderTemplate");
2570
+ function isMapDescriptor(v) {
2571
+ if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
2572
+ const d = v;
2573
+ const keys = Object.keys(d);
2574
+ const only = /* @__PURE__ */ __name3((...allowed) => keys.every((k) => allowed.includes(k)), "only");
2575
+ if ("value" in d) return keys.length === 1;
2576
+ if ("template" in d) return keys.length === 1 && typeof d.template === "string";
2577
+ if ("requestContextPath" in d) return keys.length === 1 && typeof d.requestContextPath === "string";
2578
+ if ("knowledge" in d) return keys.length === 1 && typeof d.knowledge === "object" && d.knowledge !== null;
2579
+ if ("initData" in d) return d.initData === true && typeof d.path === "string" && only("initData", "path");
2580
+ if ("step" in d) {
2581
+ const stepOk = typeof d.step === "string" || Array.isArray(d.step) && d.step.every((x) => typeof x === "string");
2582
+ return stepOk && typeof d.path === "string" && only("step", "path", "rows");
2583
+ }
2584
+ return false;
2128
2585
  }
2129
- __name(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
2130
- __name3(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
2131
- var WORKFLOW_JOB_TOOLS = [
2132
- "shell",
2133
- "read",
2134
- "write",
2135
- "edit",
2136
- "glob",
2137
- "grep",
2138
- "git",
2139
- "gh",
2140
- "fetch"
2586
+ __name(isMapDescriptor, "isMapDescriptor");
2587
+ __name3(isMapDescriptor, "isMapDescriptor");
2588
+ var MAP_DESCRIPTOR_KEYS = [
2589
+ "step",
2590
+ "path",
2591
+ "initData",
2592
+ "value",
2593
+ "template",
2594
+ "requestContextPath",
2595
+ "knowledge"
2141
2596
  ];
2142
- var WORKFLOW_JOB_MAX_WORKTREE_ARMS = 8;
2143
- function classifyModelProvider(model) {
2144
- const m = (model ?? "").trim().toLowerCase();
2145
- if (!m) return null;
2146
- if (/^(anthropic\/|claude)/.test(m)) return "anthropic";
2147
- if (/^(openai\/|gpt-|o[1-9](-|$)|chatgpt)/.test(m)) return "openai";
2148
- if (/^(google\/|gemini)/.test(m)) return "google";
2149
- return null;
2150
- }
2151
- __name(classifyModelProvider, "classifyModelProvider");
2152
- __name3(classifyModelProvider, "classifyModelProvider");
2153
- var workspaceOf = /* @__PURE__ */ __name3((node) => node.workspace, "workspaceOf");
2154
- var mountsWorkspace = /* @__PURE__ */ __name3((node) => {
2155
- const w = workspaceOf(node);
2156
- return w !== void 0 && w !== "inherit";
2157
- }, "mountsWorkspace");
2158
- var isJobTier = /* @__PURE__ */ __name3((node) => node.tier === "job" || mountsWorkspace(node), "isJobTier");
2159
- var jobToolsOf = /* @__PURE__ */ __name3((node) => {
2160
- if (node.type === "agent") return node.toolScope?.jobTools;
2161
- return node.jobTools;
2162
- }, "jobToolsOf");
2163
- function schemaAtPath(schema, path) {
2164
- let cur = schema;
2165
- if (!cur || typeof cur !== "object") return void 0;
2166
- for (const seg of path.split(".").filter(Boolean)) {
2167
- const props = cur.properties;
2168
- const next = props?.[seg];
2169
- if (!next || typeof next !== "object") return void 0;
2170
- cur = next;
2597
+ var MAP_MEMBER_MALFORMED_CODE = "map-member-malformed";
2598
+ function malformedMapMembers(cfg) {
2599
+ if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) return [];
2600
+ const out = [];
2601
+ for (const [member, v] of Object.entries(cfg)) {
2602
+ if (!v || typeof v !== "object" || Array.isArray(v) || isMapDescriptor(v)) continue;
2603
+ const keys = Object.keys(v).filter((k) => MAP_DESCRIPTOR_KEYS.includes(k));
2604
+ if (keys.length > 0) out.push({
2605
+ member,
2606
+ keys
2607
+ });
2171
2608
  }
2172
- return cur;
2609
+ return out;
2173
2610
  }
2174
- __name(schemaAtPath, "schemaAtPath");
2175
- __name3(schemaAtPath, "schemaAtPath");
2176
- var schemaIsArray = /* @__PURE__ */ __name3((schema) => {
2177
- if (!schema) return void 0;
2178
- const t = schema.type;
2179
- if (t === void 0) return void 0;
2180
- return Array.isArray(t) ? t.includes("array") : t === "array";
2181
- }, "schemaIsArray");
2182
- var singleId = /* @__PURE__ */ __name3((s) => s.type === "step" ? s.step.id : s.id, "singleId");
2183
- var armId = /* @__PURE__ */ __name3((a) => a.type === "mapping" ? a.id : singleId(a), "armId");
2184
- var TEMPLATE_STEP_REF = /\$\{\s*stepResults\.([A-Za-z0-9_\-]+)/g;
2185
- function templateStepRefs(text) {
2186
- const ids = [];
2187
- for (const m of text.matchAll(TEMPLATE_STEP_REF)) ids.push(m[1]);
2188
- return ids;
2611
+ __name(malformedMapMembers, "malformedMapMembers");
2612
+ __name3(malformedMapMembers, "malformedMapMembers");
2613
+ function mapMemberMalformedMessage(id, m) {
2614
+ const keys = m.keys.map((k) => `\`${k}\``).join(", ");
2615
+ return `"${id}".${m.member} carries descriptor key${m.keys.length === 1 ? "" : "s"} ${keys} but is not an exact binding form ({initData:true, path} | {step, path[, rows]} | {value} | {template} | {requestContextPath} | {knowledge}) \u2014 it is passed to the step verbatim as a literal; fix the descriptor, or wrap it in {value: \u2026} if the literal is intended`;
2189
2616
  }
2190
- __name(templateStepRefs, "templateStepRefs");
2191
- __name3(templateStepRefs, "templateStepRefs");
2192
- function mapConfigStepRefs(raw) {
2193
- if (!raw) return [];
2194
- let cfg;
2617
+ __name(mapMemberMalformedMessage, "mapMemberMalformedMessage");
2618
+ __name3(mapMemberMalformedMessage, "mapMemberMalformedMessage");
2619
+ function resolveDescriptor(key, m, ctx) {
2620
+ if (!isMapDescriptor(m)) return {
2621
+ value: m
2622
+ };
2195
2623
  try {
2196
- cfg = typeof raw === "string" ? JSON.parse(raw) : raw;
2197
- } catch {
2198
- return [];
2199
- }
2200
- const ids = [];
2201
- for (const d of Object.values(cfg)) {
2202
- if (!d || typeof d !== "object") continue;
2203
- const desc = d;
2204
- if (desc.step !== void 0) ids.push(...Array.isArray(desc.step) ? desc.step : [
2205
- desc.step
2206
- ]);
2207
- if (typeof desc.template === "string") ids.push(...templateStepRefs(desc.template));
2208
- }
2209
- return ids;
2210
- }
2211
- __name(mapConfigStepRefs, "mapConfigStepRefs");
2212
- __name3(mapConfigStepRefs, "mapConfigStepRefs");
2213
- function nodeStepRefs(entry) {
2214
- switch (entry.type) {
2215
- case "agent": {
2216
- const a = entry;
2217
- const p = a.promptTemplate;
2218
- return typeof p === "string" ? templateStepRefs(p) : p && "template" in p ? templateStepRefs(p.template) : [];
2624
+ if ("value" in m) return {
2625
+ value: m.value
2626
+ };
2627
+ if ("template" in m && typeof m.template === "string") {
2628
+ return {
2629
+ value: renderTemplate(m.template, ctx, {
2630
+ fenced: false
2631
+ })
2632
+ };
2219
2633
  }
2220
- case "tool":
2221
- return mapConfigStepRefs(entry.input);
2222
- case "workflow":
2223
- return mapConfigStepRefs(entry.input);
2224
- case "mapping":
2225
- return mapConfigStepRefs(entry.mapConfig);
2226
- case "approval": {
2227
- const a = entry;
2228
- return typeof a.details === "string" ? templateStepRefs(a.details) : [];
2634
+ if ("knowledge" in m || "rows" in m && m.rows !== void 0) {
2635
+ return {
2636
+ error: "binding_unresolved",
2637
+ key
2638
+ };
2229
2639
  }
2230
- default:
2231
- return [];
2640
+ if ("requestContextPath" in m) {
2641
+ const label = `requestContext path for key "${key}"`;
2642
+ return {
2643
+ value: traverseMappingPath(ctx.requestContext, m.requestContextPath, label)
2644
+ };
2645
+ }
2646
+ if ("path" in m) {
2647
+ const source = "initData" in m && m.initData ? "initData" : "step";
2648
+ if (source === "initData") {
2649
+ return {
2650
+ value: traverseMappingPath(ctx.initData, m.path, `initData for key "${key}"`)
2651
+ };
2652
+ }
2653
+ const stepRef = m.step;
2654
+ const candidates = Array.isArray(stepRef) ? stepRef : [
2655
+ stepRef
2656
+ ];
2657
+ const stepId = candidates.find((s) => ctx.stepResults[s] !== void 0 && ctx.stepResults[s] !== null);
2658
+ if (stepId === void 0) return {
2659
+ error: "binding_unresolved",
2660
+ key
2661
+ };
2662
+ return {
2663
+ value: traverseMappingPath(ctx.stepResults[stepId], m.path, `step ${candidates.join("|")} for key "${key}"`)
2664
+ };
2665
+ }
2666
+ return {
2667
+ error: "binding_unresolved",
2668
+ key
2669
+ };
2670
+ } catch (err) {
2671
+ if (err instanceof WorkflowTemplateError) return {
2672
+ error: "binding_unresolved",
2673
+ key
2674
+ };
2675
+ throw err;
2232
2676
  }
2233
2677
  }
2234
- __name(nodeStepRefs, "nodeStepRefs");
2235
- __name3(nodeStepRefs, "nodeStepRefs");
2236
- function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2237
- static: true
2238
- }) {
2239
- const issues = [];
2240
- const err = /* @__PURE__ */ __name3((code, message, path, stepId) => {
2241
- issues.push({
2242
- code,
2243
- message,
2244
- severity: "error",
2245
- path,
2246
- stepId
2247
- });
2248
- }, "err");
2249
- const warn = /* @__PURE__ */ __name3((code, message, path, stepId) => {
2250
- issues.push({
2251
- code,
2252
- message,
2253
- severity: "warning",
2254
- path,
2255
- stepId
2256
- });
2257
- }, "warn");
2258
- const graph = g?.definition?.graph;
2259
- if (!Array.isArray(graph)) {
2260
- err("invalid-envelope", "`definition.graph` must be an array", "definition.graph");
2261
- return issues;
2262
- }
2263
- if (graph.length === 0) {
2264
- err("empty-graph", "a workflow needs at least one entry", "definition.graph");
2265
- return issues;
2266
- }
2267
- if (!opts.static && g.concurrencyPolicy !== void 0) {
2268
- err("static-only-field", "`concurrencyPolicy` is a static-only envelope field (03 \xA73.4.2)", "concurrencyPolicy");
2269
- }
2270
- const envelopeWorkspace = g.workspace;
2271
- if (envelopeWorkspace?.backend && envelopeWorkspace.backend !== "ebs" && opts.policy?.workspaceBackends && !opts.policy.workspaceBackends.includes(envelopeWorkspace.backend)) {
2272
- 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");
2678
+ __name(resolveDescriptor, "resolveDescriptor");
2679
+ __name3(resolveDescriptor, "resolveDescriptor");
2680
+ function resolveMapping(cfg, ctx) {
2681
+ const keys = Object.keys(cfg);
2682
+ if (keys.length === 1 && keys[0] === "") {
2683
+ return resolveDescriptor("", cfg[""], ctx);
2273
2684
  }
2274
- const declaredKeys = new Set(opts.connectionKeys ?? []);
2275
- if (g.connections !== void 0 && !Array.isArray(g.connections)) {
2276
- err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
2685
+ const result = {};
2686
+ for (const key of keys) {
2687
+ const resolved = resolveDescriptor(key, cfg[key], ctx);
2688
+ if ("error" in resolved) return resolved;
2689
+ result[key] = resolved.value;
2277
2690
  }
2278
- (Array.isArray(g.connections) ? g.connections : []).forEach((c, i) => {
2279
- const path = `connections.${i}`;
2280
- const key = c?.key;
2281
- const integrationType = c?.integrationType;
2282
- if (typeof key !== "string" || !WORKFLOW_CONNECTION_KEY_RE.test(key)) {
2283
- err("connection-declaration-invalid", `connections[${i}].key must match ${WORKFLOW_CONNECTION_KEY_RE}`, `${path}.key`);
2691
+ return {
2692
+ value: result
2693
+ };
2694
+ }
2695
+ __name(resolveMapping, "resolveMapping");
2696
+ __name3(resolveMapping, "resolveMapping");
2697
+ var fromInit = /* @__PURE__ */ __name3((path) => ({
2698
+ initData: true,
2699
+ path
2700
+ }), "fromInit");
2701
+ var fromStep = /* @__PURE__ */ __name3((s, path = "") => {
2702
+ const idOf = /* @__PURE__ */ __name3((x) => typeof x === "string" ? x : x.id, "idOf");
2703
+ return {
2704
+ step: Array.isArray(s) ? s.map(idOf) : idOf(s),
2705
+ path
2706
+ };
2707
+ }, "fromStep");
2708
+ var value = /* @__PURE__ */ __name3((v) => ({
2709
+ value: v
2710
+ }), "value");
2711
+ var template = /* @__PURE__ */ __name3((s) => ({
2712
+ template: s
2713
+ }), "template");
2714
+ var fromRequest = /* @__PURE__ */ __name3((path) => ({
2715
+ requestContextPath: path
2716
+ }), "fromRequest");
2717
+ var rows = /* @__PURE__ */ __name3((s, path, page) => ({
2718
+ step: typeof s === "string" ? s : s.id,
2719
+ path,
2720
+ rows: page
2721
+ }), "rows");
2722
+ var fromKnowledge = /* @__PURE__ */ __name3((k) => ({
2723
+ knowledge: k
2724
+ }), "fromKnowledge");
2725
+ var SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
2726
+ var JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
2727
+ var WORKFLOW_ARM_SUBRUN_ID = "$arm";
2728
+ var WORKSPACE_TEMPLATE_EXPR_RE = /^\$\{\s*(?:initData|input)\.([^}]+?)\s*\}$/;
2729
+ function workspaceTemplatePath(template22) {
2730
+ const key = template22.trim();
2731
+ const expr = WORKSPACE_TEMPLATE_EXPR_RE.exec(key);
2732
+ if (expr) return expr[1].split(".");
2733
+ if (key.includes("${")) return void 0;
2734
+ return key.replace(/^(?:input|initData)\./, "").split(".");
2735
+ }
2736
+ __name(workspaceTemplatePath, "workspaceTemplatePath");
2737
+ __name3(workspaceTemplatePath, "workspaceTemplatePath");
2738
+ function retryBackoffs() {
2739
+ if (!Array.isArray(WORKFLOW_RETRY_BACKOFFS)) {
2740
+ 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')");
2741
+ }
2742
+ return WORKFLOW_RETRY_BACKOFFS;
2743
+ }
2744
+ __name(retryBackoffs, "retryBackoffs");
2745
+ __name3(retryBackoffs, "retryBackoffs");
2746
+ var SLEEP_UNTIL_REPLACEMENT = Object.freeze({
2747
+ type: "sleep",
2748
+ duration: 6e4
2749
+ });
2750
+ function sleepUntilUnsupportedMessage(id) {
2751
+ 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} }`;
2752
+ }
2753
+ __name(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
2754
+ __name3(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
2755
+ var WORKFLOW_CAPS_DEFAULT = Object.freeze({
2756
+ maxParallelArms: 16,
2757
+ maxForeachConcurrency: 16,
2758
+ maxForeachItems: 2e4,
2759
+ maxNodes: 200,
2760
+ maxNestingDepth: 3,
2761
+ maxForeachRatePerSecond: 50,
2762
+ maxLoopIntervalSeconds: 86400,
2763
+ maxWorkerTimeoutSeconds: 600,
2764
+ maxJobTimeoutSeconds: 86400,
2765
+ maxJobSegmentSeconds: 14400
2766
+ });
2767
+ var WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS = 300;
2768
+ var WORKFLOW_AGENT_DEFAULT_TIMEOUT_SECONDS = 600;
2769
+ var WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS = 3600;
2770
+ var WORKFLOW_FOREACH_DEFAULT_CONCURRENCY = 4;
2771
+ var WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS = 256;
2772
+ var WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS = 100;
2773
+ var WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS = 168;
2774
+ var WORKFLOW_SIGNAL_DEFAULT_SOURCES = [
2775
+ "webhook",
2776
+ "api",
2777
+ "user"
2778
+ ];
2779
+ var clone = /* @__PURE__ */ __name3((v) => JSON.parse(JSON.stringify(v)), "clone");
2780
+ function fillPolicy(node, defaultTimeout) {
2781
+ if (node.tier === void 0 && node.workspace !== void 0 && node.workspace !== "inherit") node.tier = "job";
2782
+ if (node.timeoutSeconds === void 0) node.timeoutSeconds = node.tier === "job" ? WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS : defaultTimeout;
2783
+ if (node.retry === void 0) node.retry = {
2784
+ maxAttempts: 1
2785
+ };
2786
+ if (node.onError === void 0) node.onError = "fail";
2787
+ if ((node.type === "step" || node.type === "tool") && node.sideEffects === void 0) node.sideEffects = "none";
2788
+ }
2789
+ __name(fillPolicy, "fillPolicy");
2790
+ __name3(fillPolicy, "fillPolicy");
2791
+ function fillSingle(node) {
2792
+ switch (node.type) {
2793
+ case "step": {
2794
+ fillPolicy(node, WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS);
2795
+ const s = node;
2796
+ if (s.resumeTimeoutHours === void 0) s.resumeTimeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2797
+ if (s.onSuspendTimeout === void 0) s.onSuspendTimeout = "fail";
2284
2798
  return;
2285
2799
  }
2286
- if (declaredKeys.has(key)) {
2287
- err("connection-declaration-invalid", `connections[${i}].key "${key}" is declared twice`, `${path}.key`);
2800
+ case "agent":
2801
+ fillPolicy(node, WORKFLOW_AGENT_DEFAULT_TIMEOUT_SECONDS);
2288
2802
  return;
2289
- }
2290
- if (typeof integrationType !== "string" || !integrationType.trim()) {
2291
- err("connection-declaration-invalid", `connections[${i}] ("${key}") needs an integrationType (the catalog slug, e.g. 'github')`, `${path}.integrationType`);
2803
+ case "tool":
2804
+ fillPolicy(node, WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS);
2805
+ return;
2806
+ case "workflow":
2807
+ if (node.workflowId === WORKFLOW_ARM_SUBRUN_ID && Array.isArray(node.graph) && node.graph[1]) fillSingle(node.graph[1]);
2292
2808
  return;
2293
- }
2294
- declaredKeys.add(key);
2295
- });
2296
- const undeclaredKey = /* @__PURE__ */ __name3((ref) => typeof ref === "string" && !declaredKeys.has(ref) && isConnectionKeyShaped(ref) && opts.connectionIds?.has(ref) !== true, "undeclaredKey");
2297
- const credentialsRef = envelopeWorkspace?.credentialsRef;
2298
- if (undeclaredKey(credentialsRef)) {
2299
- err("connection-key-undeclared", connectionKeyUndeclaredMessage("workspace.credentialsRef", credentialsRef), "workspace.credentialsRef");
2300
2809
  }
2301
- const seen = /* @__PURE__ */ new Map();
2302
- let nodeCount = 0;
2303
- const upstream = /* @__PURE__ */ new Set();
2304
- const checkId = /* @__PURE__ */ __name3((id, path) => {
2305
- nodeCount += 1;
2306
- if (seen.has(id)) {
2307
- err("duplicate-step-id", `step id "${id}" is declared twice (first at ${seen.get(id)})`, path, id);
2308
- } else {
2309
- seen.set(id, path);
2310
- }
2311
- }, "checkId");
2312
- const checkPolicyEnums = /* @__PURE__ */ __name3((node, path) => {
2313
- const id = singleId(node);
2314
- const check = /* @__PURE__ */ __name3((member, allowed) => {
2315
- const value22 = node[member];
2316
- if (value22 === void 0 || typeof value22 === "string" && allowed.includes(value22)) return;
2317
- err("invalid-envelope", `\`${member}\` must be ${allowed.map((a) => `'${a}'`).join(" | ")} (got ${JSON.stringify(value22)})`, `${path}.${member}`, id);
2318
- }, "check");
2319
- check("sideEffects", WORKFLOW_SIDE_EFFECTS);
2320
- check("jobResources", WORKFLOW_JOB_RESOURCES);
2321
- }, "checkPolicyEnums");
2322
- const checkRetry = /* @__PURE__ */ __name3((node, path) => {
2323
- const r = node.retry;
2324
- if (!r) return;
2325
- const id = singleId(node);
2326
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") {
2327
- err("backoff-invalid", `retry.backoff must be 'fixed' | 'exponential'`, `${path}.retry.backoff`, id);
2328
- }
2329
- if (r.backoffSeconds !== void 0 && r.backoffSeconds < 0) {
2330
- err("backoff-invalid", "retry.backoffSeconds must be \u2265 0", `${path}.retry.backoffSeconds`, id);
2331
- }
2332
- if (r.maxBackoffSeconds !== void 0) {
2333
- if (r.backoff !== "exponential") {
2334
- err("backoff-invalid", "retry.maxBackoffSeconds is only meaningful with backoff:'exponential'", `${path}.retry`, id);
2335
- } else if (r.maxBackoffSeconds <= 0 || r.backoffSeconds !== void 0 && r.maxBackoffSeconds < r.backoffSeconds) {
2336
- err("backoff-invalid", "retry.maxBackoffSeconds must be > 0 and \u2265 backoffSeconds", `${path}.retry.maxBackoffSeconds`, id);
2337
- }
2338
- }
2339
- }, "checkRetry");
2340
- const checkTimeout = /* @__PURE__ */ __name3((node, path) => {
2341
- const t = node.timeoutSeconds;
2342
- if (t === void 0) return;
2343
- const id = singleId(node);
2344
- if (!Number.isInteger(t) || t < 1) {
2345
- err("timeout-out-of-range", `timeoutSeconds must be an integer \u2265 1 (got ${t})`, `${path}.timeoutSeconds`, id);
2810
+ }
2811
+ __name(fillSingle, "fillSingle");
2812
+ __name3(fillSingle, "fillSingle");
2813
+ function fillHitl(node) {
2814
+ if (node.type === "approval") {
2815
+ const a = node;
2816
+ if (a.approver === void 0) a.approver = "creator";
2817
+ if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2818
+ if (a.onTimeout === void 0) a.onTimeout = "deny";
2819
+ if (a.onDeny === void 0) a.onDeny = "continue";
2820
+ if (a.excludeInitiator === void 0) a.excludeInitiator = false;
2821
+ if (a.editable === void 0) a.editable = false;
2822
+ return;
2823
+ }
2824
+ const w = node;
2825
+ if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2826
+ if (w.onTimeout === void 0) w.onTimeout = "fail";
2827
+ if (w.acceptedSources === void 0) w.acceptedSources = [
2828
+ ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
2829
+ ];
2830
+ }
2831
+ __name(fillHitl, "fillHitl");
2832
+ __name3(fillHitl, "fillHitl");
2833
+ function fillArm(arm) {
2834
+ if (arm.type === "mapping") return;
2835
+ if (isHitlNode(arm)) fillHitl(arm);
2836
+ else fillSingle(arm);
2837
+ }
2838
+ __name(fillArm, "fillArm");
2839
+ __name3(fillArm, "fillArm");
2840
+ function fillEntry(entry) {
2841
+ switch (entry.type) {
2842
+ case "step":
2843
+ case "agent":
2844
+ case "tool":
2845
+ case "workflow":
2846
+ fillSingle(entry);
2847
+ return;
2848
+ case "parallel":
2849
+ entry.steps.forEach(fillArm);
2850
+ return;
2851
+ case "conditional": {
2852
+ const c = entry;
2853
+ if (c.exclusive === void 0) c.exclusive = false;
2854
+ c.steps.forEach(fillArm);
2855
+ if (c.otherwise) fillArm(c.otherwise);
2346
2856
  return;
2347
2857
  }
2348
- if (node.tier === "job") {
2349
- const cap = node.type === "agent" ? caps.maxJobTimeoutSeconds : caps.maxJobSegmentSeconds;
2350
- if (t > cap) {
2351
- err("job-timeout-exceeds-cap", `timeoutSeconds ${t} exceeds the Job-tier cap of ${cap} s`, `${path}.timeoutSeconds`, id);
2352
- } else if (node.type === "agent" && t > caps.maxJobSegmentSeconds && !node.workspace) {
2353
- err("long-job-requires-workspace", `steps longer than 4 h checkpoint to the run workspace \u2014 add workspace:{mount:'rw'} (and workspace on createWorkflow) or split the step`, `${path}.timeoutSeconds`, id);
2354
- }
2355
- } else if (t > caps.maxWorkerTimeoutSeconds) {
2356
- err("timeout-exceeds-tier", `timeoutSeconds ${t} exceeds the worker tier's ${caps.maxWorkerTimeoutSeconds} s \u2014 steps longer than 10 min run on the Job tier: add tier:'job' (up to ${caps.maxJobSegmentSeconds} s)`, `${path}.timeoutSeconds`, id);
2357
- }
2358
- }, "checkTimeout");
2359
- const checkSpecialistRole = /* @__PURE__ */ __name3((node, path) => {
2360
- const role = node.role;
2361
- const hasRef = typeof role.ref === "string";
2362
- const hasInline = role.name !== void 0 || role.instructions !== void 0 || Array.isArray(role.tools) && role.tools.length > 0;
2363
- if (hasRef && hasInline) {
2364
- err("role-ref-and-inline", "a role is either `{ref}` or inline `{name, instructions, tools}`, never both", `${path}.role`, node.id);
2365
- }
2366
- if (node.agentId !== "$self") {
2367
- err("ephemeral-base-not-self", "a role runs on the owner only: use '$self'", `${path}.agentId`, node.id);
2368
- }
2369
- if (opts.policy && opts.policy.ephemeralSpecialists !== true) {
2370
- err("ephemeral-specialists-disabled", "ephemeral specialists are off for this org/agent (workflowPolicy.ephemeralSpecialists)", `${path}.role`, node.id);
2858
+ case "foreach": {
2859
+ const f = entry;
2860
+ f.opts = f.opts ?? {};
2861
+ if (f.opts.concurrency === void 0) f.opts.concurrency = WORKFLOW_FOREACH_DEFAULT_CONCURRENCY;
2862
+ if (f.opts.maxItems === void 0) f.opts.maxItems = WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2863
+ fillArm(f.step);
2864
+ return;
2371
2865
  }
2372
- let tools = Array.isArray(role.tools) ? role.tools.filter((t) => typeof t === "string") : [];
2373
- if (hasRef && opts.roleLibrary) {
2374
- const saved = opts.roleLibrary.get(role.ref);
2375
- if (!saved) {
2376
- err("role-ref-unknown", `no saved role \`${role.ref}\` in the owner's library`, `${path}.role.ref`, node.id);
2377
- } else {
2378
- tools = saved.tools ?? [];
2379
- if (typeof role.roleHash === "string" && saved.roleHash && role.roleHash !== saved.roleHash) {
2380
- warn("role-ref-stale", `saved role \`${role.ref}\` changed since this version was frozen`, `${path}.role.ref`, node.id);
2381
- }
2382
- }
2866
+ case "loop": {
2867
+ const l = entry;
2868
+ if (l.maxIterations === void 0) l.maxIterations = WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
2869
+ fillArm(l.step);
2870
+ return;
2383
2871
  }
2384
- if (tools.length && opts.toolCatalog) {
2385
- const missing = tools.filter((t) => !opts.toolCatalog.has(t));
2386
- if (missing.length) {
2387
- err("ephemeral-tools-not-subset", `role tools not in the owner's catalog: ${missing.join(", ")}`, `${path}.role.tools`, node.id);
2388
- }
2872
+ case "approval":
2873
+ case "waitForSignal":
2874
+ fillHitl(entry);
2875
+ return;
2876
+ case "mapping":
2877
+ case "sleep":
2878
+ case "sleepUntil":
2879
+ return;
2880
+ }
2881
+ }
2882
+ __name(fillEntry, "fillEntry");
2883
+ __name3(fillEntry, "fillEntry");
2884
+ function withDefaultsFilled(g) {
2885
+ const out = clone(g);
2886
+ out.definition.graph.forEach(fillEntry);
2887
+ return out;
2888
+ }
2889
+ __name(withDefaultsFilled, "withDefaultsFilled");
2890
+ __name3(withDefaultsFilled, "withDefaultsFilled");
2891
+ var CONNECTION_ID_HEX_RE = /^[0-9a-f]{24}$/;
2892
+ function isConnectionKeyShaped(value22) {
2893
+ return WORKFLOW_CONNECTION_KEY_RE.test(value22) && !CONNECTION_ID_HEX_RE.test(value22);
2894
+ }
2895
+ __name(isConnectionKeyShaped, "isConnectionKeyShaped");
2896
+ __name3(isConnectionKeyShaped, "isConnectionKeyShaped");
2897
+ function connectionKeyUndeclaredMessage(path, key) {
2898
+ 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`;
2899
+ }
2900
+ __name(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
2901
+ __name3(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
2902
+ var WORKFLOW_JOB_TOOLS = [
2903
+ "shell",
2904
+ "read",
2905
+ "write",
2906
+ "edit",
2907
+ "glob",
2908
+ "grep",
2909
+ "git",
2910
+ "gh",
2911
+ "fetch"
2912
+ ];
2913
+ var WORKFLOW_JOB_MAX_WORKTREE_ARMS = 8;
2914
+ function classifyModelProvider(model) {
2915
+ const m = (model ?? "").trim().toLowerCase();
2916
+ if (!m) return null;
2917
+ if (/^(anthropic\/|claude)/.test(m)) return "anthropic";
2918
+ if (/^(openai\/|gpt-|o[1-9](-|$)|chatgpt)/.test(m)) return "openai";
2919
+ if (/^(google\/|gemini)/.test(m)) return "google";
2920
+ return null;
2921
+ }
2922
+ __name(classifyModelProvider, "classifyModelProvider");
2923
+ __name3(classifyModelProvider, "classifyModelProvider");
2924
+ var workspaceOf = /* @__PURE__ */ __name3((node) => node.workspace, "workspaceOf");
2925
+ var mountsWorkspace = /* @__PURE__ */ __name3((node) => {
2926
+ const w = workspaceOf(node);
2927
+ return w !== void 0 && w !== "inherit";
2928
+ }, "mountsWorkspace");
2929
+ var isJobTier = /* @__PURE__ */ __name3((node) => node.tier === "job" || mountsWorkspace(node), "isJobTier");
2930
+ var jobToolsOf = /* @__PURE__ */ __name3((node) => {
2931
+ if (node.type === "agent") return node.toolScope?.jobTools;
2932
+ return node.jobTools;
2933
+ }, "jobToolsOf");
2934
+ function schemaAtPath(schema, path) {
2935
+ let cur = schema;
2936
+ if (!cur || typeof cur !== "object") return void 0;
2937
+ for (const seg of path.split(".").filter(Boolean)) {
2938
+ const props = cur.properties;
2939
+ const next = props?.[seg];
2940
+ if (!next || typeof next !== "object") return void 0;
2941
+ cur = next;
2942
+ }
2943
+ return cur;
2944
+ }
2945
+ __name(schemaAtPath, "schemaAtPath");
2946
+ __name3(schemaAtPath, "schemaAtPath");
2947
+ var schemaIsArray = /* @__PURE__ */ __name3((schema) => {
2948
+ if (!schema) return void 0;
2949
+ const t = schema.type;
2950
+ if (t === void 0) return void 0;
2951
+ return Array.isArray(t) ? t.includes("array") : t === "array";
2952
+ }, "schemaIsArray");
2953
+ var isHitlNode = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
2954
+ var isSingleStep = /* @__PURE__ */ __name3((n2) => !isHitlNode(n2), "isSingleStep");
2955
+ var singleId = /* @__PURE__ */ __name3((s) => s.type === "step" ? s.step.id : s.id, "singleId");
2956
+ var armId = /* @__PURE__ */ __name3((a) => a.type === "mapping" ? a.id : singleId(a), "armId");
2957
+ var TEMPLATE_STEP_REF = /\$\{\s*stepResults\.([A-Za-z0-9_\-]+)/g;
2958
+ function templateStepRefs(text) {
2959
+ const ids = [];
2960
+ for (const m of text.matchAll(TEMPLATE_STEP_REF)) ids.push(m[1]);
2961
+ return ids;
2962
+ }
2963
+ __name(templateStepRefs, "templateStepRefs");
2964
+ __name3(templateStepRefs, "templateStepRefs");
2965
+ function readMapConfig(raw) {
2966
+ if (!raw) return void 0;
2967
+ if (typeof raw !== "string") return raw;
2968
+ try {
2969
+ const cfg = JSON.parse(raw);
2970
+ return cfg && typeof cfg === "object" && !Array.isArray(cfg) ? cfg : void 0;
2971
+ } catch {
2972
+ return void 0;
2973
+ }
2974
+ }
2975
+ __name(readMapConfig, "readMapConfig");
2976
+ __name3(readMapConfig, "readMapConfig");
2977
+ function mapConfigStepRefs(raw) {
2978
+ const cfg = readMapConfig(raw);
2979
+ if (!cfg) return [];
2980
+ const ids = [];
2981
+ for (const d of Object.values(cfg)) {
2982
+ if (!isMapDescriptor(d)) continue;
2983
+ if ("step" in d) ids.push(...Array.isArray(d.step) ? d.step : [
2984
+ d.step
2985
+ ]);
2986
+ if ("template" in d) ids.push(...templateStepRefs(d.template));
2987
+ }
2988
+ return ids;
2989
+ }
2990
+ __name(mapConfigStepRefs, "mapConfigStepRefs");
2991
+ __name3(mapConfigStepRefs, "mapConfigStepRefs");
2992
+ function nodeStepRefs(entry) {
2993
+ switch (entry.type) {
2994
+ case "agent": {
2995
+ const a = entry;
2996
+ const p = a.promptTemplate;
2997
+ return typeof p === "string" ? templateStepRefs(p) : p && "template" in p ? templateStepRefs(p.template) : [];
2389
2998
  }
2390
- }, "checkSpecialistRole");
2391
- const checkRequiredConnections = /* @__PURE__ */ __name3((node, path) => {
2392
- const required = node.requiredConnections;
2393
- if (!Array.isArray(required)) return;
2394
- const undeclared = required.filter(undeclaredKey);
2395
- if (undeclared.length) {
2396
- err("connection-key-undeclared", connectionKeyUndeclaredMessage(`${path}.requiredConnections`, undeclared[0]) + (undeclared.length > 1 ? ` (also undeclared: ${JSON.stringify(undeclared.slice(1))})` : ""), `${path}.requiredConnections`, singleId(node));
2999
+ case "tool":
3000
+ return mapConfigStepRefs(entry.input);
3001
+ case "workflow":
3002
+ return mapConfigStepRefs(entry.input);
3003
+ case "mapping":
3004
+ return mapConfigStepRefs(entry.mapConfig);
3005
+ case "approval": {
3006
+ const a = entry;
3007
+ return typeof a.details === "string" ? templateStepRefs(a.details) : [];
2397
3008
  }
2398
- if (!opts.connectionIds) return;
2399
- const unknown = required.filter((c) => typeof c !== "string" || !declaredKeys.has(c) && !opts.connectionIds.has(c));
2400
- if (unknown.length) {
2401
- err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are neither declared connections[].key values nor connections the owner can mount`, `${path}.requiredConnections`, singleId(node));
3009
+ default:
3010
+ return [];
3011
+ }
3012
+ }
3013
+ __name(nodeStepRefs, "nodeStepRefs");
3014
+ __name3(nodeStepRefs, "nodeStepRefs");
3015
+ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
3016
+ static: true
3017
+ }) {
3018
+ const issues = [];
3019
+ const err = /* @__PURE__ */ __name3((code, message, path, stepId) => {
3020
+ issues.push({
3021
+ code,
3022
+ message,
3023
+ severity: "error",
3024
+ path,
3025
+ stepId
3026
+ });
3027
+ }, "err");
3028
+ const warn = /* @__PURE__ */ __name3((code, message, path, stepId) => {
3029
+ issues.push({
3030
+ code,
3031
+ message,
3032
+ severity: "warning",
3033
+ path,
3034
+ stepId
3035
+ });
3036
+ }, "warn");
3037
+ const graph = g?.definition?.graph;
3038
+ if (!Array.isArray(graph)) {
3039
+ err("invalid-envelope", "`definition.graph` must be an array", "definition.graph");
3040
+ return issues;
3041
+ }
3042
+ if (graph.length === 0) {
3043
+ err("empty-graph", "a workflow needs at least one entry", "definition.graph");
3044
+ return issues;
3045
+ }
3046
+ if (!opts.static && g.concurrencyPolicy !== void 0) {
3047
+ err("static-only-field", "`concurrencyPolicy` is a static-only envelope field (03 \xA73.4.2)", "concurrencyPolicy");
3048
+ }
3049
+ const envelopeWorkspace = g.workspace;
3050
+ if (envelopeWorkspace?.backend && envelopeWorkspace.backend !== "ebs" && opts.policy?.workspaceBackends && !opts.policy.workspaceBackends.includes(envelopeWorkspace.backend)) {
3051
+ 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");
3052
+ }
3053
+ for (const member of [
3054
+ "repo",
3055
+ "ref"
3056
+ ]) {
3057
+ const v = envelopeWorkspace?.[member];
3058
+ const tpl = v && typeof v === "object" && typeof v.template === "string" ? v.template : void 0;
3059
+ if (tpl !== void 0 && workspaceTemplatePath(tpl) === void 0) {
3060
+ 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}`);
2402
3061
  }
2403
- }, "checkRequiredConnections");
2404
- const checkTier = /* @__PURE__ */ __name3((node, path) => {
2405
- const id = singleId(node);
2406
- if (node.workspace && node.workspace !== "inherit" && node.tier !== void 0 && node.tier !== "job") {
2407
- err("workspace-requires-job-tier", "a step mounting a workspace must be tier:'job'", `${path}.workspace`, id);
3062
+ }
3063
+ const declaredKeys = new Set(opts.connectionKeys ?? []);
3064
+ if (g.connections !== void 0 && !Array.isArray(g.connections)) {
3065
+ err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
3066
+ }
3067
+ (Array.isArray(g.connections) ? g.connections : []).forEach((c, i) => {
3068
+ const path = `connections.${i}`;
3069
+ const key = c?.key;
3070
+ const integrationType = c?.integrationType;
3071
+ if (typeof key !== "string" || !WORKFLOW_CONNECTION_KEY_RE.test(key)) {
3072
+ err("connection-declaration-invalid", `connections[${i}].key must match ${WORKFLOW_CONNECTION_KEY_RE}`, `${path}.key`);
3073
+ return;
2408
3074
  }
2409
- if (node.harness !== void 0 && node.tier !== "job") {
2410
- err("harness-requires-job-tier", "`harness` is only legal on a tier:'job' agent step", `${path}.harness`, id);
3075
+ if (declaredKeys.has(key)) {
3076
+ err("connection-declaration-invalid", `connections[${i}].key "${key}" is declared twice`, `${path}.key`);
3077
+ return;
2411
3078
  }
2412
- const provider = node.type === "agent" ? classifyModelProvider(node.model) : null;
2413
- if (node.harness === "claude-code" && provider !== null && provider !== "anthropic") {
2414
- err("harness-provider-mismatch", `harness:'claude-code' needs an Anthropic model (model "${node.type === "agent" ? node.model : ""}" is ${provider})`, `${path}.harness`, id);
3079
+ if (typeof integrationType !== "string" || !integrationType.trim()) {
3080
+ err("connection-declaration-invalid", `connections[${i}] ("${key}") needs an integrationType (the catalog slug, e.g. 'github')`, `${path}.integrationType`);
3081
+ return;
2415
3082
  }
2416
- if (isJobTier(node) && provider !== null && opts.policy?.jobProviders && !opts.policy.jobProviders.includes(provider)) {
2417
- err("job-tier-provider-unsupported", `model provider '${provider}' is outside LUA_WF_JOB_PROVIDERS [${opts.policy.jobProviders.join(", ")}]`, `${path}.model`, id);
3083
+ declaredKeys.add(key);
3084
+ });
3085
+ const undeclaredKey = /* @__PURE__ */ __name3((ref) => typeof ref === "string" && !declaredKeys.has(ref) && isConnectionKeyShaped(ref) && opts.connectionIds?.has(ref) !== true, "undeclaredKey");
3086
+ const credentialsRef = envelopeWorkspace?.credentialsRef;
3087
+ if (undeclaredKey(credentialsRef)) {
3088
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage("workspace.credentialsRef", credentialsRef), "workspace.credentialsRef");
3089
+ }
3090
+ const seen = /* @__PURE__ */ new Map();
3091
+ let nodeCount = 0;
3092
+ const upstream = /* @__PURE__ */ new Set();
3093
+ const checkId = /* @__PURE__ */ __name3((id, path) => {
3094
+ nodeCount += 1;
3095
+ if (seen.has(id)) {
3096
+ err("duplicate-step-id", `step id "${id}" is declared twice (first at ${seen.get(id)})`, path, id);
3097
+ } else {
3098
+ seen.set(id, path);
2418
3099
  }
2419
- }, "checkTier");
2420
- const checkWorkspace = /* @__PURE__ */ __name3((node, path) => {
3100
+ }, "checkId");
3101
+ const checkPolicyEnums = /* @__PURE__ */ __name3((node, path) => {
2421
3102
  const id = singleId(node);
2422
- const ws = workspaceOf(node);
2423
- if (isJobTier(node) && opts.policy && opts.policy.jobTier !== true) {
2424
- err("job-tier-disabled", `"${id}" runs on the Job tier but the org's workflowPolicy.jobTier is off \u2014 an org admin enables it (lua features workflows.caps.jobTier true)`, `${path}.tier`, id);
3103
+ const check = /* @__PURE__ */ __name3((member, allowed) => {
3104
+ const value22 = node[member];
3105
+ if (value22 === void 0 || typeof value22 === "string" && allowed.includes(value22)) return;
3106
+ err("invalid-envelope", `\`${member}\` must be ${allowed.map((a) => `'${a}'`).join(" | ")} (got ${JSON.stringify(value22)})`, `${path}.${member}`, id);
3107
+ }, "check");
3108
+ check("sideEffects", WORKFLOW_SIDE_EFFECTS);
3109
+ check("jobResources", WORKFLOW_JOB_RESOURCES);
3110
+ }, "checkPolicyEnums");
3111
+ const checkRetry = /* @__PURE__ */ __name3((node, path) => {
3112
+ const r = node.retry;
3113
+ if (!r) return;
3114
+ const id = singleId(node);
3115
+ const unknown = unknownWorkflowRetryMembers(r);
3116
+ if (unknown.length) err("invalid-envelope", workflowRetryUnknownMembersMessage(unknown), `${path}.retry`, id);
3117
+ if (r.maxAttempts !== void 0 && !isWithinWorkflowRetryAttempts(r.maxAttempts)) {
3118
+ const over = typeof r.maxAttempts === "number" && r.maxAttempts > WORKFLOW_RETRY_MAX_ATTEMPTS;
3119
+ err(over ? "cap-exceeded" : "invalid-envelope", workflowRetryMaxAttemptsMessage(r.maxAttempts), `${path}.retry.maxAttempts`, id);
2425
3120
  }
2426
- const tools = jobToolsOf(node);
2427
- if (tools !== void 0) {
2428
- if (!isJobTier(node)) {
2429
- err("job-tools-require-job-tier", "`jobTools` needs tier:'job'", `${path}.jobTools`, id);
3121
+ const backoffs = retryBackoffs();
3122
+ if (r.backoff !== void 0 && !backoffs.includes(r.backoff)) {
3123
+ const list = backoffs.map((b) => `'${b}'`).join(" | ");
3124
+ err("backoff-invalid", `retry.backoff must be ${list}`, `${path}.retry.backoff`, id);
3125
+ }
3126
+ if (r.backoffSeconds !== void 0 && r.backoffSeconds < 0) {
3127
+ err("backoff-invalid", "retry.backoffSeconds must be \u2265 0", `${path}.retry.backoffSeconds`, id);
3128
+ }
3129
+ if (r.maxBackoffSeconds !== void 0) {
3130
+ if (r.backoff !== "exponential") {
3131
+ err("backoff-invalid", "retry.maxBackoffSeconds is only meaningful with backoff:'exponential'", `${path}.retry`, id);
3132
+ } else if (r.maxBackoffSeconds <= 0 || r.backoffSeconds !== void 0 && r.maxBackoffSeconds < r.backoffSeconds) {
3133
+ err("backoff-invalid", "retry.maxBackoffSeconds must be > 0 and \u2265 backoffSeconds", `${path}.retry.maxBackoffSeconds`, id);
2430
3134
  }
2431
- const unknown = (Array.isArray(tools) ? tools : [
3135
+ }
3136
+ }, "checkRetry");
3137
+ const checkTimeout = /* @__PURE__ */ __name3((node, path) => {
3138
+ const t = node.timeoutSeconds;
3139
+ if (t === void 0) return;
3140
+ const id = singleId(node);
3141
+ if (!Number.isInteger(t) || t < 1) {
3142
+ err("timeout-out-of-range", `timeoutSeconds must be an integer \u2265 1 (got ${t})`, `${path}.timeoutSeconds`, id);
3143
+ return;
3144
+ }
3145
+ if (node.tier === "job") {
3146
+ const cap = node.type === "agent" ? caps.maxJobTimeoutSeconds : caps.maxJobSegmentSeconds;
3147
+ if (t > cap) {
3148
+ err("job-timeout-exceeds-cap", `timeoutSeconds ${t} exceeds the Job-tier cap of ${cap} s`, `${path}.timeoutSeconds`, id);
3149
+ } else if (node.type === "agent" && t > caps.maxJobSegmentSeconds && !node.workspace) {
3150
+ err("long-job-requires-workspace", `steps longer than 4 h checkpoint to the run workspace \u2014 add workspace:{mount:'rw'} (and workspace on createWorkflow) or split the step`, `${path}.timeoutSeconds`, id);
3151
+ }
3152
+ } else if (t > caps.maxWorkerTimeoutSeconds) {
3153
+ err("timeout-exceeds-tier", `timeoutSeconds ${t} exceeds the worker tier's ${caps.maxWorkerTimeoutSeconds} s \u2014 steps longer than 10 min run on the Job tier: add tier:'job' (up to ${caps.maxJobSegmentSeconds} s)`, `${path}.timeoutSeconds`, id);
3154
+ }
3155
+ }, "checkTimeout");
3156
+ const checkSpecialistRole = /* @__PURE__ */ __name3((node, path) => {
3157
+ const role = node.role;
3158
+ const hasRef = typeof role.ref === "string";
3159
+ const hasInline = role.name !== void 0 || role.instructions !== void 0 || Array.isArray(role.tools) && role.tools.length > 0;
3160
+ if (hasRef && hasInline) {
3161
+ err("role-ref-and-inline", "a role is either `{ref}` or inline `{name, instructions, tools}`, never both", `${path}.role`, node.id);
3162
+ }
3163
+ if (node.agentId !== "$self") {
3164
+ err("ephemeral-base-not-self", "a role runs on the owner only: use '$self'", `${path}.agentId`, node.id);
3165
+ }
3166
+ if (opts.policy && opts.policy.ephemeralSpecialists !== true) {
3167
+ err("ephemeral-specialists-disabled", "ephemeral specialists are off for this org/agent (workflowPolicy.ephemeralSpecialists)", `${path}.role`, node.id);
3168
+ }
3169
+ let tools = Array.isArray(role.tools) ? role.tools.filter((t) => typeof t === "string") : [];
3170
+ if (hasRef && opts.roleLibrary) {
3171
+ const saved = opts.roleLibrary.get(role.ref);
3172
+ if (!saved) {
3173
+ err("role-ref-unknown", `no saved role \`${role.ref}\` in the owner's library`, `${path}.role.ref`, node.id);
3174
+ } else {
3175
+ tools = saved.tools ?? [];
3176
+ if (typeof role.roleHash === "string" && saved.roleHash && role.roleHash !== saved.roleHash) {
3177
+ warn("role-ref-stale", `saved role \`${role.ref}\` changed since this version was frozen`, `${path}.role.ref`, node.id);
3178
+ }
3179
+ }
3180
+ }
3181
+ if (tools.length && opts.toolCatalog) {
3182
+ const missing = tools.filter((t) => !opts.toolCatalog.has(t));
3183
+ if (missing.length) {
3184
+ err("ephemeral-tools-not-subset", `role tools not in the owner's catalog: ${missing.join(", ")}`, `${path}.role.tools`, node.id);
3185
+ }
3186
+ }
3187
+ }, "checkSpecialistRole");
3188
+ const checkRequiredConnections = /* @__PURE__ */ __name3((node, path) => {
3189
+ const required = node.requiredConnections;
3190
+ if (!Array.isArray(required)) return;
3191
+ const undeclared = required.filter(undeclaredKey);
3192
+ if (undeclared.length) {
3193
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage(`${path}.requiredConnections`, undeclared[0]) + (undeclared.length > 1 ? ` (also undeclared: ${JSON.stringify(undeclared.slice(1))})` : ""), `${path}.requiredConnections`, singleId(node));
3194
+ }
3195
+ if (!opts.connectionIds) return;
3196
+ const unknown = required.filter((c) => typeof c !== "string" || !declaredKeys.has(c) && !opts.connectionIds.has(c));
3197
+ if (unknown.length) {
3198
+ err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are neither declared connections[].key values nor connections the owner can mount`, `${path}.requiredConnections`, singleId(node));
3199
+ }
3200
+ }, "checkRequiredConnections");
3201
+ const checkTier = /* @__PURE__ */ __name3((node, path) => {
3202
+ const id = singleId(node);
3203
+ if (node.workspace && node.workspace !== "inherit" && node.tier !== void 0 && node.tier !== "job") {
3204
+ err("workspace-requires-job-tier", "a step mounting a workspace must be tier:'job'", `${path}.workspace`, id);
3205
+ }
3206
+ if (node.harness !== void 0 && node.tier !== "job") {
3207
+ err("harness-requires-job-tier", "`harness` is only legal on a tier:'job' agent step", `${path}.harness`, id);
3208
+ }
3209
+ const provider = node.type === "agent" ? classifyModelProvider(node.model) : null;
3210
+ if (node.harness === "claude-code" && provider !== null && provider !== "anthropic") {
3211
+ err("harness-provider-mismatch", `harness:'claude-code' needs an Anthropic model (model "${node.type === "agent" ? node.model : ""}" is ${provider})`, `${path}.harness`, id);
3212
+ }
3213
+ if (isJobTier(node) && provider !== null && opts.policy?.jobProviders && !opts.policy.jobProviders.includes(provider)) {
3214
+ err("job-tier-provider-unsupported", `model provider '${provider}' is outside LUA_WF_JOB_PROVIDERS [${opts.policy.jobProviders.join(", ")}]`, `${path}.model`, id);
3215
+ }
3216
+ }, "checkTier");
3217
+ const checkWorkspace = /* @__PURE__ */ __name3((node, path) => {
3218
+ const id = singleId(node);
3219
+ const ws = workspaceOf(node);
3220
+ if (isJobTier(node) && opts.policy && opts.policy.jobTier !== true) {
3221
+ err("job-tier-disabled", `"${id}" runs on the Job tier but the org's workflowPolicy.jobTier is off \u2014 an org admin enables it (lua features workflows.caps.jobTier true)`, `${path}.tier`, id);
3222
+ }
3223
+ const tools = jobToolsOf(node);
3224
+ if (tools !== void 0) {
3225
+ if (!isJobTier(node)) {
3226
+ err("job-tools-require-job-tier", "`jobTools` needs tier:'job'", `${path}.jobTools`, id);
3227
+ }
3228
+ const unknown = (Array.isArray(tools) ? tools : [
2432
3229
  tools
2433
3230
  ]).filter((t) => !WORKFLOW_JOB_TOOLS.includes(t));
2434
3231
  if (unknown.length > 0) {
@@ -2463,6 +3260,21 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2463
3260
  const schema = node.type === "step" ? node.step.outputSchema : node.type === "agent" ? node.outputSchema : void 0;
2464
3261
  if (schema !== void 0) outputSchemas.set(singleId(node), schema);
2465
3262
  }, "recordOutputSchema");
3263
+ const checkMapMembers = /* @__PURE__ */ __name3((cfg, basePath, id) => {
3264
+ for (const m of malformedMapMembers(cfg)) {
3265
+ warn(MAP_MEMBER_MALFORMED_CODE, mapMemberMalformedMessage(id, m), `${basePath}.${m.member}`, id);
3266
+ }
3267
+ }, "checkMapMembers");
3268
+ const checkInputShape = /* @__PURE__ */ __name3((node, path) => {
3269
+ if (node.type !== "tool" && node.type !== "workflow") return;
3270
+ const input = node.input;
3271
+ if (input === void 0) return;
3272
+ if (input !== null && typeof input === "object" && !Array.isArray(input)) {
3273
+ checkMapMembers(input, `${path}.input`, node.id);
3274
+ return;
3275
+ }
3276
+ err("invalid-envelope", `\`input\` must be an object map \u2014 each member a binding descriptor ({initData:true, path} | {step, path} | {value} | {template} | {requestContextPath}) or a JSON literal (got ${JSON.stringify(input)})`, `${path}.input`, node.id);
3277
+ }, "checkInputShape");
2466
3278
  const checkSingle = /* @__PURE__ */ __name3((node, path, depth) => {
2467
3279
  recordOutputSchema(node);
2468
3280
  if (node.type === "workflow" && node.workflowId === WORKFLOW_ARM_SUBRUN_ID) {
@@ -2476,8 +3288,13 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2476
3288
  err("container-arm-empty", "a bare mapping arm has nothing to run", `${path}.graph.1`, node.id);
2477
3289
  return;
2478
3290
  }
3291
+ const inner = body[1];
3292
+ if (isHitlNode(inner)) {
3293
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(inner.type, inner.id, "mapped-arm"), `${path}.graph.1`, inner.id);
3294
+ return;
3295
+ }
2479
3296
  upstream.add(singleId(body[1]));
2480
- checkArm(body[0], `${path}.graph.0`, depth);
3297
+ checkArm(body[0], `${path}.graph.0`, depth, "parallel");
2481
3298
  checkSingle(body[1], `${path}.graph.1`, depth);
2482
3299
  upstream.add(body[0].id);
2483
3300
  upstream.add(singleId(body[1]));
@@ -2485,6 +3302,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2485
3302
  }
2486
3303
  checkId(singleId(node), path);
2487
3304
  checkPolicyEnums(node, path);
3305
+ checkInputShape(node, path);
2488
3306
  checkTimeout(node, path);
2489
3307
  checkTier(node, path);
2490
3308
  checkRetry(node, path);
@@ -2509,20 +3327,72 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2509
3327
  if (node.type === "workflow" && node.kind === "subrun" && depth > caps.maxNestingDepth) {
2510
3328
  err("cap-exceeded", `nesting depth ${depth} exceeds ${caps.maxNestingDepth}`, path, node.id);
2511
3329
  }
3330
+ if (node.type === "workflow" && node.workflowId !== WORKFLOW_ARM_SUBRUN_ID && typeof g.definition?.id === "string" && node.workflowId === g.definition.id) {
3331
+ err("subrun-cycle", `"${node.id}" starts "${node.workflowId}", which is this workflow itself`, path, node.id);
3332
+ }
2512
3333
  for (const ref of nodeStepRefs(node)) {
2513
3334
  if (!upstream.has(ref)) {
2514
3335
  err("template-reference-unresolved", `"${singleId(node)}" references stepResults.${ref}, which is not upstream`, path, singleId(node));
2515
3336
  }
2516
3337
  }
2517
3338
  }, "checkSingle");
2518
- const checkArm = /* @__PURE__ */ __name3((arm, path, depth) => {
3339
+ const checkHitl = /* @__PURE__ */ __name3((node, path) => {
3340
+ if (node.type === "waitForSignal") {
3341
+ const w = node;
3342
+ checkId(w.id, path);
3343
+ if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path}.signal`, w.id);
3344
+ return;
3345
+ }
3346
+ const a = node;
3347
+ checkId(a.id, path);
3348
+ if (a.approver === "creator" && a.excludeInitiator === true) {
3349
+ err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path, a.id);
3350
+ }
3351
+ if (a.fourEyes !== void 0 && a.editable !== true) {
3352
+ err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path}.fourEyes`, a.id);
3353
+ }
3354
+ if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
3355
+ err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path}.editablePaths`, a.id);
3356
+ }
3357
+ for (const p of a.editablePaths ?? []) {
3358
+ if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path}.editablePaths`, a.id);
3359
+ }
3360
+ if (Array.isArray(a.onTimeout)) {
3361
+ const chain = a.onTimeout;
3362
+ const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
3363
+ if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path}.onTimeout`, a.id);
3364
+ const last = chain[chain.length - 1];
3365
+ if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
3366
+ err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path}.onTimeout`, a.id);
3367
+ }
3368
+ }
3369
+ if (typeof a.details === "string") {
3370
+ for (const ref of templateStepRefs(a.details)) {
3371
+ if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path, a.id);
3372
+ }
3373
+ }
3374
+ }, "checkHitl");
3375
+ const checkHitlArm = /* @__PURE__ */ __name3((node, path, container) => {
3376
+ if (!workflowContainerRunsHitlArm(container)) {
3377
+ checkId(node.id, path);
3378
+ err("node-type-unsupported-in-container", workflowHitlArmUnsupportedMessage(node.type, node.id, container), path, node.id);
3379
+ return;
3380
+ }
3381
+ checkHitl(node, path);
3382
+ }, "checkHitlArm");
3383
+ const checkArm = /* @__PURE__ */ __name3((arm, path, depth, container) => {
2519
3384
  if (arm.type === "mapping") {
2520
3385
  checkId(arm.id, path);
3386
+ checkMapMembers(readMapConfig(arm.mapConfig), `${path}.mapConfig`, arm.id);
2521
3387
  for (const ref of nodeStepRefs(arm)) {
2522
3388
  if (!upstream.has(ref)) err("template-reference-unresolved", `"${arm.id}" references stepResults.${ref}, which is not upstream`, path, arm.id);
2523
3389
  }
2524
3390
  return;
2525
3391
  }
3392
+ if (isHitlNode(arm)) {
3393
+ checkHitlArm(arm, path, container);
3394
+ return;
3395
+ }
2526
3396
  checkSingle(arm, path, depth);
2527
3397
  }, "checkArm");
2528
3398
  graph.forEach((entry, i) => {
@@ -2538,6 +3408,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2538
3408
  break;
2539
3409
  case "mapping":
2540
3410
  checkId(entry.id, path);
3411
+ checkMapMembers(readMapConfig(entry.mapConfig), `${path}.mapConfig`, entry.id);
2541
3412
  for (const ref of nodeStepRefs(entry)) {
2542
3413
  if (!upstream.has(ref)) err("template-reference-unresolved", `"${entry.id}" references stepResults.${ref}, which is not upstream`, path, entry.id);
2543
3414
  }
@@ -2572,18 +3443,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2572
3443
  err("mapping-placement", "a bare mapping cannot be a parallel arm \u2014 chain it as [map, step]", armPath);
2573
3444
  return;
2574
3445
  }
2575
- if (arm.type === "approval" || arm.type === "waitForSignal") {
2576
- err("approval-inside-container", "approval / waitForSignal are top-level only in v1", armPath);
2577
- return;
2578
- }
2579
- checkSingle(arm, armPath, 1);
3446
+ checkArm(arm, armPath, 1, "parallel");
2580
3447
  declared.push(singleId(arm));
2581
3448
  });
2582
- const worktreeArms = p.steps.filter((a) => a.workspace?.isolation === "worktree");
3449
+ const executableArms = p.steps.filter(isSingleStep);
3450
+ const worktreeArms = executableArms.filter((a) => a.workspace?.isolation === "worktree");
2583
3451
  if (worktreeArms.length > 0 && !p.merge) err("worktree-arms-require-merge", "worktree arms need a `merge` policy", path);
2584
3452
  if (worktreeArms.length === 0 && p.merge) err("merge-requires-worktree-arms", "`merge` needs at least one worktree arm", path);
2585
3453
  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);
2586
- const sharedArms = p.steps.filter((a) => {
3454
+ const sharedArms = executableArms.filter((a) => {
2587
3455
  const w = workspaceOf(a);
2588
3456
  if (!w || w === "inherit" || w.isolation === "worktree") return false;
2589
3457
  return !(envelopeWorkspace?.backend === "efs" && w.mount === "ro");
@@ -2600,11 +3468,11 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2600
3468
  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}`);
2601
3469
  });
2602
3470
  c.steps.forEach((arm, j) => {
2603
- checkArm(arm, `${path}.steps.${j}`, 1);
3471
+ checkArm(arm, `${path}.steps.${j}`, 1, "conditional");
2604
3472
  declared.push(armId(arm));
2605
3473
  });
2606
3474
  if (c.otherwise) {
2607
- checkArm(c.otherwise, `${path}.otherwise`, 1);
3475
+ checkArm(c.otherwise, `${path}.otherwise`, 1, "conditional");
2608
3476
  declared.push(armId(c.otherwise));
2609
3477
  }
2610
3478
  break;
@@ -2627,8 +3495,9 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2627
3495
  }
2628
3496
  if (o.chunk !== void 0) {
2629
3497
  const max = o.maxItems ?? WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2630
- if (!Number.isInteger(o.chunk.size) || o.chunk.size < 1 || o.chunk.size > max) {
2631
- err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, `${path}.opts.chunk.size`);
3498
+ const size = typeof o.chunk === "number" ? o.chunk : o.chunk.size;
3499
+ if (!Number.isInteger(size) || size < 1 || size > max) {
3500
+ 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`);
2632
3501
  }
2633
3502
  }
2634
3503
  if (o.rateLimit !== void 0) {
@@ -2645,12 +3514,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2645
3514
  const bodyType = f.step.type;
2646
3515
  const prevEntry = i > 0 ? graph[i - 1] : void 0;
2647
3516
  if (bodyType !== "mapping" && prevEntry?.type === "mapping" && prevEntry.id === `${singleId(f.step)}_items`) {
2648
- let cfg;
2649
- try {
2650
- cfg = JSON.parse(prevEntry.mapConfig);
2651
- } catch {
2652
- cfg = void 0;
2653
- }
3517
+ const cfg = readMapConfig(prevEntry.mapConfig);
2654
3518
  const d = cfg?.[""];
2655
3519
  let arrayLike;
2656
3520
  if (d && "value" in d) arrayLike = Array.isArray(d.value);
@@ -2662,8 +3526,13 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2662
3526
  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));
2663
3527
  }
2664
3528
  if (bodyType === "mapping") err("container-arm-empty", "a foreach body needs a step, not a bare mapping", `${path}.step`);
2665
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
2666
- else {
3529
+ else if (isHitlNode(f.step)) {
3530
+ if (o.chunk !== void 0) {
3531
+ checkId(f.step.id, `${path}.step`);
3532
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(f.step.type, f.step.id, "chunked-foreach"), `${path}.step`, f.step.id);
3533
+ } else checkHitlArm(f.step, `${path}.step`, "foreach");
3534
+ declared.push(f.step.id);
3535
+ } else {
2667
3536
  checkSingle(f.step, `${path}.step`, o.chunk ? 2 : 1);
2668
3537
  declared.push(singleId(f.step));
2669
3538
  }
@@ -2680,52 +3549,20 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2680
3549
  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`);
2681
3550
  const bodyType = l.step.type;
2682
3551
  if (bodyType === "mapping") err("container-arm-empty", "a loop body needs a step, not a bare mapping", `${path}.step`);
2683
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path}.step`);
2684
- else {
3552
+ else if (isHitlNode(l.step)) {
3553
+ checkHitlArm(l.step, `${path}.step`, "loop");
3554
+ declared.push(l.step.id);
3555
+ } else {
2685
3556
  checkSingle(l.step, `${path}.step`, 1);
2686
3557
  declared.push(singleId(l.step));
2687
3558
  }
2688
3559
  break;
2689
3560
  }
2690
- case "approval": {
2691
- const a = entry;
2692
- checkId(a.id, path);
2693
- if (a.approver === "creator" && a.excludeInitiator === true) {
2694
- err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path, a.id);
2695
- }
2696
- if (a.fourEyes !== void 0 && a.editable !== true) {
2697
- err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path}.fourEyes`, a.id);
2698
- }
2699
- if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2700
- err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path}.editablePaths`, a.id);
2701
- }
2702
- for (const p of a.editablePaths ?? []) {
2703
- if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path}.editablePaths`, a.id);
2704
- }
2705
- if (Array.isArray(a.onTimeout)) {
2706
- const chain = a.onTimeout;
2707
- const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2708
- if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path}.onTimeout`, a.id);
2709
- const last = chain[chain.length - 1];
2710
- if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2711
- err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path}.onTimeout`, a.id);
2712
- }
2713
- }
2714
- if (typeof a.details === "string") {
2715
- for (const ref of templateStepRefs(a.details)) {
2716
- if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path, a.id);
2717
- }
2718
- }
2719
- declared.push(a.id);
2720
- break;
2721
- }
2722
- case "waitForSignal": {
2723
- const w = entry;
2724
- checkId(w.id, path);
2725
- if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path}.signal`, w.id);
2726
- declared.push(w.id);
3561
+ case "approval":
3562
+ case "waitForSignal":
3563
+ checkHitl(entry, path);
3564
+ declared.push(entry.id);
2727
3565
  break;
2728
- }
2729
3566
  default:
2730
3567
  err("invalid-envelope", `unknown entry type "${entry.type}"`, path);
2731
3568
  }
@@ -2846,14 +3683,9 @@ var WorkflowPlanError = class extends Error {
2846
3683
  this.name = "WorkflowPlanError";
2847
3684
  }
2848
3685
  };
2849
- var SINGLE_STEP_KINDS = {
2850
- step: "code",
2851
- agent: "agent",
2852
- tool: "tool",
2853
- workflow: "subrun"
2854
- };
2855
- var isSingleStep = /* @__PURE__ */ __name3((e) => e.type === "step" || e.type === "agent" || e.type === "tool" || e.type === "workflow", "isSingleStep");
2856
- var singleStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "singleStepId");
3686
+ var isArmStep = /* @__PURE__ */ __name3((e) => isWorkflowArmEntryType(e.type), "isArmStep");
3687
+ var armStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "armStepId");
3688
+ var armStepKind = /* @__PURE__ */ __name3((e) => WORKFLOW_ARM_ENTRY_STEP_KINDS[e.type], "armStepKind");
2857
3689
  var joinIdOf = /* @__PURE__ */ __name3((entryId) => `${entryId}.join`, "joinIdOf");
2858
3690
  var containerIdOf = /* @__PURE__ */ __name3((type, entryIndex) => `${type}@${entryIndex}`, "containerIdOf");
2859
3691
  function compilePlan(g) {
@@ -2869,10 +3701,10 @@ function compilePlan(g) {
2869
3701
  let prevTails = [];
2870
3702
  const foreachJoinToEntry = /* @__PURE__ */ new Map();
2871
3703
  g.definition.graph.forEach((entry, entryIndex) => {
2872
- if (isSingleStep(entry)) {
2873
- const id = singleStepId(entry);
3704
+ if (isArmStep(entry)) {
3705
+ const id = armStepId(entry);
2874
3706
  addNode(id, {
2875
- kind: SINGLE_STEP_KINDS[entry.type],
3707
+ kind: armStepKind(entry),
2876
3708
  dependsOn: prevTails,
2877
3709
  downstream: [],
2878
3710
  unsatisfiedDeps: prevTails.length,
@@ -2897,9 +3729,8 @@ function compilePlan(g) {
2897
3729
  ];
2898
3730
  return;
2899
3731
  case "sleep":
2900
- case "sleepUntil":
2901
- case "approval": {
2902
- const kind = entry.type === "approval" ? "approval" : entry.type;
3732
+ case "sleepUntil": {
3733
+ const kind = entry.type;
2903
3734
  addNode(entry.id, {
2904
3735
  kind,
2905
3736
  dependsOn: prevTails,
@@ -2912,24 +3743,12 @@ function compilePlan(g) {
2912
3743
  ];
2913
3744
  return;
2914
3745
  }
2915
- case "waitForSignal":
2916
- addNode(entry.id, {
2917
- kind: "signal",
2918
- dependsOn: prevTails,
2919
- downstream: [],
2920
- unsatisfiedDeps: prevTails.length,
2921
- entry
2922
- });
2923
- prevTails = [
2924
- entry.id
2925
- ];
2926
- return;
2927
3746
  case "parallel": {
2928
3747
  const entryId = containerIdOf("parallel", entryIndex);
2929
3748
  const childIds = entry.steps.map((arm) => {
2930
- const childId = singleStepId(arm);
3749
+ const childId = armStepId(arm);
2931
3750
  addNode(childId, {
2932
- kind: SINGLE_STEP_KINDS[arm.type],
3751
+ kind: armStepKind(arm),
2933
3752
  dependsOn: prevTails,
2934
3753
  downstream: [],
2935
3754
  unsatisfiedDeps: prevTails.length,
@@ -2967,9 +3786,9 @@ function compilePlan(g) {
2967
3786
  node.otherwise
2968
3787
  ] : node.steps;
2969
3788
  const childIds = arms.map((arm) => {
2970
- const childId = arm.type === "mapping" ? arm.id : singleStepId(arm);
3789
+ const childId = arm.type === "mapping" ? arm.id : armStepId(arm);
2971
3790
  addNode(childId, {
2972
- kind: arm.type === "mapping" ? "map" : SINGLE_STEP_KINDS[arm.type],
3791
+ kind: arm.type === "mapping" ? "map" : armStepKind(arm),
2973
3792
  dependsOn: [
2974
3793
  entryId
2975
3794
  ],
@@ -3062,496 +3881,274 @@ function resolvePath(rawPath, ctx) {
3062
3881
  root = ctx.inputData;
3063
3882
  break;
3064
3883
  case "state":
3065
- root = ctx.state;
3066
- break;
3067
- case "stepResults": {
3068
- if (!rest) return MISSING;
3069
- const innerDot = rest.indexOf(".");
3070
- const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
3071
- const subPath = innerDot === -1 ? "" : rest.slice(innerDot + 1);
3072
- let stepResult;
3073
- if (ctx.stepResults && stepId in ctx.stepResults) stepResult = ctx.stepResults[stepId];
3074
- else if (ctx.getStepResult) {
3075
- try {
3076
- stepResult = ctx.getStepResult(stepId);
3077
- } catch {
3078
- return MISSING;
3079
- }
3080
- } else return MISSING;
3081
- if (stepResult === void 0 || stepResult === null) return MISSING;
3082
- return walk(stepResult, subPath);
3083
- }
3084
- default:
3085
- return MISSING;
3086
- }
3087
- return walk(root, rest);
3088
- }
3089
- __name(resolvePath, "resolvePath");
3090
- __name3(resolvePath, "resolvePath");
3091
- function walk(root, path) {
3092
- if (path === "") return root;
3093
- const parts = path.split(".");
3094
- let value22 = root;
3095
- for (const part of parts) {
3096
- if (value22 === null || value22 === void 0) return MISSING;
3097
- if (typeof value22 !== "object") return MISSING;
3098
- const record = value22;
3099
- if (!(part in record)) return MISSING;
3100
- value22 = record[part];
3101
- }
3102
- return value22;
3103
- }
3104
- __name(walk, "walk");
3105
- __name3(walk, "walk");
3106
- function resolveValue(ref, ctx) {
3107
- if ("literal" in ref) return ref.literal;
3108
- return resolvePath(ref.path, ctx);
3109
- }
3110
- __name(resolveValue, "resolveValue");
3111
- __name3(resolveValue, "resolveValue");
3112
- function evaluatePredicate(pred, ctx) {
3113
- switch (pred.op) {
3114
- case "and":
3115
- return pred.args.every((arg) => evaluatePredicate(arg, ctx));
3116
- case "or":
3117
- return pred.args.some((arg) => evaluatePredicate(arg, ctx));
3118
- case "not":
3119
- return !evaluatePredicate(pred.arg, ctx);
3120
- case "exists":
3121
- return resolvePath(pred.path, ctx) !== MISSING;
3122
- case "notExists":
3123
- return resolvePath(pred.path, ctx) === MISSING;
3124
- case "truthy":
3125
- case "falsy": {
3126
- const v = resolveValue(pred.value, ctx);
3127
- const truthy22 = v !== MISSING && Boolean(v);
3128
- return pred.op === "truthy" ? truthy22 : !truthy22;
3129
- }
3130
- case "in":
3131
- case "notIn": {
3132
- const v = resolveValue(pred.value, ctx);
3133
- if (v === MISSING) return pred.op === "notIn";
3134
- const member = pred.set.some((candidate) => candidate === v);
3135
- return pred.op === "in" ? member : !member;
3136
- }
3137
- case "eq":
3138
- case "ne":
3139
- case "lt":
3140
- case "lte":
3141
- case "gt":
3142
- case "gte": {
3143
- const left = resolveValue(pred.left, ctx);
3144
- const right = resolveValue(pred.right, ctx);
3145
- if (left === MISSING || right === MISSING) return false;
3146
- return compare(pred.op, left, right);
3147
- }
3148
- }
3149
- }
3150
- __name(evaluatePredicate, "evaluatePredicate");
3151
- __name3(evaluatePredicate, "evaluatePredicate");
3152
- function compare(op, left, right) {
3153
- if (op === "eq") return left === right;
3154
- if (op === "ne") return left !== right;
3155
- if (typeof left === "number" && typeof right === "number" || typeof left === "string" && typeof right === "string") {
3156
- switch (op) {
3157
- case "lt":
3158
- return left < right;
3159
- case "lte":
3160
- return left <= right;
3161
- case "gt":
3162
- return left > right;
3163
- case "gte":
3164
- return left >= right;
3165
- }
3166
- }
3167
- return false;
3168
- }
3169
- __name(compare, "compare");
3170
- __name3(compare, "compare");
3171
- function derivePredicateLabel(pred, maxLength = 80) {
3172
- const raw = renderPredicate(pred);
3173
- if (raw.length <= maxLength) return raw;
3174
- return raw.slice(0, maxLength - 1) + "\u2026";
3175
- }
3176
- __name(derivePredicateLabel, "derivePredicateLabel");
3177
- __name3(derivePredicateLabel, "derivePredicateLabel");
3178
- function renderPredicate(pred) {
3179
- switch (pred.op) {
3180
- case "and":
3181
- case "or":
3182
- return pred.args.map((arg) => wrapLabel(arg, renderPredicate(arg))).join(pred.op === "and" ? " AND " : " OR ");
3183
- case "not":
3184
- return `NOT ${wrapLabel(pred.arg, renderPredicate(pred.arg))}`;
3185
- case "exists":
3186
- return `${pred.path} exists`;
3187
- case "notExists":
3188
- return `${pred.path} missing`;
3189
- case "truthy":
3190
- return `${renderRef(pred.value)} is truthy`;
3191
- case "falsy":
3192
- return `${renderRef(pred.value)} is falsy`;
3193
- case "in":
3194
- return `${renderRef(pred.value)} in ${JSON.stringify(pred.set)}`;
3195
- case "notIn":
3196
- return `${renderRef(pred.value)} not in ${JSON.stringify(pred.set)}`;
3197
- case "eq":
3198
- return `${renderRef(pred.left)} == ${renderRef(pred.right)}`;
3199
- case "ne":
3200
- return `${renderRef(pred.left)} != ${renderRef(pred.right)}`;
3201
- case "lt":
3202
- return `${renderRef(pred.left)} < ${renderRef(pred.right)}`;
3203
- case "lte":
3204
- return `${renderRef(pred.left)} <= ${renderRef(pred.right)}`;
3205
- case "gt":
3206
- return `${renderRef(pred.left)} > ${renderRef(pred.right)}`;
3207
- case "gte":
3208
- return `${renderRef(pred.left)} >= ${renderRef(pred.right)}`;
3209
- }
3210
- }
3211
- __name(renderPredicate, "renderPredicate");
3212
- __name3(renderPredicate, "renderPredicate");
3213
- function wrapLabel(child, rendered) {
3214
- return child.op === "and" || child.op === "or" || child.op === "not" ? `(${rendered})` : rendered;
3215
- }
3216
- __name(wrapLabel, "wrapLabel");
3217
- __name3(wrapLabel, "wrapLabel");
3218
- function renderRef(ref) {
3219
- if ("literal" in ref) return JSON.stringify(ref.literal);
3220
- return ref.path;
3221
- }
3222
- __name(renderRef, "renderRef");
3223
- __name3(renderRef, "renderRef");
3224
- var stepIdOf = /* @__PURE__ */ __name3((s) => typeof s === "string" ? s : s.id, "stepIdOf");
3225
- function step(s) {
3226
- const id = stepIdOf(s);
3227
- return {
3228
- path: /* @__PURE__ */ __name3((p) => ({
3229
- path: p === "" ? `stepResults.${id}` : `stepResults.${id}.${p}`
3230
- }), "path")
3231
- };
3232
- }
3233
- __name(step, "step");
3234
- __name3(step, "step");
3235
- function stepOf(id) {
3236
- return step(id);
3237
- }
3238
- __name(stepOf, "stepOf");
3239
- __name3(stepOf, "stepOf");
3240
- function init(path) {
3241
- return {
3242
- path: path === "" ? "initData" : `initData.${path}`
3243
- };
3244
- }
3245
- __name(init, "init");
3246
- __name3(init, "init");
3247
- function state(path) {
3248
- return {
3249
- path: path === "" ? "state" : `state.${path}`
3250
- };
3251
- }
3252
- __name(state, "state");
3253
- __name3(state, "state");
3254
- function lit(v) {
3255
- return {
3256
- literal: v
3257
- };
3258
- }
3259
- __name(lit, "lit");
3260
- __name3(lit, "lit");
3261
- function toPathOrLiteral(v) {
3262
- if (typeof v === "object" && v !== null) {
3263
- if ("path" in v) return {
3264
- path: v.path
3265
- };
3266
- if ("literal" in v) return {
3267
- literal: v.literal
3268
- };
3269
- }
3270
- return {
3271
- literal: v
3272
- };
3273
- }
3274
- __name(toPathOrLiteral, "toPathOrLiteral");
3275
- __name3(toPathOrLiteral, "toPathOrLiteral");
3276
- var cmp = /* @__PURE__ */ __name3((op) => (l, r) => ({
3277
- op,
3278
- left: toPathOrLiteral(l),
3279
- right: toPathOrLiteral(r)
3280
- }), "cmp");
3281
- var eq = cmp("eq");
3282
- var ne = cmp("ne");
3283
- var gt = cmp("gt");
3284
- var gte = cmp("gte");
3285
- var lt = cmp("lt");
3286
- var lte = cmp("lte");
3287
- var inSet = /* @__PURE__ */ __name3((v, set) => ({
3288
- op: "in",
3289
- value: {
3290
- path: v.path
3291
- },
3292
- set
3293
- }), "inSet");
3294
- var notIn = /* @__PURE__ */ __name3((v, set) => ({
3295
- op: "notIn",
3296
- value: {
3297
- path: v.path
3298
- },
3299
- set
3300
- }), "notIn");
3301
- var exists = /* @__PURE__ */ __name3((ref) => ({
3302
- op: "exists",
3303
- path: ref.path
3304
- }), "exists");
3305
- var notExists = /* @__PURE__ */ __name3((ref) => ({
3306
- op: "notExists",
3307
- path: ref.path
3308
- }), "notExists");
3309
- var truthy = /* @__PURE__ */ __name3((ref) => ({
3310
- op: "truthy",
3311
- value: {
3312
- path: ref.path
3313
- }
3314
- }), "truthy");
3315
- var falsy = /* @__PURE__ */ __name3((ref) => ({
3316
- op: "falsy",
3317
- value: {
3318
- path: ref.path
3319
- }
3320
- }), "falsy");
3321
- var and = /* @__PURE__ */ __name3((...args) => ({
3322
- op: "and",
3323
- args
3324
- }), "and");
3325
- var or = /* @__PURE__ */ __name3((...args) => ({
3326
- op: "or",
3327
- args
3328
- }), "or");
3329
- var not = /* @__PURE__ */ __name3((arg) => ({
3330
- op: "not",
3331
- arg
3332
- }), "not");
3333
- var WorkflowTemplateError = class extends Error {
3334
- static {
3335
- __name(this, "WorkflowTemplateError");
3336
- }
3337
- static {
3338
- __name3(this, "WorkflowTemplateError");
3339
- }
3340
- placeholder;
3341
- constructor(message, placeholder) {
3342
- super(message), this.placeholder = placeholder;
3343
- this.name = "WorkflowTemplateError";
3344
- }
3345
- };
3346
- function parseMapConfig(raw, stepId) {
3347
- try {
3348
- return JSON.parse(raw);
3349
- } catch (e) {
3350
- throw new Error(`Stored mapping step "${stepId}" has invalid JSON mapConfig: ${e.message}`);
3884
+ root = ctx.state;
3885
+ break;
3886
+ case "stepResults": {
3887
+ if (!rest) return MISSING;
3888
+ const innerDot = rest.indexOf(".");
3889
+ const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
3890
+ const subPath = innerDot === -1 ? "" : rest.slice(innerDot + 1);
3891
+ let stepResult;
3892
+ if (ctx.stepResults && stepId in ctx.stepResults) stepResult = ctx.stepResults[stepId];
3893
+ else if (ctx.getStepResult) {
3894
+ try {
3895
+ stepResult = ctx.getStepResult(stepId);
3896
+ } catch {
3897
+ return MISSING;
3898
+ }
3899
+ } else return MISSING;
3900
+ if (stepResult === void 0 || stepResult === null) return MISSING;
3901
+ return walk(stepResult, subPath);
3902
+ }
3903
+ default:
3904
+ return MISSING;
3351
3905
  }
3906
+ return walk(root, rest);
3352
3907
  }
3353
- __name(parseMapConfig, "parseMapConfig");
3354
- __name3(parseMapConfig, "parseMapConfig");
3355
- var TEMPLATE_PLACEHOLDER = /\$\{([^}]*)\}/g;
3356
- var TEMPLATE_NAMESPACES = [
3357
- "initData",
3358
- "state",
3359
- "requestContext",
3360
- "stepResults"
3361
- ];
3362
- function describeBadPlaceholder(template22, idx, rawExpr) {
3363
- return `Template placeholder #${idx} (\${${rawExpr}}) in '${template22}'`;
3364
- }
3365
- __name(describeBadPlaceholder, "describeBadPlaceholder");
3366
- __name3(describeBadPlaceholder, "describeBadPlaceholder");
3367
- function parseTemplatePlaceholder(rawExpr) {
3368
- const dot = rawExpr.indexOf(".");
3369
- return {
3370
- scope: dot === -1 ? rawExpr : rawExpr.slice(0, dot),
3371
- rest: dot === -1 ? "" : rawExpr.slice(dot + 1)
3372
- };
3373
- }
3374
- __name(parseTemplatePlaceholder, "parseTemplatePlaceholder");
3375
- __name3(parseTemplatePlaceholder, "parseTemplatePlaceholder");
3376
- function traverseMappingPath(root, path, errorLabel) {
3377
- if (path === "" || path === ".") return root;
3908
+ __name(resolvePath, "resolvePath");
3909
+ __name3(resolvePath, "resolvePath");
3910
+ function walk(root, path) {
3911
+ if (path === "") return root;
3378
3912
  const parts = path.split(".");
3379
3913
  let value22 = root;
3380
3914
  for (const part of parts) {
3381
- if (typeof value22 === "object" && value22 !== null) value22 = value22[part];
3382
- else throw new WorkflowTemplateError(`Invalid path ${path} in ${errorLabel}`, path);
3915
+ if (value22 === null || value22 === void 0) return MISSING;
3916
+ if (typeof value22 !== "object") return MISSING;
3917
+ const record = value22;
3918
+ if (!(part in record)) return MISSING;
3919
+ value22 = record[part];
3383
3920
  }
3384
3921
  return value22;
3385
3922
  }
3386
- __name(traverseMappingPath, "traverseMappingPath");
3387
- __name3(traverseMappingPath, "traverseMappingPath");
3388
- function stringifyTemplateValue(v, template22, idx, rawExpr) {
3389
- if (v === null || v === void 0) return "";
3390
- if (typeof v === "object") {
3391
- try {
3392
- return JSON.stringify(v);
3393
- } catch (err) {
3394
- throw new WorkflowTemplateError(`${describeBadPlaceholder(template22, idx, rawExpr)} resolved to a value that could not be JSON-stringified (${err.message}).`, rawExpr);
3395
- }
3396
- }
3397
- return String(v);
3398
- }
3399
- __name(stringifyTemplateValue, "stringifyTemplateValue");
3400
- __name3(stringifyTemplateValue, "stringifyTemplateValue");
3401
- function escapeFence(content) {
3402
- return content.replace(/<\/lua-data/g, "<\\/lua-data");
3403
- }
3404
- __name(escapeFence, "escapeFence");
3405
- __name3(escapeFence, "escapeFence");
3406
- function fenceBlock(name, source, content) {
3407
- return `<lua-data name="${name}" source="${source}" untrusted="true">${escapeFence(content)}</lua-data>`;
3408
- }
3409
- __name(fenceBlock, "fenceBlock");
3410
- __name3(fenceBlock, "fenceBlock");
3411
- function renderTemplate(template22, ctx, opts) {
3412
- let idx = 0;
3413
- return template22.replace(TEMPLATE_PLACEHOLDER, (_match, rawExpr) => {
3414
- idx += 1;
3415
- const { scope, rest } = parseTemplatePlaceholder(rawExpr);
3416
- const label = describeBadPlaceholder(template22, idx, rawExpr);
3417
- let rendered;
3418
- let source;
3419
- switch (scope) {
3420
- case "initData":
3421
- rendered = stringifyTemplateValue(traverseMappingPath(ctx.initData, rest, label), template22, idx, rawExpr);
3422
- source = "initData";
3423
- break;
3424
- case "state":
3425
- rendered = stringifyTemplateValue(traverseMappingPath(ctx.state, rest, label), template22, idx, rawExpr);
3426
- source = "state";
3427
- break;
3428
- case "requestContext":
3429
- rendered = stringifyTemplateValue(traverseMappingPath(ctx.requestContext, rest, label), template22, idx, rawExpr);
3430
- source = "requestContext";
3431
- break;
3432
- case "stepResults": {
3433
- const innerDot = rest.indexOf(".");
3434
- const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
3435
- const subPath = innerDot === -1 ? "" : rest.slice(innerDot + 1);
3436
- if (!stepId) throw new WorkflowTemplateError(`${label} must name a step: \${stepResults.<stepId>.<path>}.`, rawExpr);
3437
- if (!(stepId in ctx.stepResults) || ctx.stepResults[stepId] == null) {
3438
- throw new WorkflowTemplateError(`${label} references stepResults.${stepId} but step "${stepId}" has no resolvable output (not an ancestor, not run, failed, or produced no output).`, rawExpr);
3439
- }
3440
- rendered = stringifyTemplateValue(traverseMappingPath(ctx.stepResults[stepId], subPath, label), template22, idx, rawExpr);
3441
- source = `step:${stepId}`;
3442
- break;
3443
- }
3444
- default:
3445
- throw new WorkflowTemplateError(`${label} references unknown namespace "${scope}". Use one of: ${TEMPLATE_NAMESPACES.join(", ")}.`, rawExpr);
3446
- }
3447
- return opts.fenced ? fenceBlock(rawExpr, source, rendered) : rendered;
3448
- });
3923
+ __name(walk, "walk");
3924
+ __name3(walk, "walk");
3925
+ function resolveValue(ref, ctx) {
3926
+ if ("literal" in ref) return ref.literal;
3927
+ return resolvePath(ref.path, ctx);
3449
3928
  }
3450
- __name(renderTemplate, "renderTemplate");
3451
- __name3(renderTemplate, "renderTemplate");
3452
- function resolveDescriptor(key, m, ctx) {
3453
- try {
3454
- if ("value" in m) return {
3455
- value: m.value
3456
- };
3457
- if ("template" in m && typeof m.template === "string") {
3458
- return {
3459
- value: renderTemplate(m.template, ctx, {
3460
- fenced: false
3461
- })
3462
- };
3929
+ __name(resolveValue, "resolveValue");
3930
+ __name3(resolveValue, "resolveValue");
3931
+ function evaluatePredicate(pred, ctx) {
3932
+ switch (pred.op) {
3933
+ case "and":
3934
+ return pred.args.every((arg) => evaluatePredicate(arg, ctx));
3935
+ case "or":
3936
+ return pred.args.some((arg) => evaluatePredicate(arg, ctx));
3937
+ case "not":
3938
+ return !evaluatePredicate(pred.arg, ctx);
3939
+ case "exists":
3940
+ return resolvePath(pred.path, ctx) !== MISSING;
3941
+ case "notExists":
3942
+ return resolvePath(pred.path, ctx) === MISSING;
3943
+ case "truthy":
3944
+ case "falsy": {
3945
+ const v = resolveValue(pred.value, ctx);
3946
+ const truthy22 = v !== MISSING && Boolean(v);
3947
+ return pred.op === "truthy" ? truthy22 : !truthy22;
3463
3948
  }
3464
- if ("knowledge" in m || "rows" in m && m.rows !== void 0) {
3465
- return {
3466
- error: "binding_unresolved",
3467
- key
3468
- };
3949
+ case "in":
3950
+ case "notIn": {
3951
+ const v = resolveValue(pred.value, ctx);
3952
+ if (v === MISSING) return pred.op === "notIn";
3953
+ const member = pred.set.some((candidate) => candidate === v);
3954
+ return pred.op === "in" ? member : !member;
3469
3955
  }
3470
- if ("requestContextPath" in m) {
3471
- const label = `requestContext path for key "${key}"`;
3472
- return {
3473
- value: traverseMappingPath(ctx.requestContext, m.requestContextPath, label)
3474
- };
3956
+ case "eq":
3957
+ case "ne":
3958
+ case "lt":
3959
+ case "lte":
3960
+ case "gt":
3961
+ case "gte": {
3962
+ const left = resolveValue(pred.left, ctx);
3963
+ const right = resolveValue(pred.right, ctx);
3964
+ if (left === MISSING || right === MISSING) return false;
3965
+ return compare(pred.op, left, right);
3475
3966
  }
3476
- if ("path" in m) {
3477
- const source = "initData" in m && m.initData ? "initData" : "step";
3478
- if (source === "initData") {
3479
- return {
3480
- value: traverseMappingPath(ctx.initData, m.path, `initData for key "${key}"`)
3481
- };
3482
- }
3483
- const stepRef = m.step;
3484
- const candidates = Array.isArray(stepRef) ? stepRef : [
3485
- stepRef
3486
- ];
3487
- const stepId = candidates.find((s) => ctx.stepResults[s] !== void 0 && ctx.stepResults[s] !== null);
3488
- if (stepId === void 0) return {
3489
- error: "binding_unresolved",
3490
- key
3491
- };
3492
- return {
3493
- value: traverseMappingPath(ctx.stepResults[stepId], m.path, `step ${candidates.join("|")} for key "${key}"`)
3494
- };
3967
+ }
3968
+ }
3969
+ __name(evaluatePredicate, "evaluatePredicate");
3970
+ __name3(evaluatePredicate, "evaluatePredicate");
3971
+ function compare(op, left, right) {
3972
+ if (op === "eq") return left === right;
3973
+ if (op === "ne") return left !== right;
3974
+ if (typeof left === "number" && typeof right === "number" || typeof left === "string" && typeof right === "string") {
3975
+ switch (op) {
3976
+ case "lt":
3977
+ return left < right;
3978
+ case "lte":
3979
+ return left <= right;
3980
+ case "gt":
3981
+ return left > right;
3982
+ case "gte":
3983
+ return left >= right;
3495
3984
  }
3496
- return {
3497
- error: "binding_unresolved",
3498
- key
3499
- };
3500
- } catch (err) {
3501
- if (err instanceof WorkflowTemplateError) return {
3502
- error: "binding_unresolved",
3503
- key
3504
- };
3505
- throw err;
3985
+ }
3986
+ return false;
3987
+ }
3988
+ __name(compare, "compare");
3989
+ __name3(compare, "compare");
3990
+ function derivePredicateLabel(pred, maxLength = 80) {
3991
+ const raw = renderPredicate(pred);
3992
+ if (raw.length <= maxLength) return raw;
3993
+ return raw.slice(0, maxLength - 1) + "\u2026";
3994
+ }
3995
+ __name(derivePredicateLabel, "derivePredicateLabel");
3996
+ __name3(derivePredicateLabel, "derivePredicateLabel");
3997
+ function renderPredicate(pred) {
3998
+ switch (pred.op) {
3999
+ case "and":
4000
+ case "or":
4001
+ return pred.args.map((arg) => wrapLabel(arg, renderPredicate(arg))).join(pred.op === "and" ? " AND " : " OR ");
4002
+ case "not":
4003
+ return `NOT ${wrapLabel(pred.arg, renderPredicate(pred.arg))}`;
4004
+ case "exists":
4005
+ return `${pred.path} exists`;
4006
+ case "notExists":
4007
+ return `${pred.path} missing`;
4008
+ case "truthy":
4009
+ return `${renderRef(pred.value)} is truthy`;
4010
+ case "falsy":
4011
+ return `${renderRef(pred.value)} is falsy`;
4012
+ case "in":
4013
+ return `${renderRef(pred.value)} in ${JSON.stringify(pred.set)}`;
4014
+ case "notIn":
4015
+ return `${renderRef(pred.value)} not in ${JSON.stringify(pred.set)}`;
4016
+ case "eq":
4017
+ return `${renderRef(pred.left)} == ${renderRef(pred.right)}`;
4018
+ case "ne":
4019
+ return `${renderRef(pred.left)} != ${renderRef(pred.right)}`;
4020
+ case "lt":
4021
+ return `${renderRef(pred.left)} < ${renderRef(pred.right)}`;
4022
+ case "lte":
4023
+ return `${renderRef(pred.left)} <= ${renderRef(pred.right)}`;
4024
+ case "gt":
4025
+ return `${renderRef(pred.left)} > ${renderRef(pred.right)}`;
4026
+ case "gte":
4027
+ return `${renderRef(pred.left)} >= ${renderRef(pred.right)}`;
3506
4028
  }
3507
4029
  }
3508
- __name(resolveDescriptor, "resolveDescriptor");
3509
- __name3(resolveDescriptor, "resolveDescriptor");
3510
- function resolveMapping(cfg, ctx) {
3511
- const keys = Object.keys(cfg);
3512
- if (keys.length === 1 && keys[0] === "") {
3513
- return resolveDescriptor("", cfg[""], ctx);
3514
- }
3515
- const result = {};
3516
- for (const key of keys) {
3517
- const resolved = resolveDescriptor(key, cfg[key], ctx);
3518
- if ("error" in resolved) return resolved;
3519
- result[key] = resolved.value;
3520
- }
4030
+ __name(renderPredicate, "renderPredicate");
4031
+ __name3(renderPredicate, "renderPredicate");
4032
+ function wrapLabel(child, rendered) {
4033
+ return child.op === "and" || child.op === "or" || child.op === "not" ? `(${rendered})` : rendered;
4034
+ }
4035
+ __name(wrapLabel, "wrapLabel");
4036
+ __name3(wrapLabel, "wrapLabel");
4037
+ function renderRef(ref) {
4038
+ if ("literal" in ref) return JSON.stringify(ref.literal);
4039
+ return ref.path;
4040
+ }
4041
+ __name(renderRef, "renderRef");
4042
+ __name3(renderRef, "renderRef");
4043
+ var stepIdOf = /* @__PURE__ */ __name3((s) => typeof s === "string" ? s : s.id, "stepIdOf");
4044
+ function step(s) {
4045
+ const id = stepIdOf(s);
3521
4046
  return {
3522
- value: result
4047
+ path: /* @__PURE__ */ __name3((p) => ({
4048
+ path: p === "" ? `stepResults.${id}` : `stepResults.${id}.${p}`
4049
+ }), "path")
3523
4050
  };
3524
4051
  }
3525
- __name(resolveMapping, "resolveMapping");
3526
- __name3(resolveMapping, "resolveMapping");
3527
- var fromInit = /* @__PURE__ */ __name3((path) => ({
3528
- initData: true,
3529
- path
3530
- }), "fromInit");
3531
- var fromStep = /* @__PURE__ */ __name3((s, path = "") => {
3532
- const idOf = /* @__PURE__ */ __name3((x) => typeof x === "string" ? x : x.id, "idOf");
4052
+ __name(step, "step");
4053
+ __name3(step, "step");
4054
+ function stepOf(id) {
4055
+ return step(id);
4056
+ }
4057
+ __name(stepOf, "stepOf");
4058
+ __name3(stepOf, "stepOf");
4059
+ function init(path) {
3533
4060
  return {
3534
- step: Array.isArray(s) ? s.map(idOf) : idOf(s),
3535
- path
4061
+ path: path === "" ? "initData" : `initData.${path}`
3536
4062
  };
3537
- }, "fromStep");
3538
- var value = /* @__PURE__ */ __name3((v) => ({
3539
- value: v
3540
- }), "value");
3541
- var template = /* @__PURE__ */ __name3((s) => ({
3542
- template: s
3543
- }), "template");
3544
- var fromRequest = /* @__PURE__ */ __name3((path) => ({
3545
- requestContextPath: path
3546
- }), "fromRequest");
3547
- var rows = /* @__PURE__ */ __name3((s, path, page) => ({
3548
- step: typeof s === "string" ? s : s.id,
3549
- path,
3550
- rows: page
3551
- }), "rows");
3552
- var fromKnowledge = /* @__PURE__ */ __name3((k) => ({
3553
- knowledge: k
3554
- }), "fromKnowledge");
4063
+ }
4064
+ __name(init, "init");
4065
+ __name3(init, "init");
4066
+ function state(path) {
4067
+ return {
4068
+ path: path === "" ? "state" : `state.${path}`
4069
+ };
4070
+ }
4071
+ __name(state, "state");
4072
+ __name3(state, "state");
4073
+ function lit(v) {
4074
+ return {
4075
+ literal: v
4076
+ };
4077
+ }
4078
+ __name(lit, "lit");
4079
+ __name3(lit, "lit");
4080
+ function toPathOrLiteral(v) {
4081
+ if (typeof v === "object" && v !== null) {
4082
+ if ("path" in v) return {
4083
+ path: v.path
4084
+ };
4085
+ if ("literal" in v) return {
4086
+ literal: v.literal
4087
+ };
4088
+ }
4089
+ return {
4090
+ literal: v
4091
+ };
4092
+ }
4093
+ __name(toPathOrLiteral, "toPathOrLiteral");
4094
+ __name3(toPathOrLiteral, "toPathOrLiteral");
4095
+ var cmp = /* @__PURE__ */ __name3((op) => (l, r) => ({
4096
+ op,
4097
+ left: toPathOrLiteral(l),
4098
+ right: toPathOrLiteral(r)
4099
+ }), "cmp");
4100
+ var eq = cmp("eq");
4101
+ var ne = cmp("ne");
4102
+ var gt = cmp("gt");
4103
+ var gte = cmp("gte");
4104
+ var lt = cmp("lt");
4105
+ var lte = cmp("lte");
4106
+ var inSet = /* @__PURE__ */ __name3((v, set) => ({
4107
+ op: "in",
4108
+ value: {
4109
+ path: v.path
4110
+ },
4111
+ set
4112
+ }), "inSet");
4113
+ var notIn = /* @__PURE__ */ __name3((v, set) => ({
4114
+ op: "notIn",
4115
+ value: {
4116
+ path: v.path
4117
+ },
4118
+ set
4119
+ }), "notIn");
4120
+ var exists = /* @__PURE__ */ __name3((ref) => ({
4121
+ op: "exists",
4122
+ path: ref.path
4123
+ }), "exists");
4124
+ var notExists = /* @__PURE__ */ __name3((ref) => ({
4125
+ op: "notExists",
4126
+ path: ref.path
4127
+ }), "notExists");
4128
+ var truthy = /* @__PURE__ */ __name3((ref) => ({
4129
+ op: "truthy",
4130
+ value: {
4131
+ path: ref.path
4132
+ }
4133
+ }), "truthy");
4134
+ var falsy = /* @__PURE__ */ __name3((ref) => ({
4135
+ op: "falsy",
4136
+ value: {
4137
+ path: ref.path
4138
+ }
4139
+ }), "falsy");
4140
+ var and = /* @__PURE__ */ __name3((...args) => ({
4141
+ op: "and",
4142
+ args
4143
+ }), "and");
4144
+ var or = /* @__PURE__ */ __name3((...args) => ({
4145
+ op: "or",
4146
+ args
4147
+ }), "or");
4148
+ var not = /* @__PURE__ */ __name3((arg) => ({
4149
+ op: "not",
4150
+ arg
4151
+ }), "not");
3555
4152
  var CONTINUED_FAILURE_TAG = "continued_failure";
3556
4153
  var CONTINUED_FAILURE_DEFAULT_CODE = "step_failed";
3557
4154
  var CONTINUED_FAILURE_OUTPUT_SCHEMA = Object.freeze({
@@ -3622,6 +4219,7 @@ function isContinuedFailureValue(v) {
3622
4219
  }
3623
4220
  __name(isContinuedFailureValue, "isContinuedFailureValue");
3624
4221
  __name3(isContinuedFailureValue, "isContinuedFailureValue");
4222
+ var isHitlNode2 = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
3625
4223
  function lowerContainerArm(mapping, step22) {
3626
4224
  const stepId = nodeIdOf(step22);
3627
4225
  return {
@@ -3707,7 +4305,28 @@ function resolvePlacements(calls) {
3707
4305
  break;
3708
4306
  }
3709
4307
  });
3710
- const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping) => {
4308
+ const hitlPlacementIssue = /* @__PURE__ */ __name3((node, ref, i, container) => {
4309
+ if (!isHitlNode2(node)) return void 0;
4310
+ const id = node.id;
4311
+ if (ref.armMap) {
4312
+ return {
4313
+ code: "node-type-unsupported-in-container",
4314
+ message: workflowHitlArmShapeMessage(node.type, id, "mapped-arm"),
4315
+ callIndex: i,
4316
+ stepId: id
4317
+ };
4318
+ }
4319
+ if (container !== "place" && !workflowContainerRunsHitlArm(container)) {
4320
+ return {
4321
+ code: "node-type-unsupported-in-container",
4322
+ message: workflowHitlArmUnsupportedMessage(node.type, id, container),
4323
+ callIndex: i,
4324
+ stepId: id
4325
+ };
4326
+ }
4327
+ return void 0;
4328
+ }, "hitlPlacementIssue");
4329
+ const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
3711
4330
  if ("node" in ref) {
3712
4331
  if (ref.node.type === "mapping" && !allowMapping) {
3713
4332
  issues.push({
@@ -3724,7 +4343,7 @@ function resolvePlacements(calls) {
3724
4343
  if (!d) {
3725
4344
  issues.push({
3726
4345
  code: "unknown-step-ref",
3727
- message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026)`,
4346
+ 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)`,
3728
4347
  callIndex: i,
3729
4348
  stepId: ref.ref
3730
4349
  });
@@ -3739,6 +4358,11 @@ function resolvePlacements(calls) {
3739
4358
  });
3740
4359
  return void 0;
3741
4360
  }
4361
+ const hitl = hitlPlacementIssue(d.node, ref, i, container);
4362
+ if (hitl) {
4363
+ issues.push(hitl);
4364
+ return void 0;
4365
+ }
3742
4366
  const prior = placedBy.get(ref.ref);
3743
4367
  if (prior !== void 0 && prior !== i) {
3744
4368
  issues.push({
@@ -3752,23 +4376,31 @@ function resolvePlacements(calls) {
3752
4376
  placedBy.set(ref.ref, i);
3753
4377
  return d.node;
3754
4378
  }, "resolve");
4379
+ const claim = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
4380
+ if ("ref" in ref) {
4381
+ resolve(ref, i, allowMapping, container);
4382
+ return;
4383
+ }
4384
+ const hitl = hitlPlacementIssue(ref.node, ref, i, container);
4385
+ if (hitl) issues.push(hitl);
4386
+ }, "claim");
3755
4387
  calls.forEach((call, i) => {
3756
4388
  switch (call.kind) {
3757
4389
  case "place":
3758
- resolve({
4390
+ claim({
3759
4391
  ref: call.ref
3760
- }, i, false);
4392
+ }, i, false, "place");
3761
4393
  break;
3762
4394
  case "parallel":
3763
- for (const arm of call.arms) if ("ref" in arm) resolve(arm, i, false);
4395
+ for (const arm of call.arms) claim(arm, i, false, "parallel");
3764
4396
  break;
3765
4397
  case "conditional":
3766
- for (const a of call.arms) if ("ref" in a.target) resolve(a.target, i, true);
3767
- if (call.otherwise && "ref" in call.otherwise) resolve(call.otherwise, i, true);
4398
+ for (const a of call.arms) claim(a.target, i, true, "conditional");
4399
+ if (call.otherwise) claim(call.otherwise, i, true, "conditional");
3768
4400
  break;
3769
4401
  case "foreach":
3770
4402
  case "loop":
3771
- if ("ref" in call.body) resolve(call.body, i, false);
4403
+ claim(call.body, i, false, call.kind);
3772
4404
  break;
3773
4405
  default:
3774
4406
  break;
@@ -3777,7 +4409,7 @@ function resolvePlacements(calls) {
3777
4409
  const graph = [];
3778
4410
  const lookup = /* @__PURE__ */ __name3((ref) => {
3779
4411
  const n2 = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3780
- if (!n2 || !ref.armMap || n2.type === "mapping") return n2;
4412
+ if (!n2 || !ref.armMap || n2.type === "mapping" || isHitlNode2(n2)) return n2;
3781
4413
  return lowerContainerArm(ref.armMap, n2);
3782
4414
  }, "lookup");
3783
4415
  calls.forEach((call, i) => {
@@ -3859,6 +4491,75 @@ function resolvePlacements(calls) {
3859
4491
  }
3860
4492
  __name(resolvePlacements, "resolvePlacements");
3861
4493
  __name3(resolvePlacements, "resolvePlacements");
4494
+ var GOAL_JUDGE_STEP_ID = "__goal_judge";
4495
+ var NON_LEAF_KINDS = /* @__PURE__ */ new Set([
4496
+ "foreach",
4497
+ "branch"
4498
+ ]);
4499
+ var CONDITIONAL_JOIN_ID = /^conditional@\d+\.join$/;
4500
+ function isConditionalJoinId(stepId) {
4501
+ return CONDITIONAL_JOIN_ID.test(stepId);
4502
+ }
4503
+ __name(isConditionalJoinId, "isConditionalJoinId");
4504
+ __name3(isConditionalJoinId, "isConditionalJoinId");
4505
+ function isPlainObject(v) {
4506
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4507
+ }
4508
+ __name(isPlainObject, "isPlainObject");
4509
+ __name3(isPlainObject, "isPlainObject");
4510
+ function leafValue(row) {
4511
+ if (row.status === "completed") return row.output === void 0 ? null : row.output;
4512
+ return continuedFailureValue(row.error, row.killReason);
4513
+ }
4514
+ __name(leafValue, "leafValue");
4515
+ __name3(leafValue, "leafValue");
4516
+ function runOutputLeaves(steps) {
4517
+ const dependedOn = /* @__PURE__ */ new Set();
4518
+ for (const s of steps) {
4519
+ if (s.stepId === GOAL_JUDGE_STEP_ID) continue;
4520
+ for (const d of s.dependsOn ?? []) dependedOn.add(d);
4521
+ }
4522
+ 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));
4523
+ }
4524
+ __name(runOutputLeaves, "runOutputLeaves");
4525
+ __name3(runOutputLeaves, "runOutputLeaves");
4526
+ function deriveRunOutput(steps) {
4527
+ const leaves = runOutputLeaves(steps);
4528
+ if (leaves.length === 0) return void 0;
4529
+ if (leaves.length === 1) {
4530
+ const leaf = leaves[0];
4531
+ const value22 = leafValue(leaf);
4532
+ if (isConditionalJoinId(leaf.stepId) && isPlainObject(value22)) {
4533
+ const keys = Object.keys(value22);
4534
+ if (keys.length === 1) return {
4535
+ output: value22[keys[0]],
4536
+ leafIds: [
4537
+ keys[0]
4538
+ ]
4539
+ };
4540
+ }
4541
+ return {
4542
+ output: value22,
4543
+ leafIds: [
4544
+ leaf.stepId
4545
+ ]
4546
+ };
4547
+ }
4548
+ const output = {};
4549
+ for (const leaf of leaves) output[leaf.stepId] = leafValue(leaf);
4550
+ return {
4551
+ output,
4552
+ leafIds: leaves.map((l) => l.stepId)
4553
+ };
4554
+ }
4555
+ __name(deriveRunOutput, "deriveRunOutput");
4556
+ __name3(deriveRunOutput, "deriveRunOutput");
4557
+ function subrunSettledOutput(child) {
4558
+ if (child.output !== void 0) return child.output;
4559
+ return deriveRunOutput(child.steps ?? [])?.output ?? null;
4560
+ }
4561
+ __name(subrunSettledOutput, "subrunSettledOutput");
4562
+ __name3(subrunSettledOutput, "subrunSettledOutput");
3862
4563
  function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3863
4564
  const byId = /* @__PURE__ */ new Map();
3864
4565
  for (const s of steps) {
@@ -4027,6 +4728,15 @@ function replayLedger(g, ledger) {
4027
4728
  local,
4028
4729
  diverged: recordedCount !== local
4029
4730
  });
4731
+ } else if (node.kind === "subrun" && recorded.status === "completed" && recorded.child) {
4732
+ const local = subrunSettledOutput(recorded.child);
4733
+ verdicts.push({
4734
+ stepId: id,
4735
+ kind: "subrun",
4736
+ recorded: recorded.output,
4737
+ local,
4738
+ diverged: canonical(recorded.output) !== canonical(local)
4739
+ });
4030
4740
  }
4031
4741
  }
4032
4742
  return {
@@ -4198,10 +4908,57 @@ function runCounts(counts) {
4198
4908
  }
4199
4909
  __name(runCounts, "runCounts");
4200
4910
  __name3(runCounts, "runCounts");
4201
- function runUsage(run) {
4911
+ function isPricedStepReceipt(receipt) {
4912
+ return typeof receipt?.multiplier === "number" && Number.isFinite(receipt.multiplier);
4913
+ }
4914
+ __name(isPricedStepReceipt, "isPricedStepReceipt");
4915
+ __name3(isPricedStepReceipt, "isPricedStepReceipt");
4916
+ function receiptEngine(engine) {
4917
+ if (engine === "actions") return "seat";
4918
+ if (engine === "credits") return "legacy";
4919
+ return void 0;
4920
+ }
4921
+ __name(receiptEngine, "receiptEngine");
4922
+ __name3(receiptEngine, "receiptEngine");
4923
+ function receiptTier(tier) {
4924
+ return tier === "light" || tier === "standard" || tier === "heavy" ? tier : void 0;
4925
+ }
4926
+ __name(receiptTier, "receiptTier");
4927
+ __name3(receiptTier, "receiptTier");
4928
+ function stepBillingView(receipt) {
4929
+ const engine = receiptEngine(receipt?.engine);
4930
+ if (!receipt || engine === void 0) return void 0;
4931
+ return pruneUndefined({
4932
+ engine,
4933
+ attempt: typeof receipt.attempt === "number" ? receipt.attempt : void 0,
4934
+ credits: typeof receipt.credits === "number" ? receipt.credits : void 0,
4935
+ actions: typeof receipt.actionsEstimate === "number" ? receipt.actionsEstimate : void 0,
4936
+ model: typeof receipt.model === "string" ? receipt.model : void 0,
4937
+ tier: receiptTier(receipt.tier),
4938
+ multiplier: typeof receipt.multiplier === "number" ? receipt.multiplier : void 0,
4939
+ byok: typeof receipt.byok === "boolean" ? receipt.byok : void 0,
4940
+ calibrated: typeof receipt.calibrated === "boolean" ? receipt.calibrated : void 0
4941
+ });
4942
+ }
4943
+ __name(stepBillingView, "stepBillingView");
4944
+ __name3(stepBillingView, "stepBillingView");
4945
+ function runUsage(run, receipts) {
4946
+ const actions = n(run.budget?.spent?.actionsEstimate);
4947
+ const stamped = run.budget?.engine;
4948
+ const priced = (receipts ?? []).filter(isPricedStepReceipt);
4949
+ const engine = stamped === "seat" || stamped === "legacy" ? stamped : actions > 0 || priced.some((r) => r.engine === "actions") ? "seat" : priced.some((r) => r.engine === "credits") ? "legacy" : void 0;
4950
+ const seat = engine === "seat";
4951
+ const metering = engine ? "priced" : "flat";
4202
4952
  return {
4203
4953
  creditsUsed: run.budget?.spent?.credits ?? 0,
4204
4954
  actionsEstimate: run.budget?.spent?.actionsEstimate ?? 0,
4955
+ ...seat ? {
4956
+ actionsUsed: actions
4957
+ } : {},
4958
+ metering,
4959
+ ...engine ? {
4960
+ engine
4961
+ } : {},
4205
4962
  steps: run.budget?.spent?.steps ?? 0,
4206
4963
  inputTokens: run.usage?.inputTokens ?? 0,
4207
4964
  outputTokens: run.usage?.outputTokens ?? 0
@@ -4209,12 +4966,42 @@ function runUsage(run) {
4209
4966
  }
4210
4967
  __name(runUsage, "runUsage");
4211
4968
  __name3(runUsage, "runUsage");
4969
+ function runBudgetCap(budget) {
4970
+ const cap = budget?.maxCredits;
4971
+ return typeof cap === "number" && Number.isFinite(cap) && cap > 0 ? cap : void 0;
4972
+ }
4973
+ __name(runBudgetCap, "runBudgetCap");
4974
+ __name3(runBudgetCap, "runBudgetCap");
4975
+ function runBudgetRemaining(budget) {
4976
+ const cap = runBudgetCap(budget);
4977
+ if (cap === void 0) return void 0;
4978
+ const spent = budget?.spent;
4979
+ return Math.max(0, cap - n(spent?.credits) - n(spent?.actionsEstimate) - n(budget?.reserved));
4980
+ }
4981
+ __name(runBudgetRemaining, "runBudgetRemaining");
4982
+ __name3(runBudgetRemaining, "runBudgetRemaining");
4212
4983
  function runCancelView(cancel) {
4213
4984
  if (!cancel) return void 0;
4214
4985
  return {
4215
4986
  requestedAt: cancel.requestedAt,
4216
4987
  requestedBy: cancel.requestedBy ?? "",
4217
- forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS
4988
+ forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS,
4989
+ // LUA-686: the wall's own request (LUA-683 stamps `wall:true` from a system actor only) rides the wire, so a
4990
+ // client can tell a wall-ended `timed_out` run's audit block from a cancel it should chip. Absent, never false.
4991
+ ...cancel.wall === true ? {
4992
+ wall: true
4993
+ } : {},
4994
+ // LUA-704: a forced terminal's audit — who forced it, when, and a human's own reason text (the run's `reason`
4995
+ // is the terminal's). Absent on an unforced terminal, never null.
4996
+ ...typeof cancel.forcedAt === "number" ? {
4997
+ forcedAt: cancel.forcedAt
4998
+ } : {},
4999
+ ...typeof cancel.forcedBy === "string" && cancel.forcedBy ? {
5000
+ forcedBy: cancel.forcedBy
5001
+ } : {},
5002
+ ...typeof cancel.forceReason === "string" && cancel.forceReason ? {
5003
+ forceReason: cancel.forceReason
5004
+ } : {}
4218
5005
  };
4219
5006
  }
4220
5007
  __name(runCancelView, "runCancelView");
@@ -4237,7 +5024,6 @@ __name3(runWorkspaceView, "runWorkspaceView");
4237
5024
  function toWorkflowRunSummary(run) {
4238
5025
  const status = run.status;
4239
5026
  const principal = run.principal?.principal;
4240
- const gated = status === "gated" || status === "suspended";
4241
5027
  return pruneUndefined({
4242
5028
  runId: run.id,
4243
5029
  workflowId: run.workflowId,
@@ -4249,7 +5035,14 @@ function toWorkflowRunSummary(run) {
4249
5035
  orgId: run.orgId,
4250
5036
  spaceAgentId: run.spaceAgentId,
4251
5037
  status,
4252
- gate: gated ? run.gate : void 0,
5038
+ // LUA-702 (pass-3B D6): the gate rides the wire whenever a NON-terminal doc carries one. In the LUA-681 window
5039
+ // (an approval resolved beside an open exception park) the run is `running` AND still holds `gate{exception}`
5040
+ // until the tick tail parks it back; projecting it only on gated|suspended hid that park from R4 / the CLI /
5041
+ // the desktop (prod 2026-09-05: `status: running, gate: null`). A terminal doc carries no gate by contract
5042
+ // (`terminalizeRun` drops `gate` / `suspendedFor` in every terminal write), but a row terminalized before that
5043
+ // held — pre-LUA-677/694 docs, a script-tier backstop that passed no `unset` — may still carry a stale one:
5044
+ // hidden here, so a `failed` run never reads "Needs a decision" on R3/R4 or the CLI (#2476 review).
5045
+ gate: run.gate && !isTerminalRunStatus(status) ? run.gate : void 0,
4253
5046
  batchId: run.batchId,
4254
5047
  goalId: run.goalId,
4255
5048
  foreachOverflow: run.foreachOverflow,
@@ -4272,9 +5065,11 @@ function toWorkflowRunSummary(run) {
4272
5065
  principalKind: run.principalKind ?? "user",
4273
5066
  cancel: runCancelView(run.cancel),
4274
5067
  usage: runUsage(run),
5068
+ // LUA-697: a row persisted before the write seams (#2406 / #2465 / the script tier) leaves scrubbed here too —
5069
+ // idempotent on a scrubbed message, bounded input; an empty message falls back to the code.
4275
5070
  error: run.error ? {
4276
5071
  code: run.error.code ?? "error",
4277
- message: run.error.message ?? "",
5072
+ message: scrubStepErrorMessage(run.error.message) ?? run.error.code ?? "error",
4278
5073
  stepId: run.error.stepId
4279
5074
  } : void 0,
4280
5075
  kind: "run",
@@ -4295,6 +5090,86 @@ function toWorkflowRunSummary(run) {
4295
5090
  }
4296
5091
  __name(toWorkflowRunSummary, "toWorkflowRunSummary");
4297
5092
  __name3(toWorkflowRunSummary, "toWorkflowRunSummary");
5093
+ var STEP_ERROR_DETAIL_KEYS = [
5094
+ "reason",
5095
+ "key",
5096
+ "integrationType",
5097
+ "candidates",
5098
+ "connectionId",
5099
+ "status",
5100
+ "code",
5101
+ "providerStatus",
5102
+ "model",
5103
+ "turnIndex",
5104
+ "message",
5105
+ // LUA-655: `output_schema_invalid` — the Ajv issues ({path, message}, ≤ 20, scrubbed at the source too) and the
5106
+ // in-session repair rounds the attempt spent.
5107
+ "issues",
5108
+ "repairRounds",
5109
+ // LUA-669 (#2446 review 4): the subrun failures — `subrun_<status>` names the child run and how it ended
5110
+ // (`childStatus`, `childReason: 'max_duration'` under `subrun_timed_out`), `subrun_depth_exceeded` its `depth` /
5111
+ // `max`, and every refusal the target `workflowId`. Short scalars only: the child's whole `childError` and the
5112
+ // cycle walk's `ancestors` stay off the wire — the child run's own R4 carries its error.
5113
+ "childRunId",
5114
+ "childStatus",
5115
+ "childReason",
5116
+ "max",
5117
+ "depth",
5118
+ "workflowId",
5119
+ // LUA-696 (review 2): the `ctx.once` key of an `effect_in_doubt` park — the step site stamps it here (scrubbed)
5120
+ // beside `park.effectKey`; a key is user text and leaves scrubbed like every other string leaf.
5121
+ "effectKey"
5122
+ ];
5123
+ var STEP_ERROR_DETAIL_MAX_BYTES = 8 * 1024;
5124
+ var DETAIL_MAX_DEPTH = 4;
5125
+ var DETAIL_MAX_ITEMS = 100;
5126
+ function scrubDetailValue(value22, depth) {
5127
+ if (typeof value22 === "string") return scrubSecretText(value22);
5128
+ if (typeof value22 === "number" || typeof value22 === "boolean" || value22 === null) return value22;
5129
+ if (depth >= DETAIL_MAX_DEPTH) return void 0;
5130
+ if (Array.isArray(value22)) {
5131
+ return value22.slice(0, DETAIL_MAX_ITEMS).map((v) => scrubDetailValue(v, depth + 1)).filter((v) => v !== void 0);
5132
+ }
5133
+ if (typeof value22 === "object") {
5134
+ const out = {};
5135
+ for (const [k, v] of Object.entries(value22)) {
5136
+ const s = scrubDetailValue(v, depth + 1);
5137
+ if (s !== void 0) out[k] = s;
5138
+ }
5139
+ return out;
5140
+ }
5141
+ return void 0;
5142
+ }
5143
+ __name(scrubDetailValue, "scrubDetailValue");
5144
+ __name3(scrubDetailValue, "scrubDetailValue");
5145
+ function stepErrorDetail(error) {
5146
+ if (!error || typeof error !== "object") return void 0;
5147
+ const d = error.detail;
5148
+ if (!d || typeof d !== "object" || Array.isArray(d)) return void 0;
5149
+ const bag = d;
5150
+ const out = {};
5151
+ for (const k of STEP_ERROR_DETAIL_KEYS) {
5152
+ if (!(k in bag)) continue;
5153
+ const v = scrubDetailValue(bag[k], 1);
5154
+ if (v !== void 0) out[k] = v;
5155
+ }
5156
+ if (!Object.keys(out).length) return void 0;
5157
+ let bytes;
5158
+ try {
5159
+ bytes = new TextEncoder().encode(JSON.stringify(out)).length;
5160
+ } catch {
5161
+ return void 0;
5162
+ }
5163
+ if (bytes <= STEP_ERROR_DETAIL_MAX_BYTES) return out;
5164
+ const scalars = Object.fromEntries(Object.entries(out).filter(([, v]) => v === null || typeof v !== "object"));
5165
+ return {
5166
+ ...scalars,
5167
+ __truncated: true,
5168
+ bytes
5169
+ };
5170
+ }
5171
+ __name(stepErrorDetail, "stepErrorDetail");
5172
+ __name3(stepErrorDetail, "stepErrorDetail");
4298
5173
  var MAX_HOLIDAYS = 366;
4299
5174
  var MAX_WALK_DAYS = 400;
4300
5175
  var HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
@@ -4903,6 +5778,34 @@ var ApprovalOnTimeoutSchema = z22.union([
4903
5778
  EscalationHopSchema
4904
5779
  ])).min(1).max(ESCALATION_MAX_HOPS + 1)
4905
5780
  ]);
5781
+ var APPROVER_SPEC_SHAPES = [
5782
+ "'creator'",
5783
+ "'org-admins'",
5784
+ "{users:[userId, \u2026]}",
5785
+ "{role:roleName}",
5786
+ "{group:groupName}",
5787
+ "{governance:{policyId}}"
5788
+ ];
5789
+ var APPROVER_WRITTEN_MAX = 120;
5790
+ var USER_ID_SHAPED_RE = /^[^\s@]{1,128}$/;
5791
+ function describeApproverSpecRefusal(spec) {
5792
+ const raw = spec === void 0 ? "undefined" : JSON.stringify(spec) ?? String(spec);
5793
+ const written = raw.length > APPROVER_WRITTEN_MAX ? `${raw.slice(0, APPROVER_WRITTEN_MAX - 1)}\u2026` : raw;
5794
+ const users = typeof spec === "object" && spec !== null ? spec.users : void 0;
5795
+ const approver = typeof users === "string" && USER_ID_SHAPED_RE.test(users) ? {
5796
+ users: [
5797
+ users
5798
+ ]
5799
+ } : "creator";
5800
+ 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)}`);
5801
+ return {
5802
+ approver,
5803
+ written,
5804
+ message
5805
+ };
5806
+ }
5807
+ __name(describeApproverSpecRefusal, "describeApproverSpecRefusal");
5808
+ __name3(describeApproverSpecRefusal, "describeApproverSpecRefusal");
4906
5809
  var BINDING_ROOTS = [
4907
5810
  "initData",
4908
5811
  "stepResults",
@@ -4937,7 +5840,19 @@ function validateApproverBlock(node, opts = {
4937
5840
  if (!r.success) {
4938
5841
  const users = spec?.users;
4939
5842
  if (Array.isArray(users) && users.length > APPROVER_SPEC_MAX_USERS) push("cap-exceeded", path, `at most ${APPROVER_SPEC_MAX_USERS} users`);
4940
- else push("approver-invalid", path, r.error.issues[0]?.message ?? "invalid approver");
5843
+ else {
5844
+ const refusal = describeApproverSpecRefusal(spec);
5845
+ issues.push({
5846
+ code: "approver-invalid",
5847
+ path,
5848
+ severity: "error",
5849
+ message: refusal.message,
5850
+ repair: {
5851
+ approver: refusal.approver,
5852
+ written: refusal.written
5853
+ }
5854
+ });
5855
+ }
4941
5856
  return;
4942
5857
  }
4943
5858
  const s = r.data;
@@ -5449,7 +6364,8 @@ var assertPredicate = /* @__PURE__ */ __name((p, where) => {
5449
6364
  }, "assertPredicate");
5450
6365
  var assertRetry = /* @__PURE__ */ __name((r, id) => {
5451
6366
  if (!r) return;
5452
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.backoff must be 'fixed' | 'exponential'`);
6367
+ if (typeof r.maxAttempts === "number" && r.maxAttempts > WORKFLOW_RETRY_MAX_ATTEMPTS) throw new LuaWorkflowBuildError("cap-exceeded", `"${id}": ${workflowRetryMaxAttemptsMessage(r.maxAttempts)}`);
6368
+ 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(" | ")}`);
5453
6369
  if (r.maxBackoffSeconds !== void 0) {
5454
6370
  if (r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.maxBackoffSeconds is only meaningful with backoff:'exponential'`);
5455
6371
  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`);
@@ -5637,8 +6553,14 @@ function materializeEntry(entry, steps) {
5637
6553
  }
5638
6554
  }
5639
6555
  __name(materializeEntry, "materializeEntry");
6556
+ var isHitlEntry = /* @__PURE__ */ __name((n2) => isWorkflowHitlEntryType(n2?.type), "isHitlEntry");
5640
6557
  function graphHasHitl(graph, steps) {
5641
- if (graph.some((e) => e.type === "approval" || e.type === "waitForSignal")) return true;
6558
+ for (const e of graph) {
6559
+ if (isHitlEntry(e)) return true;
6560
+ if (e.type === "parallel" && e.steps.some(isHitlEntry)) return true;
6561
+ if (e.type === "conditional" && (e.steps.some(isHitlEntry) || isHitlEntry(e.otherwise))) return true;
6562
+ if ((e.type === "foreach" || e.type === "loop") && isHitlEntry(e.step)) return true;
6563
+ }
5642
6564
  return Object.values(steps).some((s) => s.suspendSchema !== void 0);
5643
6565
  }
5644
6566
  __name(graphHasHitl, "graphHasHitl");
@@ -5693,7 +6615,6 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5693
6615
  const [mapping, target] = arm;
5694
6616
  if (target === void 0 || arm.length < 2) throw new LuaWorkflowBuildError("container-arm-empty", `${where}: a bare mapping arm has nothing to run`);
5695
6617
  if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) throw new LuaWorkflowBuildError("mapping-placement", `${where}: the arm head must be a map config object`);
5696
- if (mapping.type === "approval" || target.type === "approval") throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
5697
6618
  assertNoClosure(mapping, `${where} arm map`);
5698
6619
  this.recordEnvRefs(mapping);
5699
6620
  const inner = this.armRef(target, where);
@@ -5711,8 +6632,9 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5711
6632
  if (typeof arm === "string") return {
5712
6633
  ref: arm
5713
6634
  };
5714
- if (arm && typeof arm === "object" && arm.type === "approval") {
5715
- throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
6635
+ if (arm && typeof arm === "object" && isHitlEntry(arm)) {
6636
+ const id = arm.id;
6637
+ 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`);
5716
6638
  }
5717
6639
  return {
5718
6640
  node: this.registerStep(arm, where)
@@ -5913,17 +6835,13 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5913
6835
  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'`);
5914
6836
  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`);
5915
6837
  if (opts.toolScope?.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${id}": toolScope.jobTools require tier:'job' (job-tools-require-job-tier)`);
5916
- if (opts.maxTurns !== void 0) {
5917
- if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxTurns is only legal on a tier:'job' agent step`);
5918
- 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`);
5919
- }
5920
- if (opts.maxMessages !== void 0) {
5921
- if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxMessages is only legal on a tier:'job' agent step`);
5922
- if (!Number.isInteger(opts.maxMessages) || opts.maxMessages < 1 || opts.maxMessages > 5e3) throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": maxMessages must be an integer 1..5000`);
5923
- }
5924
- if (opts.maxInputTokens !== void 0) {
5925
- if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxInputTokens is only legal on a tier:'job' agent step`);
5926
- if (!Number.isInteger(opts.maxInputTokens) || opts.maxInputTokens < 1e6 || opts.maxInputTokens > 5e8) throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": maxInputTokens must be an integer 1000000..500000000`);
6838
+ for (const m of WORKFLOW_JOB_RANGE_MEMBERS) {
6839
+ if (opts[m] === void 0) continue;
6840
+ if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": ${m} is only legal on a tier:'job' agent step`);
6841
+ if (!isWithinWorkflowJobRange(m, opts[m])) {
6842
+ const { min, max } = WORKFLOW_JOB_RANGES[m];
6843
+ throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": ${m} must be an integer ${min}..${max}`);
6844
+ }
5927
6845
  }
5928
6846
  assertTimeout({
5929
6847
  id,
@@ -6075,8 +6993,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6075
6993
  itemTimeout: opts.itemTimeout
6076
6994
  });
6077
6995
  return this.push({
6078
- kind: "entry",
6079
- entry: node
6996
+ kind: "declare",
6997
+ node
6080
6998
  });
6081
6999
  }
6082
7000
  waitForSignal(id, opts) {
@@ -6094,12 +7012,13 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6094
7012
  acceptedSources: opts.acceptedSources
6095
7013
  });
6096
7014
  return this.push({
6097
- kind: "entry",
6098
- entry: node
7015
+ kind: "declare",
7016
+ node
6099
7017
  });
6100
7018
  }
6101
7019
  workflow(id, ref, input, opts) {
6102
7020
  this.assertId(id, "workflow()");
7021
+ assertRetry(opts?.retry, id);
6103
7022
  const name = typeof ref === "string" ? ref : ref instanceof LuaWorkflow ? ref.getName() : void 0;
6104
7023
  if (!name) throw new LuaWorkflowBuildError("invalid-envelope", `workflow("${id}") needs a LuaWorkflow or a workflow name`);
6105
7024
  if (opts?.workspace === "inherit") {
@@ -6121,7 +7040,8 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6121
7040
  id,
6122
7041
  workflowId: name,
6123
7042
  input,
6124
- workspace: opts?.workspace
7043
+ workspace: opts?.workspace,
7044
+ retry: opts?.retry
6125
7045
  });
6126
7046
  return this.push({
6127
7047
  kind: "declare",
@@ -6135,7 +7055,7 @@ var WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
6135
7055
  const { graph, issues } = resolvePlacements(this.calls);
6136
7056
  const fatal = issues[0];
6137
7057
  if (fatal) {
6138
- const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id })/workflow(\u2026) somewhere in the chain \u2014 before OR after the reference" : void 0;
7058
+ 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;
6139
7059
  throw new LuaWorkflowBuildError(fatal.code, fatal.message, hint);
6140
7060
  }
6141
7061
  if (graph.length === 0) throw new LuaWorkflowBuildError("empty-graph", `workflow "${this.config.name}" has no entries`);