lua-cli 3.31.0 → 3.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/api-exports.d.ts +416 -103
  2. package/dist/api-exports.js +1992 -299
  3. package/dist/api-exports.js.map +1 -1
  4. package/dist/index.js +5262 -1914
  5. package/dist/index.js.map +1 -1
  6. package/dist/voice/test/index.d.ts +54 -54
  7. package/dist/workflow-builder.d.ts +257 -44
  8. package/dist/workflow-builder.js +1382 -265
  9. package/dist/workflow-builder.js.map +1 -1
  10. package/docs/README.md +2 -2
  11. package/docs/api/LuaWorkflow.md +44 -28
  12. package/docs/api/Workflows.md +12 -1
  13. package/docs/workflows/approvals.md +14 -1
  14. package/docs/workflows/connections-in-coding-turns.md +1 -0
  15. package/docs/workflows/correlation-keys.md +1 -0
  16. package/docs/workflows/git-credentials.md +22 -1
  17. package/docs/workflows/goals.md +46 -0
  18. package/docs/workflows/limits.md +6 -0
  19. package/docs/workflows/recovery.md +6 -2
  20. package/docs/workflows/replay-local.md +10 -10
  21. package/docs/workflows/schedules.md +15 -0
  22. package/docs/workflows/script-form.md +20 -10
  23. package/docs/workflows/testing-offline.md +25 -20
  24. package/docs/workflows/workspaces-and-long-steps.md +38 -2
  25. package/package.json +2 -2
  26. package/template/examples/workflows/CLAUDE.md +16 -13
  27. package/template/examples/workflows/pr-review-round.ts +61 -20
  28. package/template/examples/workflows/provision-tenant.ts +25 -8
  29. package/template/examples/workflows/refund-approval.ts +30 -17
  30. package/template/examples/workflows/support-triage.ts +59 -22
  31. package/template/examples/workflows/ticket-to-pr.ts +125 -46
  32. package/template/examples/workflows/vendor-invoices.ts +69 -16
  33. package/template/package.json +1 -1
@@ -90,9 +90,9 @@ function mcpActionTokens(action) {
90
90
  return action.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
91
91
  }
92
92
  function isReviewableMcpDraftTool(tool) {
93
- const sep = tool.indexOf("_");
94
- if (sep <= 0 || sep >= tool.length - 1) return false;
95
- const action = tool.slice(sep + 1);
93
+ const sep4 = tool.indexOf("_");
94
+ if (sep4 <= 0 || sep4 >= tool.length - 1) return false;
95
+ const action = tool.slice(sep4 + 1);
96
96
  const tokens = mcpActionTokens(action);
97
97
  if (!tokens.includes("draft") && !tokens.includes("drafts")) return false;
98
98
  return !MCP_TOOL_READ_VERB_RE.test(action);
@@ -356,6 +356,9 @@ function isDesktopFileSessionId(value3) {
356
356
  function isImplicitModelSelectionSource(source) {
357
357
  return source !== void 0 && IMPLICIT_MODEL_SELECTION_SOURCES.includes(source);
358
358
  }
359
+ function isPlatformFallbackModelSource(source) {
360
+ return source === PLATFORM_FALLBACK_MODEL_SOURCE;
361
+ }
359
362
  function resolveRequireToolApproval(rules) {
360
363
  const raw = rules?.requireToolApproval ?? rules?.requireApproval;
361
364
  if (raw === void 0 || raw === null) return void 0;
@@ -395,14 +398,14 @@ async function readCoreDrainingRetryDelayMs(response) {
395
398
  return coreDrainingRetryDelayMs(response.headers.get("Retry-After"));
396
399
  }
397
400
  function waitForCoreDrain(delayMs, signal) {
398
- return new Promise((resolve, reject) => {
401
+ return new Promise((resolve3, reject) => {
399
402
  if (signal?.aborted) {
400
403
  reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
401
404
  return;
402
405
  }
403
406
  const timer = setTimeout(() => {
404
407
  signal?.removeEventListener("abort", onAbort);
405
- resolve();
408
+ resolve3();
406
409
  }, delayMs);
407
410
  function onAbort() {
408
411
  clearTimeout(timer);
@@ -514,7 +517,6 @@ function luaClientMetricLabels(client) {
514
517
  const canonical2 = parseLuaClientHeader(serializeLuaClientHeader(client));
515
518
  return {
516
519
  client_family: canonical2?.app ?? "unknown",
517
- client_version: canonical2?.version ?? "unknown",
518
520
  client_attribution: canonical2?.attribution ?? "unknown"
519
521
  };
520
522
  }
@@ -536,6 +538,75 @@ function isKnownProfile(profiles, profileId) {
536
538
  function hasCapability(profiles, profileId, required) {
537
539
  return capabilitiesFor(profiles, profileId).includes(required);
538
540
  }
541
+ function workflowJobId(id) {
542
+ switch (id.kind) {
543
+ case "start":
544
+ return `wft_${id.runId}_0`;
545
+ case "start-redrive":
546
+ return `wft_${id.runId}_start_r${id.now}`;
547
+ case "tick":
548
+ return `wft_${id.runId}_${id.seq}`;
549
+ case "timer":
550
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}`;
551
+ case "timer-remainder":
552
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_r${id.now}`;
553
+ case "timer-deferral":
554
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_d${id.now}`;
555
+ case "timer-sibling":
556
+ return `wft_${id.runId}_timer_${id.stepId}_${id.attempt}_w${id.now}`;
557
+ case "retry":
558
+ return `wft_${id.runId}_retry_${id.stepId}_${id.attempt}`;
559
+ case "respawn":
560
+ return `wft_${id.runId}_respawn_${id.stepId}_${id.attempt}_s${id.segment}_r${id.now}`;
561
+ case "redrive":
562
+ return `wft_${id.runId}_redrive_${id.stepId}_${id.attempt}_r${id.now}`;
563
+ case "redispatch":
564
+ return `wft_${id.runId}_redispatch_${id.stepId}_${id.attempt}_x${id.executionId ?? "none"}_r${id.now}`;
565
+ case "expiry":
566
+ return `wft_${id.runId}_expire_${id.stepId}_${id.attempt}_t${id.suspendedAt}${id.hop > 0 ? `_h${id.hop}` : ""}`;
567
+ case "step-terminal":
568
+ return `wft_${id.runId}_term_${id.stepId}_${id.attempt}_r${id.now}`;
569
+ case "handback":
570
+ return `wft_${id.runId}_handback_${id.stepId}_${id.attempt}_${id.now}`;
571
+ case "foreach-rate":
572
+ return `wft_${id.runId}_fe_${id.stepId}_${id.attempt}_${id.bucket}`;
573
+ case "budget-expiry":
574
+ return `wft_${id.runId}_expiry_budget_r${id.now}`;
575
+ case "budget-resume":
576
+ return `wft_${id.runId}_resume_budget_${id.now}`;
577
+ case "deadline":
578
+ return `wft_${id.runId}_deadline_${id.deadlineAt}`;
579
+ case "deadline-remainder":
580
+ return `wft_${id.runId}_deadline_${id.deadlineAt}_r${id.now}`;
581
+ case "readmit":
582
+ return `wft_${id.runId}_readmit_${id.now}`;
583
+ case "cancel":
584
+ return `wft_${id.runId}_cancel`;
585
+ case "reconcile":
586
+ return `wft_${id.runId}_reconcile_${id.now}`;
587
+ case "replay":
588
+ return `wft_${id.runId}_replay_g${id.leaseGeneration}`;
589
+ case "migration":
590
+ return `wfm_${id.migrationId}`;
591
+ default:
592
+ return assertNever(id);
593
+ }
594
+ }
595
+ function assertNever(id) {
596
+ throw new Error(`workflowJobId: unknown wake kind ${JSON.stringify(id)}`);
597
+ }
598
+ function agentStepJobId(msg) {
599
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_x${msg.executionId}`;
600
+ }
601
+ function heavyStepJobId(msg) {
602
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_x${msg.executionId}`;
603
+ }
604
+ function jobSpawnJobId(msg) {
605
+ return `${msg.runId}_${msg.stepId}_${msg.attempt}_s${msg.segment}_${msg.executionId}`;
606
+ }
607
+ function exportAutoJobId(exportId, suffix) {
608
+ return suffix ? `${exportId}:${suffix}` : exportId;
609
+ }
539
610
  function isSystemRun(identity) {
540
611
  return identity.userId.startsWith(SYSTEM_USER_PREFIX);
541
612
  }
@@ -582,6 +653,99 @@ function scheduledTimeKey(scheduledTime) {
582
653
  function scheduledWorkflowRunIdForTime(jobId, scheduledTime) {
583
654
  return scheduledWorkflowRunId(jobId, scheduledTime);
584
655
  }
656
+ function isWithinWorkflowJobRange(member, value3) {
657
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
658
+ return typeof value3 === "number" && Number.isInteger(value3) && value3 >= min && value3 <= max;
659
+ }
660
+ function workflowJobRangeMessage(member) {
661
+ const { min, max } = WORKFLOW_JOB_RANGES[member];
662
+ return `\`${member}\` must be an integer ${min}..${max}`;
663
+ }
664
+ function isWorkflowSingleStepType(type) {
665
+ return typeof type === "string" && WORKFLOW_SINGLE_STEP_TYPES.includes(type);
666
+ }
667
+ function isWorkflowHitlEntryType(type) {
668
+ return typeof type === "string" && WORKFLOW_HITL_ENTRY_TYPES.includes(type);
669
+ }
670
+ function isWorkflowArmEntryType(type) {
671
+ return typeof type === "string" && WORKFLOW_ARM_ENTRY_TYPES.includes(type);
672
+ }
673
+ function workflowContainerRunsHitlArm(container) {
674
+ return typeof container === "string" && WORKFLOW_HITL_ARM_CONTAINERS.includes(container);
675
+ }
676
+ function workflowHitlArmUnsupportedMessage(type, id, container) {
677
+ const legal = WORKFLOW_HITL_ARM_CONTAINERS.map((c) => `\`${c}\``).join(" / ");
678
+ 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\``;
679
+ }
680
+ function workflowHitlArmShapeMessage(type, id, shape) {
681
+ 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\``;
682
+ }
683
+ function groupCount(re) {
684
+ let n2 = GROUP_COUNT.get(re);
685
+ if (n2 === void 0) {
686
+ n2 = new RegExp(`${re.source}|`, re.flags.replace("g", "")).exec("").length - 1;
687
+ GROUP_COUNT.set(re, n2);
688
+ }
689
+ return n2;
690
+ }
691
+ function isWorkflowSecretKey(key) {
692
+ return WORKFLOW_SECRET_KEY_RE.test(key);
693
+ }
694
+ function applyPatterns(text, patterns) {
695
+ let out = text;
696
+ for (const { re, suffix } of patterns) {
697
+ re.lastIndex = 0;
698
+ if (!re.test(out)) continue;
699
+ re.lastIndex = 0;
700
+ const groups = groupCount(re);
701
+ out = out.replace(re, (...args) => {
702
+ const kept = args.slice(1, 1 + groups).map((g) => typeof g === "string" ? g : "");
703
+ if (suffix && kept.length > 0) {
704
+ const tail = kept[kept.length - 1];
705
+ return `${kept.slice(0, -1).join("")}${REDACTED_PLACEHOLDER}${tail}`;
706
+ }
707
+ return `${kept.join("")}${REDACTED_PLACEHOLDER}`;
708
+ });
709
+ }
710
+ return out;
711
+ }
712
+ function scrubSecretText(text) {
713
+ if (typeof text !== "string" || text.length < 4) return text;
714
+ return applyPatterns(applyPatterns(text, SECRET_LITERAL_PATTERNS), SECRET_PAIR_PATTERNS);
715
+ }
716
+ function boundScrubInput(text, max = SCRUB_INPUT_MAX_CHARS) {
717
+ if (typeof text !== "string" || text.length <= max) return text;
718
+ const window = text.slice(Math.max(0, max - SCRUB_CUT_BACKOFF_CHARS), max);
719
+ const ws = window.search(/\s\S*$/);
720
+ const cut = ws >= 0 ? max - window.length + ws : max;
721
+ return `${text.slice(0, cut)}\u2026`;
722
+ }
723
+ function scrubSecretLines(lines) {
724
+ return lines.map((l) => typeof l === "string" ? scrubSecretText(boundScrubInput(l)) : l);
725
+ }
726
+ function messageText(value3) {
727
+ if (typeof value3 === "string") return value3;
728
+ if (value3 instanceof Error) return value3.message;
729
+ if (value3 && typeof value3 === "object") {
730
+ const m = value3.message;
731
+ if (typeof m === "string") return m;
732
+ try {
733
+ return JSON.stringify(value3);
734
+ } catch {
735
+ return "";
736
+ }
737
+ }
738
+ return value3 === void 0 || value3 === null ? "" : String(value3);
739
+ }
740
+ function scrubProviderMessage(raw, max = PROVIDER_MESSAGE_MAX_CHARS) {
741
+ const text = boundScrubInput(messageText(raw)).replace(/\s+/g, " ").trim();
742
+ if (!text) return void 0;
743
+ const out = scrubSecretText(text);
744
+ return out.length > max ? `${out.slice(0, max - 1)}\u2026` : out;
745
+ }
746
+ function scrubStepErrorMessage(raw) {
747
+ return scrubProviderMessage(raw, ERROR_MESSAGE_MAX_CHARS);
748
+ }
585
749
  function isWorkflowAuditEvent(action) {
586
750
  return typeof action === "string" && WORKFLOW_AUDIT_EVENTS.includes(action);
587
751
  }
@@ -676,7 +840,7 @@ ${PREAMBLE}
676
840
 
677
841
  ${items.join("\n\n")}`;
678
842
  }
679
- var __defProp2, __name2, CHANNEL_SEND_CHANNELS, REVIEWABLE_ACTION_EXECUTE_TOOL_ALLOWLIST, REVIEWABLE_MCP_SEND_TOOL_SUFFIX, MCP_TOOL_READ_VERB_RE, MCP_DRAFT_CREATE_VERBS, NON_INTERACTIVE_CHANNELS, RICH_PARTS_MESSAGE_ID_PREFIX, SCREENSHOT_MESSAGE_ID_PREFIX, BROWSER_COMMANDS, BROWSER_COMMAND_NAMES, DESKTOP_FILE_COMMANDS, DESKTOP_FILE_COMMAND_SET, REASONING_EFFORT_VALUES, IMPLICIT_MODEL_SELECTION_SOURCES, AGENT_NAME_TOKEN, DEFAULT_PERSONA_GUIDE, PERSONAL_SPACE_STARTING_PERSONA, CORE_DRAINING_CODE, CORE_DRAINING_DEFAULT_RETRY_MS, CORE_DRAINING_MAX_RETRY_MS, VoiceNameSchema, PluginProviderSchema, RealtimeProviderSchema, PluginClassSchema, ModelDescriptorSchema, InferenceModelSchema, PluginModelSchema, RealtimeModelSchema, LuaVoiceModelSchema, TurnDetectionSchema, InterruptionSchema, BuiltinAudioClipSchema, AudioConfigSchema, BackgroundAudioEntrySchema, BackgroundAudioSchema, LuaVoiceConfigInnerSchema, LuaVoiceConfigSchema, LuaVoiceRefSchema, LUA_JOB_DEFAULT_TIMEOUT_SECONDS, LUA_JOB_MIN_TIMEOUT_SECONDS, LUA_JOB_MAX_TIMEOUT_SECONDS, TEMPLATE_TRIGGER_URL_ENV_PREFIX, SUBJECT_TYPES, SubjectTypeSchema, CREDENTIAL_TYPES, CredentialTypeSchema, DEVICE_OPERATIONS, DeviceOperationSchema, DEVICE_SCOPE_BY_OPERATION, DeviceBindingSchema, IdSchema, PrincipalDescriptorSchema, ActorDescriptorSchema, PrincipalOwnerSchema, CredentialLifecycleSchema, GeneralCredentialDescriptorSchema, DeviceCredentialDescriptorSchema, GeneralPrincipalContextSchema, DeviceCredentialPrincipalContextSchema, RawPrincipalContextSchema, PrincipalContextSchema, DeviceCredentialClaimSchema, LUA_CLIENT_HEADER, LUA_CLIENT_APPS, SEMVER_PATTERN, WEB_RELEASE_PATTERN, CLIENT_HEADER_PATTERN, AUTHZ_PROJECTION_VERSION, ProjectedScopeSchema, DisplayRoleSchema, AuthorizationPrincipalSchema, CredentialContextSchema, ProjectionAnomalySchema, ProjectedOrgSchema, ProjectedResourceSchema, CapabilityProfilesSchema, RoleCatalogSchema, EffectiveAuthorizationSchema, ResourcePageSchema, SYSTEM_USER_PREFIX, WORKFLOW_RUN_IN_FLIGHT, WORKFLOW_RUN_IDLE, WORKFLOW_RUN_TERMINAL, WORKFLOW_RUN_STATUSES, ARCHIVE_WINDOW_MARGIN_DAYS, WORKFLOW_ORG_PURGING_TTL_S, WORKFLOW_ORG_PURGE_FORCE_AFTER_MS, IDEMPOTENCY_HOLDING_STATUSES, WORKFLOW_SCHEDULED_RUN_ID_PREFIX, CLOUD_TASK_RUN_ID_PREFIX, WORKFLOW_OPERATION_ID_PREFIX, WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES, WORKFLOW_AUDIT_EVENTS, WORKFLOW_AUDIT_METADATA_MAX_BYTES, INDENT, WRAP_WIDTH, NOUNS, GET_TOOL_NAMES, PREAMBLE;
843
+ var __defProp2, __name2, CHANNEL_SEND_CHANNELS, REVIEWABLE_ACTION_EXECUTE_TOOL_ALLOWLIST, REVIEWABLE_MCP_SEND_TOOL_SUFFIX, MCP_TOOL_READ_VERB_RE, MCP_DRAFT_CREATE_VERBS, NON_INTERACTIVE_CHANNELS, RICH_PARTS_MESSAGE_ID_PREFIX, SCREENSHOT_MESSAGE_ID_PREFIX, BROWSER_COMMANDS, BROWSER_COMMAND_NAMES, DESKTOP_FILE_COMMANDS, DESKTOP_FILE_COMMAND_SET, REASONING_EFFORT_VALUES, IMPLICIT_MODEL_SELECTION_SOURCES, PLATFORM_FALLBACK_MODEL_SOURCE, AGENT_NAME_TOKEN, DEFAULT_PERSONA_GUIDE, PERSONAL_SPACE_STARTING_PERSONA, CORE_DRAINING_CODE, CORE_DRAINING_DEFAULT_RETRY_MS, CORE_DRAINING_MAX_RETRY_MS, VoiceNameSchema, PluginProviderSchema, RealtimeProviderSchema, PluginClassSchema, ModelDescriptorSchema, InferenceModelSchema, PluginModelSchema, RealtimeModelSchema, LuaVoiceModelSchema, TurnDetectionSchema, InterruptionSchema, BuiltinAudioClipSchema, AudioConfigSchema, BackgroundAudioEntrySchema, BackgroundAudioSchema, LuaVoiceConfigInnerSchema, LuaVoiceConfigSchema, LuaVoiceRefSchema, LUA_JOB_DEFAULT_TIMEOUT_SECONDS, LUA_JOB_MIN_TIMEOUT_SECONDS, LUA_JOB_MAX_TIMEOUT_SECONDS, TEMPLATE_TRIGGER_URL_ENV_PREFIX, SUBJECT_TYPES, SubjectTypeSchema, CREDENTIAL_TYPES, CredentialTypeSchema, DEVICE_OPERATIONS, DeviceOperationSchema, DEVICE_SCOPE_BY_OPERATION, DeviceBindingSchema, IdSchema, PrincipalDescriptorSchema, ActorDescriptorSchema, PrincipalOwnerSchema, CredentialLifecycleSchema, GeneralCredentialDescriptorSchema, DeviceCredentialDescriptorSchema, GeneralPrincipalContextSchema, DeviceCredentialPrincipalContextSchema, RawPrincipalContextSchema, PrincipalContextSchema, DeviceCredentialClaimSchema, LUA_CLIENT_HEADER, LUA_CLIENT_APPS, SEMVER_PATTERN, WEB_RELEASE_PATTERN, CLIENT_HEADER_PATTERN, AUTHZ_PROJECTION_VERSION, ProjectedScopeSchema, DisplayRoleSchema, AuthorizationPrincipalSchema, CredentialContextSchema, ProjectionAnomalySchema, ProjectedOrgSchema, ProjectedResourceSchema, CapabilityProfilesSchema, RoleCatalogSchema, EffectiveAuthorizationSchema, ResourcePageSchema, SYSTEM_USER_PREFIX, WORKFLOW_RUN_IN_FLIGHT, WORKFLOW_RUN_IDLE, WORKFLOW_RUN_TERMINAL, WORKFLOW_RUN_STATUSES, WORKFLOW_STEP_STATUSES, WORKFLOW_STEP_IN_FLIGHT, ARCHIVE_WINDOW_MARGIN_DAYS, WORKFLOW_ORG_PURGING_TTL_S, WORKFLOW_ORG_PURGE_FORCE_AFTER_MS, IDEMPOTENCY_HOLDING_STATUSES, WORKFLOW_SCHEDULED_RUN_ID_PREFIX, CLOUD_TASK_RUN_ID_PREFIX, WORKFLOW_OPERATION_ID_PREFIX, WORKFLOW_CONNECTION_KEY_RE, WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES, WORKFLOW_RESOLVE_OUTPUT_MAX_BYTES, WORKFLOW_RETRY_BACKOFFS, WORKFLOW_JOB_RESOURCES, WORKFLOW_SIDE_EFFECTS, WORKFLOW_JOB_RANGES, WORKFLOW_JOB_RANGE_MEMBERS, WORKFLOW_SINGLE_STEP_TYPES, WORKFLOW_HITL_ENTRY_TYPES, WORKFLOW_ARM_ENTRY_TYPES, WORKFLOW_HITL_ARM_CONTAINERS, WORKFLOW_GRAPH_ENTRY_STEP_KINDS, WORKFLOW_ARM_ENTRY_STEP_KINDS, WORKFLOW_BUDGET_MAX_DURATION_SECONDS, REDACTED_PLACEHOLDER, PROVIDER_MESSAGE_MAX_CHARS, ERROR_MESSAGE_MAX_CHARS, SECRET_LITERAL_PATTERNS, SECRET_NAME, SECRET_PAIR_PATTERNS, GROUP_COUNT, WORKFLOW_SECRET_KEY_RE, WORKFLOW_RESERVED_SECRET_KEYS, SCRUB_INPUT_MAX_CHARS, SCRUB_CUT_BACKOFF_CHARS, WORKFLOW_AUDIT_EVENTS, WORKFLOW_AUDIT_METADATA_MAX_BYTES, INDENT, WRAP_WIDTH, NOUNS, GET_TOOL_NAMES, PREAMBLE;
680
844
  var init_dist = __esm({
681
845
  "../shared-types/dist/index.mjs"() {
682
846
  "use strict";
@@ -1007,6 +1171,9 @@ var init_dist = __esm({
1007
1171
  ];
1008
1172
  __name(isImplicitModelSelectionSource, "isImplicitModelSelectionSource");
1009
1173
  __name2(isImplicitModelSelectionSource, "isImplicitModelSelectionSource");
1174
+ PLATFORM_FALLBACK_MODEL_SOURCE = "platform-fallback";
1175
+ __name(isPlatformFallbackModelSource, "isPlatformFallbackModelSource");
1176
+ __name2(isPlatformFallbackModelSource, "isPlatformFallbackModelSource");
1010
1177
  __name(resolveRequireToolApproval, "resolveRequireToolApproval");
1011
1178
  __name2(resolveRequireToolApproval, "resolveRequireToolApproval");
1012
1179
  AGENT_NAME_TOKEN = "[Your Agent Name]";
@@ -1659,6 +1826,18 @@ This text is who you are for this person. As you learn them, their name, their w
1659
1826
  __name2(isKnownProfile, "isKnownProfile");
1660
1827
  __name(hasCapability, "hasCapability");
1661
1828
  __name2(hasCapability, "hasCapability");
1829
+ __name(workflowJobId, "workflowJobId");
1830
+ __name2(workflowJobId, "workflowJobId");
1831
+ __name(assertNever, "assertNever");
1832
+ __name2(assertNever, "assertNever");
1833
+ __name(agentStepJobId, "agentStepJobId");
1834
+ __name2(agentStepJobId, "agentStepJobId");
1835
+ __name(heavyStepJobId, "heavyStepJobId");
1836
+ __name2(heavyStepJobId, "heavyStepJobId");
1837
+ __name(jobSpawnJobId, "jobSpawnJobId");
1838
+ __name2(jobSpawnJobId, "jobSpawnJobId");
1839
+ __name(exportAutoJobId, "exportAutoJobId");
1840
+ __name2(exportAutoJobId, "exportAutoJobId");
1662
1841
  SYSTEM_USER_PREFIX = "system:";
1663
1842
  __name(isSystemRun, "isSystemRun");
1664
1843
  __name2(isSystemRun, "isSystemRun");
@@ -1688,6 +1867,27 @@ This text is who you are for this person. As you learn them, their name, their w
1688
1867
  ...WORKFLOW_RUN_IDLE,
1689
1868
  ...WORKFLOW_RUN_TERMINAL
1690
1869
  ];
1870
+ WORKFLOW_STEP_STATUSES = [
1871
+ "pending",
1872
+ "ready",
1873
+ "waiting",
1874
+ "dispatched",
1875
+ "claimed",
1876
+ "running",
1877
+ "suspended",
1878
+ "cancellation_requested",
1879
+ "completed",
1880
+ "failed",
1881
+ "skipped",
1882
+ "cancelled",
1883
+ "timeout",
1884
+ "reaped"
1885
+ ];
1886
+ WORKFLOW_STEP_IN_FLIGHT = [
1887
+ "claimed",
1888
+ "running",
1889
+ "cancellation_requested"
1890
+ ];
1691
1891
  ARCHIVE_WINDOW_MARGIN_DAYS = 7;
1692
1892
  __name(shouldSkipArchive, "shouldSkipArchive");
1693
1893
  __name2(shouldSkipArchive, "shouldSkipArchive");
@@ -1717,11 +1917,223 @@ This text is who you are for this person. As you learn them, their name, their w
1717
1917
  __name2(workflowOccurrenceId, "workflowOccurrenceId");
1718
1918
  __name(workflowOrgSlotKey, "workflowOrgSlotKey");
1719
1919
  __name2(workflowOrgSlotKey, "workflowOrgSlotKey");
1920
+ WORKFLOW_CONNECTION_KEY_RE = /^[a-z][a-z0-9_-]{0,63}$/;
1720
1921
  __name(scheduledTimeKey, "scheduledTimeKey");
1721
1922
  __name2(scheduledTimeKey, "scheduledTimeKey");
1722
1923
  __name(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1723
1924
  __name2(scheduledWorkflowRunIdForTime, "scheduledWorkflowRunIdForTime");
1724
1925
  WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES = 64 * 1024;
1926
+ WORKFLOW_RESOLVE_OUTPUT_MAX_BYTES = 256 * 1024;
1927
+ WORKFLOW_RETRY_BACKOFFS = [
1928
+ "fixed",
1929
+ "exponential"
1930
+ ];
1931
+ WORKFLOW_JOB_RESOURCES = [
1932
+ "small",
1933
+ "medium",
1934
+ "large"
1935
+ ];
1936
+ WORKFLOW_SIDE_EFFECTS = [
1937
+ "none",
1938
+ "external"
1939
+ ];
1940
+ WORKFLOW_JOB_RANGES = Object.freeze({
1941
+ /** Coding-turn cap (`claude -p --max-turns`); absent ⇒ the cluster default (LUA_WF_JOB_MAX_TURNS). */
1942
+ maxTurns: Object.freeze({
1943
+ min: 1,
1944
+ max: 500
1945
+ }),
1946
+ /** Harness messages per attempt (one per content block); absent ⇒ the cluster default (400). */
1947
+ maxMessages: Object.freeze({
1948
+ min: 1,
1949
+ max: 5e3
1950
+ }),
1951
+ /** Input-side tokens per attempt (prompt + cache); absent ⇒ the cluster default (4M — LUA-708, was 30M). */
1952
+ maxInputTokens: Object.freeze({
1953
+ min: 1e6,
1954
+ max: 5e8
1955
+ })
1956
+ });
1957
+ WORKFLOW_JOB_RANGE_MEMBERS = Object.keys(WORKFLOW_JOB_RANGES);
1958
+ __name(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1959
+ __name2(isWithinWorkflowJobRange, "isWithinWorkflowJobRange");
1960
+ __name(workflowJobRangeMessage, "workflowJobRangeMessage");
1961
+ __name2(workflowJobRangeMessage, "workflowJobRangeMessage");
1962
+ WORKFLOW_SINGLE_STEP_TYPES = [
1963
+ "agent",
1964
+ "tool",
1965
+ "workflow",
1966
+ "step"
1967
+ ];
1968
+ WORKFLOW_HITL_ENTRY_TYPES = [
1969
+ "approval",
1970
+ "waitForSignal"
1971
+ ];
1972
+ WORKFLOW_ARM_ENTRY_TYPES = [
1973
+ ...WORKFLOW_SINGLE_STEP_TYPES,
1974
+ ...WORKFLOW_HITL_ENTRY_TYPES
1975
+ ];
1976
+ WORKFLOW_HITL_ARM_CONTAINERS = [
1977
+ "parallel",
1978
+ "conditional",
1979
+ "foreach"
1980
+ ];
1981
+ __name(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1982
+ __name2(isWorkflowSingleStepType, "isWorkflowSingleStepType");
1983
+ __name(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1984
+ __name2(isWorkflowHitlEntryType, "isWorkflowHitlEntryType");
1985
+ __name(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1986
+ __name2(isWorkflowArmEntryType, "isWorkflowArmEntryType");
1987
+ __name(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1988
+ __name2(workflowContainerRunsHitlArm, "workflowContainerRunsHitlArm");
1989
+ __name(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1990
+ __name2(workflowHitlArmUnsupportedMessage, "workflowHitlArmUnsupportedMessage");
1991
+ __name(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1992
+ __name2(workflowHitlArmShapeMessage, "workflowHitlArmShapeMessage");
1993
+ WORKFLOW_GRAPH_ENTRY_STEP_KINDS = Object.freeze({
1994
+ agent: "agent",
1995
+ tool: "tool",
1996
+ workflow: "subrun",
1997
+ step: "code",
1998
+ mapping: "map",
1999
+ sleep: "sleep",
2000
+ sleepUntil: "sleepUntil",
2001
+ parallel: null,
2002
+ conditional: "branch",
2003
+ foreach: "foreach",
2004
+ loop: "loop",
2005
+ approval: "approval",
2006
+ waitForSignal: "signal"
2007
+ });
2008
+ WORKFLOW_ARM_ENTRY_STEP_KINDS = Object.freeze(Object.fromEntries(WORKFLOW_ARM_ENTRY_TYPES.map((t) => [
2009
+ t,
2010
+ WORKFLOW_GRAPH_ENTRY_STEP_KINDS[t]
2011
+ ])));
2012
+ WORKFLOW_BUDGET_MAX_DURATION_SECONDS = Object.freeze({
2013
+ min: 60,
2014
+ max: 2592e3
2015
+ });
2016
+ REDACTED_PLACEHOLDER = "[REDACTED]";
2017
+ PROVIDER_MESSAGE_MAX_CHARS = 300;
2018
+ ERROR_MESSAGE_MAX_CHARS = 2e3;
2019
+ SECRET_LITERAL_PATTERNS = [
2020
+ {
2021
+ re: /\b(github_pat_)[A-Za-z0-9_]{16,}/g
2022
+ },
2023
+ {
2024
+ re: /\b(gh[pousr]_)[A-Za-z0-9]{16,}/g
2025
+ },
2026
+ {
2027
+ re: /\b(glpat-)[A-Za-z0-9_-]{16,}/g
2028
+ },
2029
+ // LUA-696 review (3): the npm granular / classic token (`npm_` + 36 alphanumerics).
2030
+ {
2031
+ re: /\b(npm_)[A-Za-z0-9]{36}\b/g
2032
+ },
2033
+ {
2034
+ re: /\b(sk-ant-)[A-Za-z0-9_-]{16,}/g
2035
+ },
2036
+ {
2037
+ re: /\b(sk-)(?!ant-)[A-Za-z0-9_-]{20,}/g
2038
+ },
2039
+ {
2040
+ re: /\b(AKIA)[A-Z0-9]{16}\b/g
2041
+ },
2042
+ {
2043
+ re: /\b(xox[abprs]-)[A-Za-z0-9-]{10,}/g
2044
+ },
2045
+ {
2046
+ re: /\b(AIza)[0-9A-Za-z_-]{35}/g
2047
+ },
2048
+ {
2049
+ re: /\b(ya29\.)[A-Za-z0-9_-]{20,}/g
2050
+ },
2051
+ {
2052
+ re: /\b(eyJ)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
2053
+ },
2054
+ {
2055
+ re: /(-----BEGIN [A-Z ]*PRIVATE KEY-----)[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
2056
+ },
2057
+ // `https://x-access-token:<token>@github.com/…` (the git credential shape the pod scrubbed already)
2058
+ {
2059
+ re: /\b(x-access-token:)[^@\s]+(@)/g,
2060
+ suffix: true
2061
+ }
2062
+ ];
2063
+ SECRET_NAME = "(?:authorization|proxy-authorization|x-api-key|x-wf-[a-z-]*key|x-internal-auth|api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|secret|password|passwd|pwd|passphrase|token|credentials?)";
2064
+ SECRET_PAIR_PATTERNS = [
2065
+ // `Authorization: Bearer x`, `x-api-key: x`, `AWS_SECRET_ACCESS_KEY=x`, `SVC_JOB_KEY=x`, `"apiKey": "x"`,
2066
+ // `FOO_TOKEN=x`, `secretKey=x`, `password=x`. Groups: the char before the name, the name, the separator
2067
+ // (with its quotes), the scheme word — all kept; the value goes. A value a literal rule already replaced
2068
+ // (`x-access-token:[REDACTED]@host`) is left alone so the host after it survives.
2069
+ // LUA-696: the second lookahead keeps a scrubbed `Authorization: Bearer [REDACTED]` as it is — without it the
2070
+ // optional scheme group backtracks to empty and `Bearer` itself becomes the value (`[REDACTED] [REDACTED]`).
2071
+ // The scrub is applied more than once on purpose (the Job site at its copy, the outcome table at the row
2072
+ // write, lua-api on the way out), so it must be idempotent.
2073
+ // LUA-696 review (HIGH): the identifier prefix is BOUNDED (`{1,64}`) — unbounded, `[A-Za-z0-9-]+` made the
2074
+ // scan quadratic on `-`-heavy text (100 KB of `-` took 12.7 s on the lua-core event loop; a worker-tier step
2075
+ // can throw that). No identifier that names a credential is longer; the probe list is unchanged.
2076
+ {
2077
+ 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")
2078
+ },
2079
+ // `?token=x`, `&key=x`, `&X-Amz-Signature=x`, `&sig=x`
2080
+ {
2081
+ re: /([?&](?:token|key|api[_-]?key|apikey|access[_-]?token|id[_-]?token|auth|sig|signature|secret|password|pwd|x-amz-signature|x-amz-credential|x-amz-security-token)=)[^&\s"'#]+/gi
2082
+ },
2083
+ // `mongodb+srv://user:pass@host`, `postgres://user:pass@host`, `https://user:pass@host`
2084
+ {
2085
+ re: /(\/\/[^\s/:@]+:)[^\s/@]+(@)/g,
2086
+ suffix: true
2087
+ },
2088
+ // `Basic <base64>` / `bearer <short token>` (the literal list needs ≥ 16 chars; any case)
2089
+ {
2090
+ re: /\b((?:Basic|Bearer)\s+)(?!\[REDACTED\])[A-Za-z0-9+/=_.-]{8,}/gi
2091
+ }
2092
+ ];
2093
+ GROUP_COUNT = /* @__PURE__ */ new WeakMap();
2094
+ __name(groupCount, "groupCount");
2095
+ __name2(groupCount, "groupCount");
2096
+ 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;
2097
+ WORKFLOW_RESERVED_SECRET_KEYS = Object.freeze([
2098
+ "secret",
2099
+ "token",
2100
+ "password",
2101
+ "passwd",
2102
+ "pwd",
2103
+ "passphrase",
2104
+ "api_key",
2105
+ "apikey",
2106
+ "access_key",
2107
+ "secret_key",
2108
+ "private_key",
2109
+ "client_secret",
2110
+ "authorization",
2111
+ "auth_token",
2112
+ "access_token",
2113
+ "id_token",
2114
+ "refresh_token",
2115
+ "session_key",
2116
+ "credential",
2117
+ "credentials"
2118
+ ]);
2119
+ __name(isWorkflowSecretKey, "isWorkflowSecretKey");
2120
+ __name2(isWorkflowSecretKey, "isWorkflowSecretKey");
2121
+ __name(applyPatterns, "applyPatterns");
2122
+ __name2(applyPatterns, "applyPatterns");
2123
+ __name(scrubSecretText, "scrubSecretText");
2124
+ __name2(scrubSecretText, "scrubSecretText");
2125
+ SCRUB_INPUT_MAX_CHARS = 16 * 1024;
2126
+ SCRUB_CUT_BACKOFF_CHARS = 256;
2127
+ __name(boundScrubInput, "boundScrubInput");
2128
+ __name2(boundScrubInput, "boundScrubInput");
2129
+ __name(scrubSecretLines, "scrubSecretLines");
2130
+ __name2(scrubSecretLines, "scrubSecretLines");
2131
+ __name(messageText, "messageText");
2132
+ __name2(messageText, "messageText");
2133
+ __name(scrubProviderMessage, "scrubProviderMessage");
2134
+ __name2(scrubProviderMessage, "scrubProviderMessage");
2135
+ __name(scrubStepErrorMessage, "scrubStepErrorMessage");
2136
+ __name2(scrubStepErrorMessage, "scrubStepErrorMessage");
1725
2137
  WORKFLOW_AUDIT_EVENTS = [
1726
2138
  // --- definitions / versions / templates (13, 11 §11.11.5) ---
1727
2139
  "workflow.published",
@@ -1843,7 +2255,24 @@ listed here; never invent a target.`;
1843
2255
  // ../workflow-graph/dist/index.mjs
1844
2256
  import { createHash } from "crypto";
1845
2257
  import { z as z4 } from "zod";
2258
+ import { z as z22 } from "zod";
1846
2259
  import { createHash as createHash2 } from "crypto";
2260
+ function workspaceTemplatePath(template22) {
2261
+ const key = template22.trim();
2262
+ const expr = WORKSPACE_TEMPLATE_EXPR_RE.exec(key);
2263
+ if (expr) return expr[1].split(".");
2264
+ if (key.includes("${")) return void 0;
2265
+ return key.replace(/^(?:input|initData)\./, "").split(".");
2266
+ }
2267
+ function retryBackoffs() {
2268
+ if (!Array.isArray(WORKFLOW_RETRY_BACKOFFS)) {
2269
+ 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')");
2270
+ }
2271
+ return WORKFLOW_RETRY_BACKOFFS;
2272
+ }
2273
+ function sleepUntilUnsupportedMessage(id) {
2274
+ 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} }`;
2275
+ }
1847
2276
  function fillPolicy(node, defaultTimeout) {
1848
2277
  if (node.tier === void 0 && node.workspace !== void 0 && node.workspace !== "inherit") node.tier = "job";
1849
2278
  if (node.timeoutSeconds === void 0) node.timeoutSeconds = node.tier === "job" ? WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS : defaultTimeout;
@@ -1873,8 +2302,28 @@ function fillSingle(node) {
1873
2302
  return;
1874
2303
  }
1875
2304
  }
2305
+ function fillHitl(node) {
2306
+ if (node.type === "approval") {
2307
+ const a = node;
2308
+ if (a.approver === void 0) a.approver = "creator";
2309
+ if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2310
+ if (a.onTimeout === void 0) a.onTimeout = "deny";
2311
+ if (a.onDeny === void 0) a.onDeny = "continue";
2312
+ if (a.excludeInitiator === void 0) a.excludeInitiator = false;
2313
+ if (a.editable === void 0) a.editable = false;
2314
+ return;
2315
+ }
2316
+ const w = node;
2317
+ if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
2318
+ if (w.onTimeout === void 0) w.onTimeout = "fail";
2319
+ if (w.acceptedSources === void 0) w.acceptedSources = [
2320
+ ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
2321
+ ];
2322
+ }
1876
2323
  function fillArm(arm) {
1877
- if (arm.type !== "mapping") fillSingle(arm);
2324
+ if (arm.type === "mapping") return;
2325
+ if (isHitlNode(arm)) fillHitl(arm);
2326
+ else fillSingle(arm);
1878
2327
  }
1879
2328
  function fillEntry(entry) {
1880
2329
  switch (entry.type) {
@@ -1885,7 +2334,7 @@ function fillEntry(entry) {
1885
2334
  fillSingle(entry);
1886
2335
  return;
1887
2336
  case "parallel":
1888
- entry.steps.forEach(fillSingle);
2337
+ entry.steps.forEach(fillArm);
1889
2338
  return;
1890
2339
  case "conditional": {
1891
2340
  const c = entry;
@@ -1899,34 +2348,19 @@ function fillEntry(entry) {
1899
2348
  f.opts = f.opts ?? {};
1900
2349
  if (f.opts.concurrency === void 0) f.opts.concurrency = WORKFLOW_FOREACH_DEFAULT_CONCURRENCY;
1901
2350
  if (f.opts.maxItems === void 0) f.opts.maxItems = WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
1902
- fillSingle(f.step);
2351
+ fillArm(f.step);
1903
2352
  return;
1904
2353
  }
1905
2354
  case "loop": {
1906
2355
  const l = entry;
1907
2356
  if (l.maxIterations === void 0) l.maxIterations = WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
1908
- fillSingle(l.step);
2357
+ fillArm(l.step);
1909
2358
  return;
1910
2359
  }
1911
- case "approval": {
1912
- const a = entry;
1913
- if (a.approver === void 0) a.approver = "creator";
1914
- if (a.timeoutHours === void 0) a.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
1915
- if (a.onTimeout === void 0) a.onTimeout = "deny";
1916
- if (a.onDeny === void 0) a.onDeny = "continue";
1917
- if (a.excludeInitiator === void 0) a.excludeInitiator = false;
1918
- if (a.editable === void 0) a.editable = false;
1919
- return;
1920
- }
1921
- case "waitForSignal": {
1922
- const w = entry;
1923
- if (w.timeoutHours === void 0) w.timeoutHours = WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS;
1924
- if (w.onTimeout === void 0) w.onTimeout = "fail";
1925
- if (w.acceptedSources === void 0) w.acceptedSources = [
1926
- ...WORKFLOW_SIGNAL_DEFAULT_SOURCES
1927
- ];
2360
+ case "approval":
2361
+ case "waitForSignal":
2362
+ fillHitl(entry);
1928
2363
  return;
1929
- }
1930
2364
  case "mapping":
1931
2365
  case "sleep":
1932
2366
  case "sleepUntil":
@@ -1938,6 +2372,12 @@ function withDefaultsFilled(g) {
1938
2372
  out.definition.graph.forEach(fillEntry);
1939
2373
  return out;
1940
2374
  }
2375
+ function isConnectionKeyShaped(value22) {
2376
+ return WORKFLOW_CONNECTION_KEY_RE.test(value22) && !CONNECTION_ID_HEX_RE.test(value22);
2377
+ }
2378
+ function connectionKeyUndeclaredMessage(path3, key) {
2379
+ return `${path3} '${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`;
2380
+ }
1941
2381
  function classifyModelProvider(model) {
1942
2382
  const m = (model ?? "").trim().toLowerCase();
1943
2383
  if (!m) return null;
@@ -1962,14 +2402,19 @@ function templateStepRefs(text) {
1962
2402
  for (const m of text.matchAll(TEMPLATE_STEP_REF)) ids.push(m[1]);
1963
2403
  return ids;
1964
2404
  }
1965
- function mapConfigStepRefs(raw) {
1966
- if (!raw) return [];
1967
- let cfg;
2405
+ function readMapConfig(raw) {
2406
+ if (!raw) return void 0;
2407
+ if (typeof raw !== "string") return raw;
1968
2408
  try {
1969
- cfg = typeof raw === "string" ? JSON.parse(raw) : raw;
2409
+ const cfg = JSON.parse(raw);
2410
+ return cfg && typeof cfg === "object" && !Array.isArray(cfg) ? cfg : void 0;
1970
2411
  } catch {
1971
- return [];
2412
+ return void 0;
1972
2413
  }
2414
+ }
2415
+ function mapConfigStepRefs(raw) {
2416
+ const cfg = readMapConfig(raw);
2417
+ if (!cfg) return [];
1973
2418
  const ids = [];
1974
2419
  for (const d of Object.values(cfg)) {
1975
2420
  if (!d || typeof d !== "object") continue;
@@ -2040,6 +2485,43 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2040
2485
  if (envelopeWorkspace?.backend && envelopeWorkspace.backend !== "ebs" && opts.policy?.workspaceBackends && !opts.policy.workspaceBackends.includes(envelopeWorkspace.backend)) {
2041
2486
  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");
2042
2487
  }
2488
+ for (const member of [
2489
+ "repo",
2490
+ "ref"
2491
+ ]) {
2492
+ const v = envelopeWorkspace?.[member];
2493
+ const tpl = v && typeof v === "object" && typeof v.template === "string" ? v.template : void 0;
2494
+ if (tpl !== void 0 && workspaceTemplatePath(tpl) === void 0) {
2495
+ 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}`);
2496
+ }
2497
+ }
2498
+ const declaredKeys = new Set(opts.connectionKeys ?? []);
2499
+ if (g.connections !== void 0 && !Array.isArray(g.connections)) {
2500
+ err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
2501
+ }
2502
+ (Array.isArray(g.connections) ? g.connections : []).forEach((c, i) => {
2503
+ const path3 = `connections.${i}`;
2504
+ const key = c?.key;
2505
+ const integrationType = c?.integrationType;
2506
+ if (typeof key !== "string" || !WORKFLOW_CONNECTION_KEY_RE.test(key)) {
2507
+ err("connection-declaration-invalid", `connections[${i}].key must match ${WORKFLOW_CONNECTION_KEY_RE}`, `${path3}.key`);
2508
+ return;
2509
+ }
2510
+ if (declaredKeys.has(key)) {
2511
+ err("connection-declaration-invalid", `connections[${i}].key "${key}" is declared twice`, `${path3}.key`);
2512
+ return;
2513
+ }
2514
+ if (typeof integrationType !== "string" || !integrationType.trim()) {
2515
+ err("connection-declaration-invalid", `connections[${i}] ("${key}") needs an integrationType (the catalog slug, e.g. 'github')`, `${path3}.integrationType`);
2516
+ return;
2517
+ }
2518
+ declaredKeys.add(key);
2519
+ });
2520
+ const undeclaredKey = /* @__PURE__ */ __name3((ref) => typeof ref === "string" && !declaredKeys.has(ref) && isConnectionKeyShaped(ref) && opts.connectionIds?.has(ref) !== true, "undeclaredKey");
2521
+ const credentialsRef = envelopeWorkspace?.credentialsRef;
2522
+ if (undeclaredKey(credentialsRef)) {
2523
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage("workspace.credentialsRef", credentialsRef), "workspace.credentialsRef");
2524
+ }
2043
2525
  const seen = /* @__PURE__ */ new Map();
2044
2526
  let nodeCount = 0;
2045
2527
  const upstream = /* @__PURE__ */ new Set();
@@ -2051,12 +2533,27 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2051
2533
  seen.set(id, path3);
2052
2534
  }
2053
2535
  }, "checkId");
2536
+ const checkPolicyEnums = /* @__PURE__ */ __name3((node, path3) => {
2537
+ const id = singleId(node);
2538
+ const check = /* @__PURE__ */ __name3((member, allowed) => {
2539
+ const value22 = node[member];
2540
+ if (value22 === void 0 || typeof value22 === "string" && allowed.includes(value22)) return;
2541
+ err("invalid-envelope", `\`${member}\` must be ${allowed.map((a) => `'${a}'`).join(" | ")} (got ${JSON.stringify(value22)})`, `${path3}.${member}`, id);
2542
+ }, "check");
2543
+ check("sideEffects", WORKFLOW_SIDE_EFFECTS);
2544
+ check("jobResources", WORKFLOW_JOB_RESOURCES);
2545
+ }, "checkPolicyEnums");
2054
2546
  const checkRetry = /* @__PURE__ */ __name3((node, path3) => {
2055
2547
  const r = node.retry;
2056
2548
  if (!r) return;
2057
2549
  const id = singleId(node);
2058
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") {
2059
- err("backoff-invalid", `retry.backoff must be 'fixed' | 'exponential'`, `${path3}.retry.backoff`, id);
2550
+ const backoffs = retryBackoffs();
2551
+ if (r.backoff !== void 0 && !backoffs.includes(r.backoff)) {
2552
+ const list = backoffs.map((b) => `'${b}'`).join(" | ");
2553
+ err("backoff-invalid", `retry.backoff must be ${list}`, `${path3}.retry.backoff`, id);
2554
+ }
2555
+ if (r.backoffSeconds !== void 0 && r.backoffSeconds < 0) {
2556
+ err("backoff-invalid", "retry.backoffSeconds must be \u2265 0", `${path3}.retry.backoffSeconds`, id);
2060
2557
  }
2061
2558
  if (r.maxBackoffSeconds !== void 0) {
2062
2559
  if (r.backoff !== "exponential") {
@@ -2119,10 +2616,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2119
2616
  }, "checkSpecialistRole");
2120
2617
  const checkRequiredConnections = /* @__PURE__ */ __name3((node, path3) => {
2121
2618
  const required = node.requiredConnections;
2122
- if (!Array.isArray(required) || !opts.connectionIds) return;
2123
- const unknown = required.filter((c) => typeof c !== "string" || !opts.connectionIds.has(c));
2619
+ if (!Array.isArray(required)) return;
2620
+ const undeclared = required.filter(undeclaredKey);
2621
+ if (undeclared.length) {
2622
+ err("connection-key-undeclared", connectionKeyUndeclaredMessage(`${path3}.requiredConnections`, undeclared[0]) + (undeclared.length > 1 ? ` (also undeclared: ${JSON.stringify(undeclared.slice(1))})` : ""), `${path3}.requiredConnections`, singleId(node));
2623
+ }
2624
+ if (!opts.connectionIds) return;
2625
+ const unknown = required.filter((c) => typeof c !== "string" || !declaredKeys.has(c) && !opts.connectionIds.has(c));
2124
2626
  if (unknown.length) {
2125
- err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are not connections the owner can mount`, `${path3}.requiredConnections`, singleId(node));
2627
+ err("required-connection-unknown", `requiredConnections ${JSON.stringify(unknown)} are neither declared connections[].key values nor connections the owner can mount`, `${path3}.requiredConnections`, singleId(node));
2126
2628
  }
2127
2629
  }, "checkRequiredConnections");
2128
2630
  const checkTier = /* @__PURE__ */ __name3((node, path3) => {
@@ -2200,14 +2702,20 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2200
2702
  err("container-arm-empty", "a bare mapping arm has nothing to run", `${path3}.graph.1`, node.id);
2201
2703
  return;
2202
2704
  }
2705
+ const inner = body[1];
2706
+ if (isHitlNode(inner)) {
2707
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(inner.type, inner.id, "mapped-arm"), `${path3}.graph.1`, inner.id);
2708
+ return;
2709
+ }
2203
2710
  upstream.add(singleId(body[1]));
2204
- checkArm(body[0], `${path3}.graph.0`, depth);
2711
+ checkArm(body[0], `${path3}.graph.0`, depth, "parallel");
2205
2712
  checkSingle(body[1], `${path3}.graph.1`, depth);
2206
2713
  upstream.add(body[0].id);
2207
2714
  upstream.add(singleId(body[1]));
2208
2715
  return;
2209
2716
  }
2210
2717
  checkId(singleId(node), path3);
2718
+ checkPolicyEnums(node, path3);
2211
2719
  checkTimeout(node, path3);
2212
2720
  checkTier(node, path3);
2213
2721
  checkRetry(node, path3);
@@ -2232,13 +2740,60 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2232
2740
  if (node.type === "workflow" && node.kind === "subrun" && depth > caps.maxNestingDepth) {
2233
2741
  err("cap-exceeded", `nesting depth ${depth} exceeds ${caps.maxNestingDepth}`, path3, node.id);
2234
2742
  }
2743
+ if (node.type === "workflow" && node.workflowId !== WORKFLOW_ARM_SUBRUN_ID && typeof g.definition?.id === "string" && node.workflowId === g.definition.id) {
2744
+ err("subrun-cycle", `"${node.id}" starts "${node.workflowId}", which is this workflow itself`, path3, node.id);
2745
+ }
2235
2746
  for (const ref of nodeStepRefs(node)) {
2236
2747
  if (!upstream.has(ref)) {
2237
2748
  err("template-reference-unresolved", `"${singleId(node)}" references stepResults.${ref}, which is not upstream`, path3, singleId(node));
2238
2749
  }
2239
2750
  }
2240
2751
  }, "checkSingle");
2241
- const checkArm = /* @__PURE__ */ __name3((arm, path3, depth) => {
2752
+ const checkHitl = /* @__PURE__ */ __name3((node, path3) => {
2753
+ if (node.type === "waitForSignal") {
2754
+ const w = node;
2755
+ checkId(w.id, path3);
2756
+ if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path3}.signal`, w.id);
2757
+ return;
2758
+ }
2759
+ const a = node;
2760
+ checkId(a.id, path3);
2761
+ if (a.approver === "creator" && a.excludeInitiator === true) {
2762
+ err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path3, a.id);
2763
+ }
2764
+ if (a.fourEyes !== void 0 && a.editable !== true) {
2765
+ err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path3}.fourEyes`, a.id);
2766
+ }
2767
+ if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2768
+ err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path3}.editablePaths`, a.id);
2769
+ }
2770
+ for (const p of a.editablePaths ?? []) {
2771
+ if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path3}.editablePaths`, a.id);
2772
+ }
2773
+ if (Array.isArray(a.onTimeout)) {
2774
+ const chain = a.onTimeout;
2775
+ const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2776
+ if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path3}.onTimeout`, a.id);
2777
+ const last = chain[chain.length - 1];
2778
+ if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2779
+ err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path3}.onTimeout`, a.id);
2780
+ }
2781
+ }
2782
+ if (typeof a.details === "string") {
2783
+ for (const ref of templateStepRefs(a.details)) {
2784
+ if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path3, a.id);
2785
+ }
2786
+ }
2787
+ }, "checkHitl");
2788
+ const checkHitlArm = /* @__PURE__ */ __name3((node, path3, container) => {
2789
+ if (!workflowContainerRunsHitlArm(container)) {
2790
+ checkId(node.id, path3);
2791
+ err("node-type-unsupported-in-container", workflowHitlArmUnsupportedMessage(node.type, node.id, container), path3, node.id);
2792
+ return;
2793
+ }
2794
+ checkHitl(node, path3);
2795
+ }, "checkHitlArm");
2796
+ const checkArm = /* @__PURE__ */ __name3((arm, path3, depth, container) => {
2242
2797
  if (arm.type === "mapping") {
2243
2798
  checkId(arm.id, path3);
2244
2799
  for (const ref of nodeStepRefs(arm)) {
@@ -2246,6 +2801,10 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2246
2801
  }
2247
2802
  return;
2248
2803
  }
2804
+ if (isHitlNode(arm)) {
2805
+ checkHitlArm(arm, path3, container);
2806
+ return;
2807
+ }
2249
2808
  checkSingle(arm, path3, depth);
2250
2809
  }, "checkArm");
2251
2810
  graph.forEach((entry, i) => {
@@ -2274,6 +2833,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2274
2833
  case "sleepUntil": {
2275
2834
  const s = entry;
2276
2835
  checkId(s.id, path3);
2836
+ err("node-type-unsupported-by-engine", sleepUntilUnsupportedMessage(s.id), path3, s.id);
2277
2837
  if (s.date === void 0 === (s.dateFrom === void 0)) {
2278
2838
  err("invalid-envelope", "sleepUntil needs exactly one of `date` | `dateFrom`", path3, s.id);
2279
2839
  }
@@ -2294,18 +2854,15 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2294
2854
  err("mapping-placement", "a bare mapping cannot be a parallel arm \u2014 chain it as [map, step]", armPath);
2295
2855
  return;
2296
2856
  }
2297
- if (arm.type === "approval" || arm.type === "waitForSignal") {
2298
- err("approval-inside-container", "approval / waitForSignal are top-level only in v1", armPath);
2299
- return;
2300
- }
2301
- checkSingle(arm, armPath, 1);
2857
+ checkArm(arm, armPath, 1, "parallel");
2302
2858
  declared.push(singleId(arm));
2303
2859
  });
2304
- const worktreeArms = p.steps.filter((a) => a.workspace?.isolation === "worktree");
2860
+ const executableArms = p.steps.filter(isSingleStep);
2861
+ const worktreeArms = executableArms.filter((a) => a.workspace?.isolation === "worktree");
2305
2862
  if (worktreeArms.length > 0 && !p.merge) err("worktree-arms-require-merge", "worktree arms need a `merge` policy", path3);
2306
2863
  if (worktreeArms.length === 0 && p.merge) err("merge-requires-worktree-arms", "`merge` needs at least one worktree arm", path3);
2307
2864
  if (worktreeArms.length > WORKFLOW_JOB_MAX_WORKTREE_ARMS) err("worktree-arms-exceed-cap", `at most ${WORKFLOW_JOB_MAX_WORKTREE_ARMS} worktree arms per parallel`, path3);
2308
- const sharedArms = p.steps.filter((a) => {
2865
+ const sharedArms = executableArms.filter((a) => {
2309
2866
  const w = workspaceOf(a);
2310
2867
  if (!w || w === "inherit" || w.isolation === "worktree") return false;
2311
2868
  return !(envelopeWorkspace?.backend === "efs" && w.mount === "ro");
@@ -2319,14 +2876,14 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2319
2876
  err("invalid-envelope", "conditional.predicates must match conditional.steps one-to-one", path3);
2320
2877
  }
2321
2878
  (c.predicates ?? []).forEach((p, j) => {
2322
- if (!isPredicate(p)) err("closure-predicate", "a conditional predicate must be a LuaPredicate object, not a function", `${path3}.predicates.${j}`);
2879
+ 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", `${path3}.predicates.${j}`);
2323
2880
  });
2324
2881
  c.steps.forEach((arm, j) => {
2325
- checkArm(arm, `${path3}.steps.${j}`, 1);
2882
+ checkArm(arm, `${path3}.steps.${j}`, 1, "conditional");
2326
2883
  declared.push(armId(arm));
2327
2884
  });
2328
2885
  if (c.otherwise) {
2329
- checkArm(c.otherwise, `${path3}.otherwise`, 1);
2886
+ checkArm(c.otherwise, `${path3}.otherwise`, 1, "conditional");
2330
2887
  declared.push(armId(c.otherwise));
2331
2888
  }
2332
2889
  break;
@@ -2349,8 +2906,9 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2349
2906
  }
2350
2907
  if (o.chunk !== void 0) {
2351
2908
  const max = o.maxItems ?? WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
2352
- if (!Number.isInteger(o.chunk.size) || o.chunk.size < 1 || o.chunk.size > max) {
2353
- err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, `${path3}.opts.chunk.size`);
2909
+ const size = typeof o.chunk === "number" ? o.chunk : o.chunk.size;
2910
+ if (!Number.isInteger(size) || size < 1 || size > max) {
2911
+ err("chunk-size-invalid", `foreach.chunk.size must be an integer in [1, ${max}]`, typeof o.chunk === "number" ? `${path3}.opts.chunk` : `${path3}.opts.chunk.size`);
2354
2912
  }
2355
2913
  }
2356
2914
  if (o.rateLimit !== void 0) {
@@ -2367,12 +2925,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2367
2925
  const bodyType = f.step.type;
2368
2926
  const prevEntry = i > 0 ? graph[i - 1] : void 0;
2369
2927
  if (bodyType !== "mapping" && prevEntry?.type === "mapping" && prevEntry.id === `${singleId(f.step)}_items`) {
2370
- let cfg;
2371
- try {
2372
- cfg = JSON.parse(prevEntry.mapConfig);
2373
- } catch {
2374
- cfg = void 0;
2375
- }
2928
+ const cfg = readMapConfig(prevEntry.mapConfig);
2376
2929
  const d = cfg?.[""];
2377
2930
  let arrayLike;
2378
2931
  if (d && "value" in d) arrayLike = Array.isArray(d.value);
@@ -2384,8 +2937,13 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2384
2937
  if (arrayLike === false) err("foreach-items-not-array", `foreach({items}) for "${singleId(f.step)}" binds a value that is not an array`, `${path3}.step`, singleId(f.step));
2385
2938
  }
2386
2939
  if (bodyType === "mapping") err("container-arm-empty", "a foreach body needs a step, not a bare mapping", `${path3}.step`);
2387
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path3}.step`);
2388
- else {
2940
+ else if (isHitlNode(f.step)) {
2941
+ if (o.chunk !== void 0) {
2942
+ checkId(f.step.id, `${path3}.step`);
2943
+ err("node-type-unsupported-in-container", workflowHitlArmShapeMessage(f.step.type, f.step.id, "chunked-foreach"), `${path3}.step`, f.step.id);
2944
+ } else checkHitlArm(f.step, `${path3}.step`, "foreach");
2945
+ declared.push(f.step.id);
2946
+ } else {
2389
2947
  checkSingle(f.step, `${path3}.step`, o.chunk ? 2 : 1);
2390
2948
  declared.push(singleId(f.step));
2391
2949
  }
@@ -2399,55 +2957,23 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2399
2957
  if (l.intervalSeconds !== void 0 && (!Number.isInteger(l.intervalSeconds) || l.intervalSeconds < 1 || l.intervalSeconds > caps.maxLoopIntervalSeconds)) {
2400
2958
  err("loop-interval-out-of-range", `loop.intervalSeconds must be an integer in 1..${caps.maxLoopIntervalSeconds}`, `${path3}.intervalSeconds`);
2401
2959
  }
2402
- if (!isPredicate(l.predicate)) err("closure-predicate", "a loop predicate must be a LuaPredicate object, not a function", `${path3}.predicate`);
2960
+ 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", `${path3}.predicate`);
2403
2961
  const bodyType = l.step.type;
2404
2962
  if (bodyType === "mapping") err("container-arm-empty", "a loop body needs a step, not a bare mapping", `${path3}.step`);
2405
- else if (bodyType === "approval" || bodyType === "waitForSignal") err("approval-inside-container", "approval / waitForSignal are top-level only in v1", `${path3}.step`);
2406
- else {
2963
+ else if (isHitlNode(l.step)) {
2964
+ checkHitlArm(l.step, `${path3}.step`, "loop");
2965
+ declared.push(l.step.id);
2966
+ } else {
2407
2967
  checkSingle(l.step, `${path3}.step`, 1);
2408
2968
  declared.push(singleId(l.step));
2409
2969
  }
2410
2970
  break;
2411
2971
  }
2412
- case "approval": {
2413
- const a = entry;
2414
- checkId(a.id, path3);
2415
- if (a.approver === "creator" && a.excludeInitiator === true) {
2416
- err("approver-excludes-only-candidate", "approver:'creator' with excludeInitiator:true always excludes the only candidate", path3, a.id);
2417
- }
2418
- if (a.fourEyes !== void 0 && a.editable !== true) {
2419
- err("four-eyes-requires-editable", "`fourEyes` requires editable:true", `${path3}.fourEyes`, a.id);
2420
- }
2421
- if ((a.editablePaths !== void 0 || a.editedPayloadSchema !== void 0) && a.editable !== true) {
2422
- err("editable-path-invalid", "`editablePaths` / `editedPayloadSchema` require editable:true", `${path3}.editablePaths`, a.id);
2423
- }
2424
- for (const p of a.editablePaths ?? []) {
2425
- if (!EDITABLE_PATH_RE.test(p)) err("editable-path-invalid", `editablePaths entry "${p}" is outside the seg(.seg)*[*]/[n] grammar`, `${path3}.editablePaths`, a.id);
2426
- }
2427
- if (Array.isArray(a.onTimeout)) {
2428
- const chain = a.onTimeout;
2429
- const hops = chain.filter((h) => typeof h === "object" && h !== null && "escalateTo" in h);
2430
- if (hops.length > 3) err("escalation-chain-too-long", "an onTimeout chain carries at most 3 escalation hops", `${path3}.onTimeout`, a.id);
2431
- const last = chain[chain.length - 1];
2432
- if (last === void 0 || typeof last === "object" && last !== null && "escalateTo" in last) {
2433
- err("escalation-chain-not-terminal", "an onTimeout chain must end in a terminal member", `${path3}.onTimeout`, a.id);
2434
- }
2435
- }
2436
- if (typeof a.details === "string") {
2437
- for (const ref of templateStepRefs(a.details)) {
2438
- if (!upstream.has(ref)) err("template-reference-unresolved", `"${a.id}" references stepResults.${ref}, which is not upstream`, path3, a.id);
2439
- }
2440
- }
2441
- declared.push(a.id);
2442
- break;
2443
- }
2444
- case "waitForSignal": {
2445
- const w = entry;
2446
- checkId(w.id, path3);
2447
- if (typeof w.signal !== "string" || w.signal.length === 0) err("invalid-envelope", "waitForSignal.signal is required", `${path3}.signal`, w.id);
2448
- declared.push(w.id);
2972
+ case "approval":
2973
+ case "waitForSignal":
2974
+ checkHitl(entry, path3);
2975
+ declared.push(entry.id);
2449
2976
  break;
2450
- }
2451
2977
  default:
2452
2978
  err("invalid-envelope", `unknown entry type "${entry.type}"`, path3);
2453
2979
  }
@@ -2462,6 +2988,41 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
2462
2988
  function isPredicate(p) {
2463
2989
  return typeof p === "object" && p !== null && typeof p.op === "string" && PREDICATE_OPS.has(p.op);
2464
2990
  }
2991
+ function isPathOrLiteral(v) {
2992
+ if (typeof v !== "object" || v === null) return false;
2993
+ const r = v;
2994
+ if ("path" in r) return typeof r.path === "string" && r.path.length > 0;
2995
+ return "literal" in r && isPredicateScalar(r.literal);
2996
+ }
2997
+ function isWellFormedPredicate(p) {
2998
+ if (!isPredicate(p)) return false;
2999
+ const r = p;
3000
+ switch (r.op) {
3001
+ case "eq":
3002
+ case "ne":
3003
+ case "lt":
3004
+ case "lte":
3005
+ case "gt":
3006
+ case "gte":
3007
+ return isPathOrLiteral(r.left) && isPathOrLiteral(r.right);
3008
+ case "in":
3009
+ case "notIn":
3010
+ return isPathOrLiteral(r.value) && Array.isArray(r.set) && r.set.every(isPredicateScalar);
3011
+ case "exists":
3012
+ case "notExists":
3013
+ return typeof r.path === "string" && r.path.length > 0;
3014
+ case "truthy":
3015
+ case "falsy":
3016
+ return isPathOrLiteral(r.value);
3017
+ case "and":
3018
+ case "or":
3019
+ return Array.isArray(r.args) && r.args.every(isWellFormedPredicate);
3020
+ case "not":
3021
+ return isWellFormedPredicate(r.arg);
3022
+ default:
3023
+ return false;
3024
+ }
3025
+ }
2465
3026
  function canonicalJson(value22) {
2466
3027
  const seen = /* @__PURE__ */ new WeakSet();
2467
3028
  const encode = /* @__PURE__ */ __name3((v) => {
@@ -2501,10 +3062,10 @@ function compilePlan(g) {
2501
3062
  let prevTails = [];
2502
3063
  const foreachJoinToEntry = /* @__PURE__ */ new Map();
2503
3064
  g.definition.graph.forEach((entry, entryIndex) => {
2504
- if (isSingleStep(entry)) {
2505
- const id = singleStepId(entry);
3065
+ if (isArmStep(entry)) {
3066
+ const id = armStepId(entry);
2506
3067
  addNode(id, {
2507
- kind: SINGLE_STEP_KINDS[entry.type],
3068
+ kind: armStepKind(entry),
2508
3069
  dependsOn: prevTails,
2509
3070
  downstream: [],
2510
3071
  unsatisfiedDeps: prevTails.length,
@@ -2529,9 +3090,8 @@ function compilePlan(g) {
2529
3090
  ];
2530
3091
  return;
2531
3092
  case "sleep":
2532
- case "sleepUntil":
2533
- case "approval": {
2534
- const kind = entry.type === "approval" ? "approval" : entry.type;
3093
+ case "sleepUntil": {
3094
+ const kind = entry.type;
2535
3095
  addNode(entry.id, {
2536
3096
  kind,
2537
3097
  dependsOn: prevTails,
@@ -2544,24 +3104,12 @@ function compilePlan(g) {
2544
3104
  ];
2545
3105
  return;
2546
3106
  }
2547
- case "waitForSignal":
2548
- addNode(entry.id, {
2549
- kind: "signal",
2550
- dependsOn: prevTails,
2551
- downstream: [],
2552
- unsatisfiedDeps: prevTails.length,
2553
- entry
2554
- });
2555
- prevTails = [
2556
- entry.id
2557
- ];
2558
- return;
2559
3107
  case "parallel": {
2560
3108
  const entryId = containerIdOf("parallel", entryIndex);
2561
3109
  const childIds = entry.steps.map((arm) => {
2562
- const childId = singleStepId(arm);
3110
+ const childId = armStepId(arm);
2563
3111
  addNode(childId, {
2564
- kind: SINGLE_STEP_KINDS[arm.type],
3112
+ kind: armStepKind(arm),
2565
3113
  dependsOn: prevTails,
2566
3114
  downstream: [],
2567
3115
  unsatisfiedDeps: prevTails.length,
@@ -2599,9 +3147,9 @@ function compilePlan(g) {
2599
3147
  node.otherwise
2600
3148
  ] : node.steps;
2601
3149
  const childIds = arms.map((arm) => {
2602
- const childId = arm.type === "mapping" ? arm.id : singleStepId(arm);
3150
+ const childId = arm.type === "mapping" ? arm.id : armStepId(arm);
2603
3151
  addNode(childId, {
2604
- kind: arm.type === "mapping" ? "map" : SINGLE_STEP_KINDS[arm.type],
3152
+ kind: arm.type === "mapping" ? "map" : armStepKind(arm),
2605
3153
  dependsOn: [
2606
3154
  entryId
2607
3155
  ],
@@ -2870,13 +3418,25 @@ function toPathOrLiteral(v) {
2870
3418
  literal: v
2871
3419
  };
2872
3420
  }
3421
+ function isMapConfigObject(v) {
3422
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3423
+ }
2873
3424
  function parseMapConfig(raw, stepId) {
3425
+ if (isMapConfigObject(raw)) return raw;
3426
+ if (typeof raw !== "string") {
3427
+ throw new Error(`Stored mapping step "${stepId}" has a mapConfig that is neither a JSON string nor an object.`);
3428
+ }
2874
3429
  try {
2875
3430
  return JSON.parse(raw);
2876
3431
  } catch (e) {
2877
3432
  throw new Error(`Stored mapping step "${stepId}" has invalid JSON mapConfig: ${e.message}`);
2878
3433
  }
2879
3434
  }
3435
+ function mapConfigWire(raw) {
3436
+ if (typeof raw === "string") return raw;
3437
+ if (isMapConfigObject(raw)) return canonicalJson(raw);
3438
+ return void 0;
3439
+ }
2880
3440
  function describeBadPlaceholder(template22, idx, rawExpr) {
2881
3441
  return `Template placeholder #${idx} (\${${rawExpr}}) in '${template22}'`;
2882
3442
  }
@@ -3024,6 +3584,25 @@ function resolveMapping(cfg, ctx) {
3024
3584
  value: result
3025
3585
  };
3026
3586
  }
3587
+ function continuedFailureValue(error, killReason) {
3588
+ const code = typeof error?.code === "string" && error.code || typeof killReason === "string" && killReason || CONTINUED_FAILURE_DEFAULT_CODE;
3589
+ const message = typeof error?.message === "string" && error.message ? error.message : code;
3590
+ return {
3591
+ __lua_workflow: CONTINUED_FAILURE_TAG,
3592
+ failed: true,
3593
+ error: {
3594
+ code,
3595
+ message
3596
+ },
3597
+ text: ""
3598
+ };
3599
+ }
3600
+ function isContinuedFailureValue(v) {
3601
+ if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
3602
+ const o = v;
3603
+ const err = o.error;
3604
+ return o.__lua_workflow === CONTINUED_FAILURE_TAG && o.failed === true && o.text === "" && err !== null && typeof err === "object" && typeof err.code === "string" && typeof err.message === "string";
3605
+ }
3027
3606
  function lowerContainerArm(mapping, step22) {
3028
3607
  const stepId = nodeIdOf(step22);
3029
3608
  return {
@@ -3104,7 +3683,28 @@ function resolvePlacements(calls) {
3104
3683
  break;
3105
3684
  }
3106
3685
  });
3107
- const resolve = /* @__PURE__ */ __name3((ref, i, allowMapping) => {
3686
+ const hitlPlacementIssue = /* @__PURE__ */ __name3((node, ref, i, container) => {
3687
+ if (!isHitlNode2(node)) return void 0;
3688
+ const id = node.id;
3689
+ if (ref.armMap) {
3690
+ return {
3691
+ code: "node-type-unsupported-in-container",
3692
+ message: workflowHitlArmShapeMessage(node.type, id, "mapped-arm"),
3693
+ callIndex: i,
3694
+ stepId: id
3695
+ };
3696
+ }
3697
+ if (container !== "place" && !workflowContainerRunsHitlArm(container)) {
3698
+ return {
3699
+ code: "node-type-unsupported-in-container",
3700
+ message: workflowHitlArmUnsupportedMessage(node.type, id, container),
3701
+ callIndex: i,
3702
+ stepId: id
3703
+ };
3704
+ }
3705
+ return void 0;
3706
+ }, "hitlPlacementIssue");
3707
+ const resolve3 = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
3108
3708
  if ("node" in ref) {
3109
3709
  if (ref.node.type === "mapping" && !allowMapping) {
3110
3710
  issues.push({
@@ -3121,7 +3721,7 @@ function resolvePlacements(calls) {
3121
3721
  if (!d) {
3122
3722
  issues.push({
3123
3723
  code: "unknown-step-ref",
3124
- message: `"${ref.ref}" is not declared anywhere in the chain \u2014 declare it with agentStep/specialistStep/toolStep/map(\u2026, { id })`,
3724
+ 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)`,
3125
3725
  callIndex: i,
3126
3726
  stepId: ref.ref
3127
3727
  });
@@ -3136,6 +3736,11 @@ function resolvePlacements(calls) {
3136
3736
  });
3137
3737
  return void 0;
3138
3738
  }
3739
+ const hitl = hitlPlacementIssue(d.node, ref, i, container);
3740
+ if (hitl) {
3741
+ issues.push(hitl);
3742
+ return void 0;
3743
+ }
3139
3744
  const prior = placedBy.get(ref.ref);
3140
3745
  if (prior !== void 0 && prior !== i) {
3141
3746
  issues.push({
@@ -3149,23 +3754,31 @@ function resolvePlacements(calls) {
3149
3754
  placedBy.set(ref.ref, i);
3150
3755
  return d.node;
3151
3756
  }, "resolve");
3757
+ const claim = /* @__PURE__ */ __name3((ref, i, allowMapping, container) => {
3758
+ if ("ref" in ref) {
3759
+ resolve3(ref, i, allowMapping, container);
3760
+ return;
3761
+ }
3762
+ const hitl = hitlPlacementIssue(ref.node, ref, i, container);
3763
+ if (hitl) issues.push(hitl);
3764
+ }, "claim");
3152
3765
  calls.forEach((call, i) => {
3153
3766
  switch (call.kind) {
3154
3767
  case "place":
3155
- resolve({
3768
+ claim({
3156
3769
  ref: call.ref
3157
- }, i, false);
3770
+ }, i, false, "place");
3158
3771
  break;
3159
3772
  case "parallel":
3160
- for (const arm of call.arms) if ("ref" in arm) resolve(arm, i, false);
3773
+ for (const arm of call.arms) claim(arm, i, false, "parallel");
3161
3774
  break;
3162
3775
  case "conditional":
3163
- for (const a of call.arms) if ("ref" in a.target) resolve(a.target, i, true);
3164
- if (call.otherwise && "ref" in call.otherwise) resolve(call.otherwise, i, true);
3776
+ for (const a of call.arms) claim(a.target, i, true, "conditional");
3777
+ if (call.otherwise) claim(call.otherwise, i, true, "conditional");
3165
3778
  break;
3166
3779
  case "foreach":
3167
3780
  case "loop":
3168
- if ("ref" in call.body) resolve(call.body, i, false);
3781
+ claim(call.body, i, false, call.kind);
3169
3782
  break;
3170
3783
  default:
3171
3784
  break;
@@ -3173,9 +3786,9 @@ function resolvePlacements(calls) {
3173
3786
  });
3174
3787
  const graph = [];
3175
3788
  const lookup = /* @__PURE__ */ __name3((ref) => {
3176
- const n = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3177
- if (!n || !ref.armMap || n.type === "mapping") return n;
3178
- return lowerContainerArm(ref.armMap, n);
3789
+ const n2 = "node" in ref ? ref.node : declared.get(ref.ref)?.node;
3790
+ if (!n2 || !ref.armMap || n2.type === "mapping" || isHitlNode2(n2)) return n2;
3791
+ return lowerContainerArm(ref.armMap, n2);
3179
3792
  }, "lookup");
3180
3793
  calls.forEach((call, i) => {
3181
3794
  switch (call.kind) {
@@ -3194,7 +3807,7 @@ function resolvePlacements(calls) {
3194
3807
  return;
3195
3808
  }
3196
3809
  case "parallel": {
3197
- const steps = call.arms.map(lookup).filter((n) => !!n && n.type !== "mapping");
3810
+ const steps = call.arms.map(lookup).filter((n2) => !!n2 && n2.type !== "mapping");
3198
3811
  const node = {
3199
3812
  type: "parallel",
3200
3813
  steps
@@ -3207,9 +3820,9 @@ function resolvePlacements(calls) {
3207
3820
  const steps = [];
3208
3821
  const predicates = [];
3209
3822
  for (const a of call.arms) {
3210
- const n = lookup(a.target);
3211
- if (!n) continue;
3212
- steps.push(n);
3823
+ const n2 = lookup(a.target);
3824
+ if (!n2) continue;
3825
+ steps.push(n2);
3213
3826
  predicates.push(a.predicate);
3214
3827
  }
3215
3828
  const node = {
@@ -3254,6 +3867,57 @@ function resolvePlacements(calls) {
3254
3867
  issues
3255
3868
  };
3256
3869
  }
3870
+ function isConditionalJoinId(stepId) {
3871
+ return CONDITIONAL_JOIN_ID.test(stepId);
3872
+ }
3873
+ function isPlainObject(v) {
3874
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3875
+ }
3876
+ function leafValue(row) {
3877
+ if (row.status === "completed") return row.output === void 0 ? null : row.output;
3878
+ return continuedFailureValue(row.error, row.killReason);
3879
+ }
3880
+ function runOutputLeaves(steps) {
3881
+ const dependedOn = /* @__PURE__ */ new Set();
3882
+ for (const s of steps) {
3883
+ if (s.stepId === GOAL_JUDGE_STEP_ID) continue;
3884
+ for (const d of s.dependsOn ?? []) dependedOn.add(d);
3885
+ }
3886
+ 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));
3887
+ }
3888
+ function deriveRunOutput(steps) {
3889
+ const leaves = runOutputLeaves(steps);
3890
+ if (leaves.length === 0) return void 0;
3891
+ if (leaves.length === 1) {
3892
+ const leaf = leaves[0];
3893
+ const value22 = leafValue(leaf);
3894
+ if (isConditionalJoinId(leaf.stepId) && isPlainObject(value22)) {
3895
+ const keys = Object.keys(value22);
3896
+ if (keys.length === 1) return {
3897
+ output: value22[keys[0]],
3898
+ leafIds: [
3899
+ keys[0]
3900
+ ]
3901
+ };
3902
+ }
3903
+ return {
3904
+ output: value22,
3905
+ leafIds: [
3906
+ leaf.stepId
3907
+ ]
3908
+ };
3909
+ }
3910
+ const output = {};
3911
+ for (const leaf of leaves) output[leaf.stepId] = leafValue(leaf);
3912
+ return {
3913
+ output,
3914
+ leafIds: leaves.map((l) => l.stepId)
3915
+ };
3916
+ }
3917
+ function subrunSettledOutput(child) {
3918
+ if (child.output !== void 0) return child.output;
3919
+ return deriveRunOutput(child.steps ?? [])?.output ?? null;
3920
+ }
3257
3921
  function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3258
3922
  const byId = /* @__PURE__ */ new Map();
3259
3923
  for (const s of steps) {
@@ -3276,15 +3940,15 @@ function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3276
3940
  parent
3277
3941
  ] : [];
3278
3942
  }, "dependsOf");
3279
- const walk2 = [
3943
+ const walk22 = [
3280
3944
  ...targetPlan.order
3281
3945
  ];
3282
3946
  for (const s of steps) {
3283
- if (!known.has(s.stepId) && parentOf(s.stepId) && known.has(parentOf(s.stepId)) && !walk2.includes(s.stepId)) {
3284
- walk2.push(s.stepId);
3947
+ if (!known.has(s.stepId) && parentOf(s.stepId) && known.has(parentOf(s.stepId)) && !walk22.includes(s.stepId)) {
3948
+ walk22.push(s.stepId);
3285
3949
  }
3286
3950
  }
3287
- for (const id of walk2) {
3951
+ for (const id of walk22) {
3288
3952
  const row = byId.get(id);
3289
3953
  if (!row || row.status !== "completed") continue;
3290
3954
  if (!dependsOf(id).every((d) => seededIds.has(d))) {
@@ -3313,31 +3977,54 @@ function seedLedgerFromRun(run, steps, targetPlan, opts = {}) {
3313
3977
  graphChanged
3314
3978
  };
3315
3979
  }
3980
+ function branchSpecFromConditional(entry) {
3981
+ return {
3982
+ arms: entry.steps.map((arm, i) => ({
3983
+ stepId: branchArmId(arm),
3984
+ predicate: entry.predicates[i]
3985
+ })),
3986
+ ...entry.exclusive === true ? {
3987
+ exclusive: true
3988
+ } : {},
3989
+ ...entry.otherwise ? {
3990
+ otherwise: branchArmId(entry.otherwise)
3991
+ } : {}
3992
+ };
3993
+ }
3994
+ function selectBranchArms(spec, ctx) {
3995
+ const taken = [];
3996
+ for (const arm of spec.arms) {
3997
+ if (spec.exclusive && taken.length > 0) break;
3998
+ const hit = arm.predicate ? evaluatePredicate(arm.predicate, ctx) : false;
3999
+ if (hit) taken.push(arm.stepId);
4000
+ }
4001
+ if (taken.length === 0 && spec.otherwise) taken.push(spec.otherwise);
4002
+ return taken;
4003
+ }
3316
4004
  function replayLedger(g, ledger) {
3317
4005
  const plan = compilePlan(g);
3318
4006
  const rows22 = new Map(ledger.steps.map((r) => [
3319
4007
  r.stepId,
3320
4008
  r
3321
4009
  ]));
3322
- const stepResults = {};
3323
- for (const r of ledger.steps) if (r.status === "completed") stepResults[r.stepId] = r.output;
3324
- const ctx = {
4010
+ const requestContext = {
4011
+ runId: "replay",
4012
+ workflowId: g.definition.id,
4013
+ orgId: "",
4014
+ agentId: "",
4015
+ userId: "",
4016
+ trigger: "sdk",
4017
+ threadId: "replay",
4018
+ depth: 0,
4019
+ startedAt: 0,
4020
+ ...ledger.requestContext
4021
+ };
4022
+ const ctxFor = /* @__PURE__ */ __name3((id) => ({
3325
4023
  initData: ledger.initData,
3326
- stepResults,
4024
+ stepResults: ancestorResults(plan, id, rows22),
3327
4025
  state: ledger.state ?? {},
3328
- requestContext: {
3329
- runId: "replay",
3330
- workflowId: g.definition.id,
3331
- orgId: "",
3332
- agentId: "",
3333
- userId: "",
3334
- trigger: "sdk",
3335
- threadId: "replay",
3336
- depth: 0,
3337
- startedAt: 0,
3338
- ...ledger.requestContext
3339
- }
3340
- };
4026
+ requestContext
4027
+ }), "ctxFor");
3341
4028
  const verdicts = [];
3342
4029
  for (const id of plan.order) {
3343
4030
  const node = plan.steps[id];
@@ -3345,17 +4032,7 @@ function replayLedger(g, ledger) {
3345
4032
  if (!recorded || recorded.status === "pending" || recorded.status === "skipped") continue;
3346
4033
  if (node.kind === "branch") {
3347
4034
  const entry = node.entry;
3348
- const taken = [];
3349
- entry.steps.forEach((arm, i) => {
3350
- const pred = entry.predicates[i];
3351
- const hit = pred ? evaluatePredicate(pred, {
3352
- initData: ledger.initData,
3353
- stepResults,
3354
- state: ledger.state
3355
- }) : false;
3356
- if (hit && (!entry.exclusive || taken.length === 0)) taken.push(armId2(arm));
3357
- });
3358
- if (taken.length === 0 && entry.otherwise) taken.push(armId2(entry.otherwise));
4035
+ const taken = selectBranchArms(branchSpecFromConditional(entry), ctxFor(id));
3359
4036
  const recordedTaken = recorded.taken ?? inferTaken(entry, rows22);
3360
4037
  verdicts.push({
3361
4038
  stepId: id,
@@ -3366,7 +4043,7 @@ function replayLedger(g, ledger) {
3366
4043
  });
3367
4044
  } else if (node.kind === "map" && !id.endsWith(".join") && recorded.status === "completed") {
3368
4045
  const entry = node.entry;
3369
- const resolved = resolveMapping(parseMapConfig(entry.mapConfig, id), ctx);
4046
+ const resolved = resolveMapping(parseMapConfig(entry.mapConfig, id), ctxFor(id));
3370
4047
  const local = "error" in resolved ? {
3371
4048
  error: resolved.error,
3372
4049
  key: resolved.key
@@ -3381,7 +4058,7 @@ function replayLedger(g, ledger) {
3381
4058
  } else if (node.kind === "foreach") {
3382
4059
  const entry = node.entry;
3383
4060
  const source = node.dependsOn[0];
3384
- const items = source ? stepResults[source] : void 0;
4061
+ const items = source ? ctxFor(id).stepResults[source] : void 0;
3385
4062
  const local = Array.isArray(items) ? items.length : void 0;
3386
4063
  const recordedCount = recorded.itemCount ?? countChildren(entry, rows22);
3387
4064
  verdicts.push({
@@ -3391,6 +4068,15 @@ function replayLedger(g, ledger) {
3391
4068
  local,
3392
4069
  diverged: recordedCount !== local
3393
4070
  });
4071
+ } else if (node.kind === "subrun" && recorded.status === "completed" && recorded.child) {
4072
+ const local = subrunSettledOutput(recorded.child);
4073
+ verdicts.push({
4074
+ stepId: id,
4075
+ kind: "subrun",
4076
+ recorded: recorded.output,
4077
+ local,
4078
+ diverged: canonical(recorded.output) !== canonical(local)
4079
+ });
3394
4080
  }
3395
4081
  }
3396
4082
  return {
@@ -3398,6 +4084,71 @@ function replayLedger(g, ledger) {
3398
4084
  diverged: verdicts.some((v) => v.diverged)
3399
4085
  };
3400
4086
  }
4087
+ function replayResultOf(row, node) {
4088
+ if (!row) return void 0;
4089
+ if (row.status === "completed") return {
4090
+ value: row.output
4091
+ };
4092
+ if (row.status === "failed") {
4093
+ const onError = row.onError ?? node?.entry?.onError;
4094
+ if (onError === "continue") return {
4095
+ value: continuedFailureValue(row.error, row.killReason)
4096
+ };
4097
+ }
4098
+ return void 0;
4099
+ }
4100
+ function ancestorResults(plan, id, rows22) {
4101
+ const out = {};
4102
+ const joinAliased = /* @__PURE__ */ new Set();
4103
+ const seen = /* @__PURE__ */ new Set();
4104
+ const take = /* @__PURE__ */ __name3((rowId) => {
4105
+ const hit = replayResultOf(rows22.get(rowId), plan.steps[rowId]);
4106
+ if (!hit) return void 0;
4107
+ if (!joinAliased.has(rowId)) out[rowId] = hit.value;
4108
+ const entryId = entryOfJoin(rowId);
4109
+ if (entryId) {
4110
+ out[entryId] = hit.value;
4111
+ joinAliased.add(entryId);
4112
+ const entryNode = plan.steps[entryId];
4113
+ if (entryNode?.kind === "foreach") {
4114
+ const body = branchArmId(entryNode.entry.step);
4115
+ out[body] = hit.value;
4116
+ joinAliased.add(body);
4117
+ }
4118
+ }
4119
+ return hit;
4120
+ }, "take");
4121
+ const walk22 = /* @__PURE__ */ __name3((ids) => {
4122
+ for (const dep of ids) {
4123
+ if (seen.has(dep)) continue;
4124
+ seen.add(dep);
4125
+ take(dep);
4126
+ const node = plan.steps[dep] ?? plan.steps[entryOfJoin(dep) ?? ""];
4127
+ if (!node) continue;
4128
+ if (node.kind === "foreach") {
4129
+ const body = branchArmId(node.entry.step);
4130
+ for (const rowId of rows22.keys()) if (rowId.startsWith(`${body}[`)) take(rowId);
4131
+ } else if (node.kind === "loop") {
4132
+ const body = branchArmId(node.entry.step);
4133
+ let best = -1;
4134
+ for (const rowId of rows22.keys()) {
4135
+ if (!rowId.startsWith(`${body}#`)) continue;
4136
+ const n2 = Number(rowId.slice(body.length + 1));
4137
+ if (!Number.isInteger(n2)) continue;
4138
+ const hit = take(rowId);
4139
+ if (!hit) continue;
4140
+ if (n2 > best) {
4141
+ best = n2;
4142
+ out[body] = hit.value;
4143
+ }
4144
+ }
4145
+ }
4146
+ walk22(node.dependsOn);
4147
+ }
4148
+ }, "walk");
4149
+ walk22(plan.steps[id]?.dependsOn ?? []);
4150
+ return out;
4151
+ }
3401
4152
  function inferTaken(entry, rows22) {
3402
4153
  const arms = [
3403
4154
  ...entry.steps,
@@ -3405,16 +4156,16 @@ function inferTaken(entry, rows22) {
3405
4156
  entry.otherwise
3406
4157
  ] : []
3407
4158
  ];
3408
- return arms.map(armId2).filter((id) => {
4159
+ return arms.map(branchArmId).filter((id) => {
3409
4160
  const r = rows22.get(id);
3410
4161
  return r !== void 0 && r.status !== "skipped" && r.status !== "pending";
3411
4162
  });
3412
4163
  }
3413
4164
  function countChildren(entry, rows22) {
3414
- const body = armId2(entry.step);
3415
- let n = 0;
3416
- for (const id of rows22.keys()) if (id.startsWith(`${body}[`)) n += 1;
3417
- return n;
4165
+ const body = branchArmId(entry.step);
4166
+ let n2 = 0;
4167
+ for (const id of rows22.keys()) if (id.startsWith(`${body}[`)) n2 += 1;
4168
+ return n2;
3418
4169
  }
3419
4170
  function isTerminalRunStatus(status) {
3420
4171
  return TERMINAL.has(status);
@@ -3423,19 +4174,48 @@ function pruneUndefined(o) {
3423
4174
  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
3424
4175
  }
3425
4176
  function runNextAction(run) {
3426
- if (isTerminalRunStatus(run.status) || !run.cancel?.requestedAt) return "none";
4177
+ if (isTerminalRunStatus(run.status)) return "none";
4178
+ if (run.status === "suspended" && run.gate?.kind === "budget") return "raise_budget";
4179
+ if (!run.cancel?.requestedAt) return "none";
3427
4180
  const forceAt = run.cancel.forceAfter ?? run.cancel.requestedAt + FORCE_CANCEL_STALE_MS;
3428
4181
  return Date.now() >= forceAt ? "force" : "cancel_again";
3429
4182
  }
4183
+ function emptyRunCounts() {
4184
+ const out = {
4185
+ total: 0,
4186
+ inFlight: 0
4187
+ };
4188
+ for (const status of WORKFLOW_STEP_STATUSES) out[status] = 0;
4189
+ return out;
4190
+ }
4191
+ function runCountsFromStatusTally(tally) {
4192
+ const out = emptyRunCounts();
4193
+ for (const [status, n2] of Object.entries(tally)) {
4194
+ if (!Number.isFinite(n2) || n2 <= 0) continue;
4195
+ out.total += n2;
4196
+ if (WORKFLOW_STEP_STATUSES.includes(status)) out[status] += n2;
4197
+ if (IN_FLIGHT.has(status)) out.inFlight += n2;
4198
+ }
4199
+ return out;
4200
+ }
4201
+ function runCountsFromStepStatuses(statuses) {
4202
+ const tally = {};
4203
+ for (const s of statuses) tally[s] = (tally[s] ?? 0) + 1;
4204
+ return runCountsFromStatusTally(tally);
4205
+ }
3430
4206
  function runCounts(counts) {
4207
+ const c = counts ?? {};
4208
+ const rawInFlight = c.dispatched !== void 0 || c.claimed !== void 0 || c.running !== void 0 || c.cancellation_requested !== void 0;
3431
4209
  return {
3432
- total: counts?.total ?? 0,
3433
- completed: counts?.completed ?? 0,
3434
- failed: counts?.failed ?? 0,
3435
- skipped: counts?.skipped ?? 0,
3436
- running: counts?.inFlight ?? 0,
3437
- suspended: counts?.suspended ?? 0,
3438
- pending: (counts?.pending ?? 0) + (counts?.ready ?? 0) + (counts?.waiting ?? 0)
4210
+ total: n(c.total),
4211
+ completed: n(c.completed),
4212
+ failed: n(c.failed) + n(c.timeout) + n(c.reaped),
4213
+ skipped: n(c.skipped),
4214
+ // LUA-664: cancelled rows never ran — their own wire bucket, never a failure (the detail's map, verbatim).
4215
+ cancelled: n(c.cancelled),
4216
+ running: rawInFlight ? n(c.dispatched) + n(c.claimed) + n(c.running) + n(c.cancellation_requested) : n(c.inFlight),
4217
+ suspended: n(c.suspended),
4218
+ pending: n(c.pending) + n(c.ready) + n(c.waiting)
3439
4219
  };
3440
4220
  }
3441
4221
  function runUsage(run) {
@@ -3452,14 +4232,30 @@ function runCancelView(cancel) {
3452
4232
  return {
3453
4233
  requestedAt: cancel.requestedAt,
3454
4234
  requestedBy: cancel.requestedBy ?? "",
3455
- forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS
4235
+ forceAvailableAt: cancel.forceAfter ?? cancel.requestedAt + FORCE_CANCEL_STALE_MS,
4236
+ // LUA-686: the wall's own request (LUA-683 stamps `wall:true` from a system actor only) rides the wire, so a
4237
+ // client can tell a wall-ended `timed_out` run's audit block from a cancel it should chip. Absent, never false.
4238
+ ...cancel.wall === true ? {
4239
+ wall: true
4240
+ } : {},
4241
+ // LUA-704: a forced terminal's audit — who forced it, when, and a human's own reason text (the run's `reason`
4242
+ // is the terminal's). Absent on an unforced terminal, never null.
4243
+ ...typeof cancel.forcedAt === "number" ? {
4244
+ forcedAt: cancel.forcedAt
4245
+ } : {},
4246
+ ...typeof cancel.forcedBy === "string" && cancel.forcedBy ? {
4247
+ forcedBy: cancel.forcedBy
4248
+ } : {},
4249
+ ...typeof cancel.forceReason === "string" && cancel.forceReason ? {
4250
+ forceReason: cancel.forceReason
4251
+ } : {}
3456
4252
  };
3457
4253
  }
3458
4254
  function runWorkspaceView(ws) {
3459
4255
  if (!ws) return void 0;
3460
4256
  const w = ws;
3461
4257
  return pruneUndefined({
3462
- kind: w.kind ?? "empty",
4258
+ kind: w.spec?.kind ?? w.kind ?? "empty",
3463
4259
  backend: w.backend,
3464
4260
  status: String(w.status ?? ""),
3465
4261
  branch: w.branch,
@@ -3471,7 +4267,6 @@ function runWorkspaceView(ws) {
3471
4267
  function toWorkflowRunSummary(run) {
3472
4268
  const status = run.status;
3473
4269
  const principal = run.principal?.principal;
3474
- const gated = status === "gated" || status === "suspended";
3475
4270
  return pruneUndefined({
3476
4271
  runId: run.id,
3477
4272
  workflowId: run.workflowId,
@@ -3483,7 +4278,14 @@ function toWorkflowRunSummary(run) {
3483
4278
  orgId: run.orgId,
3484
4279
  spaceAgentId: run.spaceAgentId,
3485
4280
  status,
3486
- gate: gated ? run.gate : void 0,
4281
+ // LUA-702 (pass-3B D6): the gate rides the wire whenever a NON-terminal doc carries one. In the LUA-681 window
4282
+ // (an approval resolved beside an open exception park) the run is `running` AND still holds `gate{exception}`
4283
+ // until the tick tail parks it back; projecting it only on gated|suspended hid that park from R4 / the CLI /
4284
+ // the desktop (prod 2026-09-05: `status: running, gate: null`). A terminal doc carries no gate by contract
4285
+ // (`terminalizeRun` drops `gate` / `suspendedFor` in every terminal write), but a row terminalized before that
4286
+ // held — pre-LUA-677/694 docs, a script-tier backstop that passed no `unset` — may still carry a stale one:
4287
+ // hidden here, so a `failed` run never reads "Needs a decision" on R3/R4 or the CLI (#2476 review).
4288
+ gate: run.gate && !isTerminalRunStatus(status) ? run.gate : void 0,
3487
4289
  batchId: run.batchId,
3488
4290
  goalId: run.goalId,
3489
4291
  foreachOverflow: run.foreachOverflow,
@@ -3506,9 +4308,11 @@ function toWorkflowRunSummary(run) {
3506
4308
  principalKind: run.principalKind ?? "user",
3507
4309
  cancel: runCancelView(run.cancel),
3508
4310
  usage: runUsage(run),
4311
+ // LUA-697: a row persisted before the write seams (#2406 / #2465 / the script tier) leaves scrubbed here too —
4312
+ // idempotent on a scrubbed message, bounded input; an empty message falls back to the code.
3509
4313
  error: run.error ? {
3510
4314
  code: run.error.code ?? "error",
3511
- message: run.error.message ?? "",
4315
+ message: scrubStepErrorMessage(run.error.message) ?? run.error.code ?? "error",
3512
4316
  stepId: run.error.stepId
3513
4317
  } : void 0,
3514
4318
  kind: "run",
@@ -3521,9 +4325,55 @@ function toWorkflowRunSummary(run) {
3521
4325
  eventSeq: run.eventSeq ?? 0,
3522
4326
  cancellable: !isTerminalRunStatus(status),
3523
4327
  nextAction: runNextAction(run),
3524
- workspace: runWorkspaceView(run.workspace)
4328
+ workspace: runWorkspaceView(run.workspace),
4329
+ // LUA-643: the runs list says a result exists; R4 `fields:'full'` (output-ACL gated, audited) serves it. The
4330
+ // stamped flag is what a list page carries (it projects `output` out); the payload members cover R4's full row.
4331
+ hasOutput: run.hasOutput === true || run.output !== void 0 || run.outputRef !== void 0 ? true : void 0
3525
4332
  });
3526
4333
  }
4334
+ function scrubDetailValue(value22, depth) {
4335
+ if (typeof value22 === "string") return scrubSecretText(value22);
4336
+ if (typeof value22 === "number" || typeof value22 === "boolean" || value22 === null) return value22;
4337
+ if (depth >= DETAIL_MAX_DEPTH) return void 0;
4338
+ if (Array.isArray(value22)) {
4339
+ return value22.slice(0, DETAIL_MAX_ITEMS).map((v) => scrubDetailValue(v, depth + 1)).filter((v) => v !== void 0);
4340
+ }
4341
+ if (typeof value22 === "object") {
4342
+ const out = {};
4343
+ for (const [k, v] of Object.entries(value22)) {
4344
+ const s = scrubDetailValue(v, depth + 1);
4345
+ if (s !== void 0) out[k] = s;
4346
+ }
4347
+ return out;
4348
+ }
4349
+ return void 0;
4350
+ }
4351
+ function stepErrorDetail(error) {
4352
+ if (!error || typeof error !== "object") return void 0;
4353
+ const d = error.detail;
4354
+ if (!d || typeof d !== "object" || Array.isArray(d)) return void 0;
4355
+ const bag = d;
4356
+ const out = {};
4357
+ for (const k of STEP_ERROR_DETAIL_KEYS) {
4358
+ if (!(k in bag)) continue;
4359
+ const v = scrubDetailValue(bag[k], 1);
4360
+ if (v !== void 0) out[k] = v;
4361
+ }
4362
+ if (!Object.keys(out).length) return void 0;
4363
+ let bytes;
4364
+ try {
4365
+ bytes = new TextEncoder().encode(JSON.stringify(out)).length;
4366
+ } catch {
4367
+ return void 0;
4368
+ }
4369
+ if (bytes <= STEP_ERROR_DETAIL_MAX_BYTES) return out;
4370
+ const scalars = Object.fromEntries(Object.entries(out).filter(([, v]) => v === null || typeof v !== "object"));
4371
+ return {
4372
+ ...scalars,
4373
+ __truncated: true,
4374
+ bytes
4375
+ };
4376
+ }
3527
4377
  function timeZoneSupported(tz) {
3528
4378
  if (typeof tz !== "string" || !tz) return false;
3529
4379
  const intl = Intl;
@@ -3987,6 +4837,22 @@ function rebaseItemPointer(pointer, itemsPath, index) {
3987
4837
  const base = `/${itemsPath.split(".").map(escapePointer).join("/")}/${index}`;
3988
4838
  return pointer === "/" || pointer === "" ? base : `${base}${pointer}`;
3989
4839
  }
4840
+ function describeApproverSpecRefusal(spec) {
4841
+ const raw = spec === void 0 ? "undefined" : JSON.stringify(spec) ?? String(spec);
4842
+ const written = raw.length > APPROVER_WRITTEN_MAX ? `${raw.slice(0, APPROVER_WRITTEN_MAX - 1)}\u2026` : raw;
4843
+ const users = typeof spec === "object" && spec !== null ? spec.users : void 0;
4844
+ const approver = typeof users === "string" && USER_ID_SHAPED_RE.test(users) ? {
4845
+ users: [
4846
+ users
4847
+ ]
4848
+ } : "creator";
4849
+ 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)}`);
4850
+ return {
4851
+ approver,
4852
+ written,
4853
+ message
4854
+ };
4855
+ }
3990
4856
  function bindingRootsOk(template22) {
3991
4857
  const refs = [
3992
4858
  ...template22.matchAll(/\$\{\s*([A-Za-z_$][\w$]*)/g)
@@ -4011,7 +4877,19 @@ function validateApproverBlock(node, opts = {
4011
4877
  if (!r.success) {
4012
4878
  const users = spec?.users;
4013
4879
  if (Array.isArray(users) && users.length > APPROVER_SPEC_MAX_USERS) push("cap-exceeded", path3, `at most ${APPROVER_SPEC_MAX_USERS} users`);
4014
- else push("approver-invalid", path3, r.error.issues[0]?.message ?? "invalid approver");
4880
+ else {
4881
+ const refusal = describeApproverSpecRefusal(spec);
4882
+ issues.push({
4883
+ code: "approver-invalid",
4884
+ path: path3,
4885
+ severity: "error",
4886
+ message: refusal.message,
4887
+ repair: {
4888
+ approver: refusal.approver,
4889
+ written: refusal.written
4890
+ }
4891
+ });
4892
+ }
4015
4893
  return;
4016
4894
  }
4017
4895
  const s = r.data;
@@ -4079,7 +4957,7 @@ function liftRenderedApprover(row, rendered) {
4079
4957
  }
4080
4958
  function collectEnvTemplateKeys(value22) {
4081
4959
  const keys = /* @__PURE__ */ new Set();
4082
- const walk2 = /* @__PURE__ */ __name3((v) => {
4960
+ const walk22 = /* @__PURE__ */ __name3((v) => {
4083
4961
  if (isEnvRef(v)) {
4084
4962
  keys.add(v.__envRef);
4085
4963
  return;
@@ -4087,26 +4965,26 @@ function collectEnvTemplateKeys(value22) {
4087
4965
  if (typeof v === "string") {
4088
4966
  if (looksLikeEmbeddedJson(v)) {
4089
4967
  try {
4090
- walk2(JSON.parse(v));
4968
+ walk22(JSON.parse(v));
4091
4969
  } catch {
4092
4970
  }
4093
4971
  }
4094
4972
  return;
4095
4973
  }
4096
4974
  if (Array.isArray(v)) {
4097
- for (const e of v) walk2(e);
4975
+ for (const e of v) walk22(e);
4098
4976
  return;
4099
4977
  }
4100
- if (v && typeof v === "object") for (const e of Object.values(v)) walk2(e);
4978
+ if (v && typeof v === "object") for (const e of Object.values(v)) walk22(e);
4101
4979
  }, "walk");
4102
- walk2(value22);
4980
+ walk22(value22);
4103
4981
  return [
4104
4982
  ...keys
4105
4983
  ].sort();
4106
4984
  }
4107
4985
  function substituteEnvRefs(value22, overlay) {
4108
4986
  const missing = /* @__PURE__ */ new Set();
4109
- const walk2 = /* @__PURE__ */ __name3((v, slot = false) => {
4987
+ const walk22 = /* @__PURE__ */ __name3((v, slot = false) => {
4110
4988
  if (isEnvRef(v)) {
4111
4989
  if (Object.prototype.hasOwnProperty.call(overlay, v.__envRef)) {
4112
4990
  const s = overlay[v.__envRef];
@@ -4123,22 +5001,22 @@ function substituteEnvRefs(value22, overlay) {
4123
5001
  const cfg = JSON.parse(v);
4124
5002
  if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) return v;
4125
5003
  const out = {};
4126
- for (const [k, e] of Object.entries(cfg)) out[k] = walk2(e, true);
5004
+ for (const [k, e] of Object.entries(cfg)) out[k] = walk22(e, true);
4127
5005
  return canonicalJson(out);
4128
5006
  } catch {
4129
5007
  return v;
4130
5008
  }
4131
5009
  }
4132
- if (Array.isArray(v)) return v.map((e) => walk2(e));
5010
+ if (Array.isArray(v)) return v.map((e) => walk22(e));
4133
5011
  if (v && typeof v === "object") {
4134
5012
  const out = {};
4135
- for (const [k, e] of Object.entries(v)) out[k] = walk2(e);
5013
+ for (const [k, e] of Object.entries(v)) out[k] = walk22(e);
4136
5014
  return out;
4137
5015
  }
4138
5016
  return v;
4139
5017
  }, "walk");
4140
5018
  return {
4141
- value: walk2(value22),
5019
+ value: walk22(value22),
4142
5020
  missing: [
4143
5021
  ...missing
4144
5022
  ].sort()
@@ -4216,8 +5094,8 @@ function armEntry(arm) {
4216
5094
  }
4217
5095
  function ofEntry(e) {
4218
5096
  if (!e || typeof e !== "object") return ZERO;
4219
- const n = e;
4220
- switch (n.type) {
5097
+ const n2 = e;
5098
+ switch (n2.type) {
4221
5099
  case "agent":
4222
5100
  return {
4223
5101
  steps: {
@@ -4250,17 +5128,17 @@ function ofEntry(e) {
4250
5128
  agentCalls: 0
4251
5129
  };
4252
5130
  case "parallel": {
4253
- const arms = Array.isArray(n.steps) ? n.steps : [];
5131
+ const arms = Array.isArray(n2.steps) ? n2.steps : [];
4254
5132
  return arms.map(armEntry).map(ofEntry).reduce(add, ZERO);
4255
5133
  }
4256
5134
  case "conditional": {
4257
- const arms = (Array.isArray(n.steps) ? n.steps : []).map(armEntry).map(ofEntry);
5135
+ const arms = (Array.isArray(n2.steps) ? n2.steps : []).map(armEntry).map(ofEntry);
4258
5136
  if (arms.length === 0) return ZERO;
4259
- const hasOtherwise = n.otherwise !== void 0;
5137
+ const hasOtherwise = n2.otherwise !== void 0;
4260
5138
  const minArm = arms.reduce((a, b) => b.credits.min < a.credits.min ? b : a);
4261
5139
  const maxArm = arms.reduce((a, b) => b.credits.max > a.credits.max ? b : a);
4262
5140
  const summed = arms.reduce(add, ZERO);
4263
- const max = n.exclusive === true ? maxArm : summed;
5141
+ const max = n2.exclusive === true ? maxArm : summed;
4264
5142
  const min = hasOtherwise ? minArm : {
4265
5143
  ...ZERO
4266
5144
  };
@@ -4277,17 +5155,14 @@ function ofEntry(e) {
4277
5155
  };
4278
5156
  }
4279
5157
  case "foreach": {
4280
- const body = ofEntry(armEntry(n.step));
4281
- const opts = n.options ?? {};
4282
- const cap = typeof opts.maxItems === "number" && opts.maxItems > 0 ? opts.maxItems : 1;
5158
+ const body = ofEntry(armEntry(n2.step));
5159
+ const opts = n2.opts ?? {};
5160
+ const cap = typeof opts.maxItems === "number" && opts.maxItems > 0 ? opts.maxItems : WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS;
4283
5161
  return scale(body, 0, cap);
4284
5162
  }
4285
- case "loop":
4286
- case "dowhile":
4287
- case "dountil": {
4288
- const body = ofEntry(armEntry(n.step));
4289
- const opts = n.options ?? {};
4290
- const cap = typeof opts.maxIterations === "number" && opts.maxIterations > 0 ? opts.maxIterations : 1;
5163
+ case "loop": {
5164
+ const body = ofEntry(armEntry(n2.step));
5165
+ const cap = typeof n2.maxIterations === "number" && n2.maxIterations > 0 ? n2.maxIterations : WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS;
4291
5166
  return scale(body, 1, cap);
4292
5167
  }
4293
5168
  default:
@@ -4318,14 +5193,85 @@ function estimateGraph(envelopeOrGraph) {
4318
5193
  consentCredits: r.credits.max
4319
5194
  };
4320
5195
  }
4321
- var __defProp3, __name3, WORKFLOW_ARM_SUBRUN_ID, WORKFLOW_CAPS_DEFAULT, WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS, WORKFLOW_AGENT_DEFAULT_TIMEOUT_SECONDS, WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS, WORKFLOW_FOREACH_DEFAULT_CONCURRENCY, WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS, WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS, WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS, WORKFLOW_SIGNAL_DEFAULT_SOURCES, clone, WORKFLOW_JOB_TOOLS, WORKFLOW_JOB_MAX_WORKTREE_ARMS, workspaceOf, mountsWorkspace, isJobTier, jobToolsOf, schemaIsArray, singleId, armId, TEMPLATE_STEP_REF, EDITABLE_PATH_RE, PREDICATE_OPS, GRAPH_HASH_PREFIX, WorkflowPlanError, SINGLE_STEP_KINDS, isSingleStep, singleStepId, joinIdOf, containerIdOf, PATH_PLACEHOLDER, MISSING, stepIdOf, cmp, eq, ne, gt, gte, lt, lte, inSet, notIn, exists, notExists, truthy, falsy, and, or, not, WorkflowTemplateError, TEMPLATE_PLACEHOLDER, TEMPLATE_NAMESPACES, fromInit, fromStep, value, template, fromRequest, rows, fromKnowledge, nodeIdOf, armId2, canonical, sortKeys, FORCE_CANCEL_STALE_MS, TERMINAL, MAX_HOLIDAYS, MAX_WALK_DAYS, HHMM, YMD, MS_PER_MIN, MS_PER_DAY, MON_FRI, supportedTz, fmtCache, WEEKDAYS, JSON_PATCH_OPS, JSON_PATCH_MAX_OPS, JSON_PATCH_MAX_VALUE_BYTES, JSON_PATCH_MAX_TOTAL_BYTES, SEGMENT_RE, APPROVER_SPEC_MAX_USERS, ESCALATION_MAX_HOPS, TemplateBindingSchema, ApproverSpecSchema, FourEyesSchema, EscalationHopSchema, TerminalOutcomeSchema, ApprovalOnTimeoutSchema, BINDING_ROOTS, WORKFLOW_ENV_OVERLAY_MAX_KEYS, WORKFLOW_ENV_OVERLAY_MAX_VALUE_BYTES, WORKFLOW_ENV_TEMPLATE_SECRET_KEY_RE, isEnvRef, looksLikeEmbeddedJson, ZERO;
5196
+ function* singleStepsOf(entry) {
5197
+ if (!isRecord2(entry)) return;
5198
+ switch (entry.type) {
5199
+ case "step":
5200
+ case "agent":
5201
+ case "tool":
5202
+ yield entry;
5203
+ return;
5204
+ case "workflow":
5205
+ yield entry;
5206
+ if (Array.isArray(entry.graph)) yield* singleStepsOf(entry.graph[1]);
5207
+ return;
5208
+ case "parallel":
5209
+ case "conditional":
5210
+ if (Array.isArray(entry.steps)) for (const arm of entry.steps) yield* singleStepsOf(arm);
5211
+ yield* singleStepsOf(entry.otherwise);
5212
+ return;
5213
+ case "foreach":
5214
+ case "loop":
5215
+ yield* singleStepsOf(entry.step);
5216
+ return;
5217
+ default:
5218
+ return;
5219
+ }
5220
+ }
5221
+ function entriesOf(graph) {
5222
+ const definition = isRecord2(graph) ? graph.definition : void 0;
5223
+ const entries = isRecord2(definition) ? definition.graph : void 0;
5224
+ return Array.isArray(entries) ? entries : [];
5225
+ }
5226
+ function inheritTargets(graphs) {
5227
+ const targets = /* @__PURE__ */ new Set();
5228
+ for (const graph of graphs) {
5229
+ for (const entry of entriesOf(graph)) {
5230
+ for (const node of singleStepsOf(entry)) {
5231
+ if (node.type === "workflow" && node.workspace === "inherit" && typeof node.workflowId === "string" && node.workflowId.length > 0) {
5232
+ targets.add(node.workflowId);
5233
+ }
5234
+ }
5235
+ }
5236
+ }
5237
+ return targets;
5238
+ }
5239
+ function needsInheritedWorkspace(graph) {
5240
+ if (!isRecord2(graph) || graph.workspace !== void 0) return false;
5241
+ for (const entry of entriesOf(graph)) {
5242
+ for (const node of singleStepsOf(entry)) {
5243
+ if (node.workspace !== void 0 && node.workspace !== "inherit") return true;
5244
+ }
5245
+ }
5246
+ return false;
5247
+ }
5248
+ var __defProp3, __name3, SideEffectsSchema, JobResourcesSchema, WORKFLOW_ARM_SUBRUN_ID, WORKSPACE_TEMPLATE_EXPR_RE, SLEEP_UNTIL_REPLACEMENT, WORKFLOW_CAPS_DEFAULT, WORKFLOW_STEP_DEFAULT_TIMEOUT_SECONDS, WORKFLOW_AGENT_DEFAULT_TIMEOUT_SECONDS, WORKFLOW_JOB_DEFAULT_TIMEOUT_SECONDS, WORKFLOW_FOREACH_DEFAULT_CONCURRENCY, WORKFLOW_FOREACH_DEFAULT_MAX_ITEMS, WORKFLOW_LOOP_DEFAULT_MAX_ITERATIONS, WORKFLOW_SUSPEND_DEFAULT_TIMEOUT_HOURS, WORKFLOW_SIGNAL_DEFAULT_SOURCES, clone, CONNECTION_ID_HEX_RE, WORKFLOW_JOB_TOOLS, WORKFLOW_JOB_MAX_WORKTREE_ARMS, workspaceOf, mountsWorkspace, isJobTier, jobToolsOf, schemaIsArray, isHitlNode, isSingleStep, singleId, armId, TEMPLATE_STEP_REF, EDITABLE_PATH_RE, PREDICATE_OPS, isPredicateScalar, GRAPH_HASH_PREFIX, WorkflowPlanError, isArmStep, armStepId, armStepKind, joinIdOf, containerIdOf, PATH_PLACEHOLDER, MISSING, stepIdOf, cmp, eq, ne, gt, gte, lt, lte, inSet, notIn, exists, notExists, truthy, falsy, and, or, not, WorkflowTemplateError, TEMPLATE_PLACEHOLDER, TEMPLATE_NAMESPACES, fromInit, fromStep, value, template, fromRequest, rows, fromKnowledge, CONTINUED_FAILURE_TAG, CONTINUED_FAILURE_DEFAULT_CODE, CONTINUED_FAILURE_OUTPUT_SCHEMA, CONTINUED_FAILURE_LEAF_PATHS, isHitlNode2, nodeIdOf, GOAL_JUDGE_STEP_ID, NON_LEAF_KINDS, CONDITIONAL_JOIN_ID, branchArmId, canonical, sortKeys, JOIN, entryOfJoin, FORCE_CANCEL_STALE_MS, TERMINAL, IN_FLIGHT, n, STEP_ERROR_DETAIL_KEYS, STEP_ERROR_DETAIL_MAX_BYTES, DETAIL_MAX_DEPTH, DETAIL_MAX_ITEMS, MAX_HOLIDAYS, MAX_WALK_DAYS, HHMM, YMD, MS_PER_MIN, MS_PER_DAY, MON_FRI, supportedTz, fmtCache, WEEKDAYS, JSON_PATCH_OPS, JSON_PATCH_MAX_OPS, JSON_PATCH_MAX_VALUE_BYTES, JSON_PATCH_MAX_TOTAL_BYTES, SEGMENT_RE, APPROVER_SPEC_MAX_USERS, ESCALATION_MAX_HOPS, TemplateBindingSchema, ApproverSpecSchema, FourEyesSchema, EscalationHopSchema, TerminalOutcomeSchema, ApprovalOnTimeoutSchema, APPROVER_SPEC_SHAPES, APPROVER_WRITTEN_MAX, USER_ID_SHAPED_RE, BINDING_ROOTS, WORKFLOW_ENV_OVERLAY_MAX_KEYS, WORKFLOW_ENV_OVERLAY_MAX_VALUE_BYTES, WORKFLOW_ENV_TEMPLATE_SECRET_KEY_RE, isEnvRef, looksLikeEmbeddedJson, ZERO, isRecord2;
4322
5249
  var init_dist2 = __esm({
4323
5250
  "../workflow-graph/dist/index.mjs"() {
4324
5251
  "use strict";
4325
5252
  init_dist();
5253
+ init_dist();
5254
+ init_dist();
5255
+ init_dist();
5256
+ init_dist();
5257
+ init_dist();
5258
+ init_dist();
4326
5259
  __defProp3 = Object.defineProperty;
4327
5260
  __name3 = /* @__PURE__ */ __name((target, value22) => __defProp3(target, "name", { value: value22, configurable: true }), "__name");
5261
+ SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
5262
+ JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
4328
5263
  WORKFLOW_ARM_SUBRUN_ID = "$arm";
5264
+ WORKSPACE_TEMPLATE_EXPR_RE = /^\$\{\s*(?:initData|input)\.([^}]+?)\s*\}$/;
5265
+ __name(workspaceTemplatePath, "workspaceTemplatePath");
5266
+ __name3(workspaceTemplatePath, "workspaceTemplatePath");
5267
+ __name(retryBackoffs, "retryBackoffs");
5268
+ __name3(retryBackoffs, "retryBackoffs");
5269
+ SLEEP_UNTIL_REPLACEMENT = Object.freeze({
5270
+ type: "sleep",
5271
+ duration: 6e4
5272
+ });
5273
+ __name(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
5274
+ __name3(sleepUntilUnsupportedMessage, "sleepUntilUnsupportedMessage");
4329
5275
  WORKFLOW_CAPS_DEFAULT = Object.freeze({
4330
5276
  maxParallelArms: 16,
4331
5277
  maxForeachConcurrency: 16,
@@ -4355,12 +5301,19 @@ var init_dist2 = __esm({
4355
5301
  __name3(fillPolicy, "fillPolicy");
4356
5302
  __name(fillSingle, "fillSingle");
4357
5303
  __name3(fillSingle, "fillSingle");
5304
+ __name(fillHitl, "fillHitl");
5305
+ __name3(fillHitl, "fillHitl");
4358
5306
  __name(fillArm, "fillArm");
4359
5307
  __name3(fillArm, "fillArm");
4360
5308
  __name(fillEntry, "fillEntry");
4361
5309
  __name3(fillEntry, "fillEntry");
4362
5310
  __name(withDefaultsFilled, "withDefaultsFilled");
4363
5311
  __name3(withDefaultsFilled, "withDefaultsFilled");
5312
+ CONNECTION_ID_HEX_RE = /^[0-9a-f]{24}$/;
5313
+ __name(isConnectionKeyShaped, "isConnectionKeyShaped");
5314
+ __name3(isConnectionKeyShaped, "isConnectionKeyShaped");
5315
+ __name(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
5316
+ __name3(connectionKeyUndeclaredMessage, "connectionKeyUndeclaredMessage");
4364
5317
  WORKFLOW_JOB_TOOLS = [
4365
5318
  "shell",
4366
5319
  "read",
@@ -4393,11 +5346,15 @@ var init_dist2 = __esm({
4393
5346
  if (t === void 0) return void 0;
4394
5347
  return Array.isArray(t) ? t.includes("array") : t === "array";
4395
5348
  }, "schemaIsArray");
5349
+ isHitlNode = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
5350
+ isSingleStep = /* @__PURE__ */ __name3((n2) => !isHitlNode(n2), "isSingleStep");
4396
5351
  singleId = /* @__PURE__ */ __name3((s) => s.type === "step" ? s.step.id : s.id, "singleId");
4397
5352
  armId = /* @__PURE__ */ __name3((a) => a.type === "mapping" ? a.id : singleId(a), "armId");
4398
5353
  TEMPLATE_STEP_REF = /\$\{\s*stepResults\.([A-Za-z0-9_\-]+)/g;
4399
5354
  __name(templateStepRefs, "templateStepRefs");
4400
5355
  __name3(templateStepRefs, "templateStepRefs");
5356
+ __name(readMapConfig, "readMapConfig");
5357
+ __name3(readMapConfig, "readMapConfig");
4401
5358
  __name(mapConfigStepRefs, "mapConfigStepRefs");
4402
5359
  __name3(mapConfigStepRefs, "mapConfigStepRefs");
4403
5360
  __name(nodeStepRefs, "nodeStepRefs");
@@ -4424,6 +5381,11 @@ var init_dist2 = __esm({
4424
5381
  ]);
4425
5382
  __name(isPredicate, "isPredicate");
4426
5383
  __name3(isPredicate, "isPredicate");
5384
+ isPredicateScalar = /* @__PURE__ */ __name3((v) => v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean", "isPredicateScalar");
5385
+ __name(isPathOrLiteral, "isPathOrLiteral");
5386
+ __name3(isPathOrLiteral, "isPathOrLiteral");
5387
+ __name(isWellFormedPredicate, "isWellFormedPredicate");
5388
+ __name3(isWellFormedPredicate, "isWellFormedPredicate");
4427
5389
  GRAPH_HASH_PREFIX = "sha256-cj1:";
4428
5390
  __name(canonicalJson, "canonicalJson");
4429
5391
  __name3(canonicalJson, "canonicalJson");
@@ -4442,14 +5404,9 @@ var init_dist2 = __esm({
4442
5404
  this.name = "WorkflowPlanError";
4443
5405
  }
4444
5406
  };
4445
- SINGLE_STEP_KINDS = {
4446
- step: "code",
4447
- agent: "agent",
4448
- tool: "tool",
4449
- workflow: "subrun"
4450
- };
4451
- isSingleStep = /* @__PURE__ */ __name3((e) => e.type === "step" || e.type === "agent" || e.type === "tool" || e.type === "workflow", "isSingleStep");
4452
- singleStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "singleStepId");
5407
+ isArmStep = /* @__PURE__ */ __name3((e) => isWorkflowArmEntryType(e.type), "isArmStep");
5408
+ armStepId = /* @__PURE__ */ __name3((e) => e.type === "step" ? e.step.id : e.id, "armStepId");
5409
+ armStepKind = /* @__PURE__ */ __name3((e) => WORKFLOW_ARM_ENTRY_STEP_KINDS[e.type], "armStepKind");
4453
5410
  joinIdOf = /* @__PURE__ */ __name3((entryId) => `${entryId}.join`, "joinIdOf");
4454
5411
  containerIdOf = /* @__PURE__ */ __name3((type, entryIndex) => `${type}@${entryIndex}`, "containerIdOf");
4455
5412
  __name(compilePlan, "compilePlan");
@@ -4557,8 +5514,12 @@ var init_dist2 = __esm({
4557
5514
  this.name = "WorkflowTemplateError";
4558
5515
  }
4559
5516
  };
5517
+ __name(isMapConfigObject, "isMapConfigObject");
5518
+ __name3(isMapConfigObject, "isMapConfigObject");
4560
5519
  __name(parseMapConfig, "parseMapConfig");
4561
5520
  __name3(parseMapConfig, "parseMapConfig");
5521
+ __name(mapConfigWire, "mapConfigWire");
5522
+ __name3(mapConfigWire, "mapConfigWire");
4562
5523
  TEMPLATE_PLACEHOLDER = /\$\{([^}]*)\}/g;
4563
5524
  TEMPLATE_NAMESPACES = [
4564
5525
  "initData",
@@ -4612,16 +5573,90 @@ var init_dist2 = __esm({
4612
5573
  fromKnowledge = /* @__PURE__ */ __name3((k) => ({
4613
5574
  knowledge: k
4614
5575
  }), "fromKnowledge");
5576
+ CONTINUED_FAILURE_TAG = "continued_failure";
5577
+ CONTINUED_FAILURE_DEFAULT_CODE = "step_failed";
5578
+ CONTINUED_FAILURE_OUTPUT_SCHEMA = Object.freeze({
5579
+ type: "object",
5580
+ properties: {
5581
+ __lua_workflow: {
5582
+ type: "string",
5583
+ const: CONTINUED_FAILURE_TAG
5584
+ },
5585
+ failed: {
5586
+ type: "boolean",
5587
+ const: true
5588
+ },
5589
+ error: {
5590
+ type: "object",
5591
+ properties: {
5592
+ code: {
5593
+ type: "string"
5594
+ },
5595
+ message: {
5596
+ type: "string"
5597
+ }
5598
+ },
5599
+ required: [
5600
+ "code",
5601
+ "message"
5602
+ ]
5603
+ },
5604
+ text: {
5605
+ type: "string",
5606
+ const: ""
5607
+ }
5608
+ },
5609
+ required: [
5610
+ "__lua_workflow",
5611
+ "failed",
5612
+ "error",
5613
+ "text"
5614
+ ]
5615
+ });
5616
+ CONTINUED_FAILURE_LEAF_PATHS = Object.freeze([
5617
+ "failed",
5618
+ "error",
5619
+ "error.code",
5620
+ "error.message",
5621
+ "text"
5622
+ ]);
5623
+ __name(continuedFailureValue, "continuedFailureValue");
5624
+ __name3(continuedFailureValue, "continuedFailureValue");
5625
+ __name(isContinuedFailureValue, "isContinuedFailureValue");
5626
+ __name3(isContinuedFailureValue, "isContinuedFailureValue");
5627
+ isHitlNode2 = /* @__PURE__ */ __name3((n2) => isWorkflowHitlEntryType(n2.type), "isHitlNode");
4615
5628
  __name(lowerContainerArm, "lowerContainerArm");
4616
5629
  __name3(lowerContainerArm, "lowerContainerArm");
4617
- nodeIdOf = /* @__PURE__ */ __name3((n) => n.type === "step" ? n.step.id : n.id, "nodeIdOf");
5630
+ nodeIdOf = /* @__PURE__ */ __name3((n2) => n2.type === "step" ? n2.step.id : n2.id, "nodeIdOf");
4618
5631
  __name(entryIds, "entryIds");
4619
5632
  __name3(entryIds, "entryIds");
4620
5633
  __name(resolvePlacements, "resolvePlacements");
4621
5634
  __name3(resolvePlacements, "resolvePlacements");
5635
+ GOAL_JUDGE_STEP_ID = "__goal_judge";
5636
+ NON_LEAF_KINDS = /* @__PURE__ */ new Set([
5637
+ "foreach",
5638
+ "branch"
5639
+ ]);
5640
+ CONDITIONAL_JOIN_ID = /^conditional@\d+\.join$/;
5641
+ __name(isConditionalJoinId, "isConditionalJoinId");
5642
+ __name3(isConditionalJoinId, "isConditionalJoinId");
5643
+ __name(isPlainObject, "isPlainObject");
5644
+ __name3(isPlainObject, "isPlainObject");
5645
+ __name(leafValue, "leafValue");
5646
+ __name3(leafValue, "leafValue");
5647
+ __name(runOutputLeaves, "runOutputLeaves");
5648
+ __name3(runOutputLeaves, "runOutputLeaves");
5649
+ __name(deriveRunOutput, "deriveRunOutput");
5650
+ __name3(deriveRunOutput, "deriveRunOutput");
5651
+ __name(subrunSettledOutput, "subrunSettledOutput");
5652
+ __name3(subrunSettledOutput, "subrunSettledOutput");
4622
5653
  __name(seedLedgerFromRun, "seedLedgerFromRun");
4623
5654
  __name3(seedLedgerFromRun, "seedLedgerFromRun");
4624
- armId2 = /* @__PURE__ */ __name3((arm) => arm.type === "step" ? arm.step.id : arm.id, "armId");
5655
+ branchArmId = /* @__PURE__ */ __name3((arm) => arm.type === "step" ? arm.step.id : arm.id, "branchArmId");
5656
+ __name(branchSpecFromConditional, "branchSpecFromConditional");
5657
+ __name3(branchSpecFromConditional, "branchSpecFromConditional");
5658
+ __name(selectBranchArms, "selectBranchArms");
5659
+ __name3(selectBranchArms, "selectBranchArms");
4625
5660
  canonical = /* @__PURE__ */ __name3((v) => JSON.stringify(sortKeys(v)), "canonical");
4626
5661
  sortKeys = /* @__PURE__ */ __name3((v) => {
4627
5662
  if (Array.isArray(v)) return v.map(sortKeys);
@@ -4635,6 +5670,12 @@ var init_dist2 = __esm({
4635
5670
  }, "sortKeys");
4636
5671
  __name(replayLedger, "replayLedger");
4637
5672
  __name3(replayLedger, "replayLedger");
5673
+ JOIN = ".join";
5674
+ entryOfJoin = /* @__PURE__ */ __name3((id) => id.endsWith(JOIN) ? id.slice(0, -JOIN.length) : void 0, "entryOfJoin");
5675
+ __name(replayResultOf, "replayResultOf");
5676
+ __name3(replayResultOf, "replayResultOf");
5677
+ __name(ancestorResults, "ancestorResults");
5678
+ __name3(ancestorResults, "ancestorResults");
4638
5679
  __name(inferTaken, "inferTaken");
4639
5680
  __name3(inferTaken, "inferTaken");
4640
5681
  __name(countChildren, "countChildren");
@@ -4647,6 +5688,14 @@ var init_dist2 = __esm({
4647
5688
  __name3(pruneUndefined, "pruneUndefined");
4648
5689
  __name(runNextAction, "runNextAction");
4649
5690
  __name3(runNextAction, "runNextAction");
5691
+ IN_FLIGHT = new Set(WORKFLOW_STEP_IN_FLIGHT);
5692
+ __name(emptyRunCounts, "emptyRunCounts");
5693
+ __name3(emptyRunCounts, "emptyRunCounts");
5694
+ __name(runCountsFromStatusTally, "runCountsFromStatusTally");
5695
+ __name3(runCountsFromStatusTally, "runCountsFromStatusTally");
5696
+ __name(runCountsFromStepStatuses, "runCountsFromStepStatuses");
5697
+ __name3(runCountsFromStepStatuses, "runCountsFromStepStatuses");
5698
+ n = /* @__PURE__ */ __name3((v) => typeof v === "number" && Number.isFinite(v) ? v : 0, "n");
4650
5699
  __name(runCounts, "runCounts");
4651
5700
  __name3(runCounts, "runCounts");
4652
5701
  __name(runUsage, "runUsage");
@@ -4657,6 +5706,43 @@ var init_dist2 = __esm({
4657
5706
  __name3(runWorkspaceView, "runWorkspaceView");
4658
5707
  __name(toWorkflowRunSummary, "toWorkflowRunSummary");
4659
5708
  __name3(toWorkflowRunSummary, "toWorkflowRunSummary");
5709
+ STEP_ERROR_DETAIL_KEYS = [
5710
+ "reason",
5711
+ "key",
5712
+ "integrationType",
5713
+ "candidates",
5714
+ "connectionId",
5715
+ "status",
5716
+ "code",
5717
+ "providerStatus",
5718
+ "model",
5719
+ "turnIndex",
5720
+ "message",
5721
+ // LUA-655: `output_schema_invalid` — the Ajv issues ({path, message}, ≤ 20, scrubbed at the source too) and the
5722
+ // in-session repair rounds the attempt spent.
5723
+ "issues",
5724
+ "repairRounds",
5725
+ // LUA-669 (#2446 review 4): the subrun failures — `subrun_<status>` names the child run and how it ended
5726
+ // (`childStatus`, `childReason: 'max_duration'` under `subrun_timed_out`), `subrun_depth_exceeded` its `depth` /
5727
+ // `max`, and every refusal the target `workflowId`. Short scalars only: the child's whole `childError` and the
5728
+ // cycle walk's `ancestors` stay off the wire — the child run's own R4 carries its error.
5729
+ "childRunId",
5730
+ "childStatus",
5731
+ "childReason",
5732
+ "max",
5733
+ "depth",
5734
+ "workflowId",
5735
+ // LUA-696 (review 2): the `ctx.once` key of an `effect_in_doubt` park — the step site stamps it here (scrubbed)
5736
+ // beside `park.effectKey`; a key is user text and leaves scrubbed like every other string leaf.
5737
+ "effectKey"
5738
+ ];
5739
+ STEP_ERROR_DETAIL_MAX_BYTES = 8 * 1024;
5740
+ DETAIL_MAX_DEPTH = 4;
5741
+ DETAIL_MAX_ITEMS = 100;
5742
+ __name(scrubDetailValue, "scrubDetailValue");
5743
+ __name3(scrubDetailValue, "scrubDetailValue");
5744
+ __name(stepErrorDetail, "stepErrorDetail");
5745
+ __name3(stepErrorDetail, "stepErrorDetail");
4660
5746
  MAX_HOLIDAYS = 366;
4661
5747
  MAX_WALK_DAYS = 400;
4662
5748
  HHMM = /^([01]\d|2[0-3]):([0-5]\d)$/;
@@ -4750,58 +5836,70 @@ var init_dist2 = __esm({
4750
5836
  __name3(rebaseItemPointer, "rebaseItemPointer");
4751
5837
  APPROVER_SPEC_MAX_USERS = 20;
4752
5838
  ESCALATION_MAX_HOPS = 3;
4753
- TemplateBindingSchema = z4.object({
4754
- template: z4.string().min(1).max(2048)
5839
+ TemplateBindingSchema = z22.object({
5840
+ template: z22.string().min(1).max(2048)
4755
5841
  }).strict();
4756
- ApproverSpecSchema = z4.union([
4757
- z4.literal("creator"),
4758
- z4.literal("org-admins"),
4759
- z4.object({
4760
- users: z4.union([
4761
- z4.array(z4.string().min(1).max(128)).min(1).max(APPROVER_SPEC_MAX_USERS),
5842
+ ApproverSpecSchema = z22.union([
5843
+ z22.literal("creator"),
5844
+ z22.literal("org-admins"),
5845
+ z22.object({
5846
+ users: z22.union([
5847
+ z22.array(z22.string().min(1).max(128)).min(1).max(APPROVER_SPEC_MAX_USERS),
4762
5848
  TemplateBindingSchema
4763
5849
  ])
4764
5850
  }).strict(),
4765
- z4.object({
4766
- role: z4.union([
4767
- z4.string().min(1).max(128),
5851
+ z22.object({
5852
+ role: z22.union([
5853
+ z22.string().min(1).max(128),
4768
5854
  TemplateBindingSchema
4769
5855
  ])
4770
5856
  }).strict(),
4771
- z4.object({
4772
- group: z4.union([
4773
- z4.string().min(1).max(128),
5857
+ z22.object({
5858
+ group: z22.union([
5859
+ z22.string().min(1).max(128),
4774
5860
  TemplateBindingSchema
4775
5861
  ])
4776
5862
  }).strict(),
4777
- z4.object({
4778
- governance: z4.object({
4779
- policyId: z4.string().min(1).max(128)
5863
+ z22.object({
5864
+ governance: z22.object({
5865
+ policyId: z22.string().min(1).max(128)
4780
5866
  }).strict()
4781
5867
  }).strict()
4782
5868
  ]);
4783
- FourEyesSchema = z4.object({
5869
+ FourEyesSchema = z22.object({
4784
5870
  edit: ApproverSpecSchema,
4785
5871
  approve: ApproverSpecSchema
4786
5872
  }).strict();
4787
- EscalationHopSchema = z4.object({
5873
+ EscalationHopSchema = z22.object({
4788
5874
  escalateTo: ApproverSpecSchema,
4789
- timeoutHours: z4.number().finite().min(1).max(720)
5875
+ timeoutHours: z22.number().finite().min(1).max(720)
4790
5876
  }).strict();
4791
- TerminalOutcomeSchema = z4.enum([
5877
+ TerminalOutcomeSchema = z22.enum([
4792
5878
  "deny",
4793
5879
  "cancel-run",
4794
5880
  "fail",
4795
5881
  "continue"
4796
5882
  ]);
4797
- ApprovalOnTimeoutSchema = z4.union([
5883
+ ApprovalOnTimeoutSchema = z22.union([
4798
5884
  TerminalOutcomeSchema,
4799
5885
  EscalationHopSchema,
4800
- z4.array(z4.union([
5886
+ z22.array(z22.union([
4801
5887
  TerminalOutcomeSchema,
4802
5888
  EscalationHopSchema
4803
5889
  ])).min(1).max(ESCALATION_MAX_HOPS + 1)
4804
5890
  ]);
5891
+ APPROVER_SPEC_SHAPES = [
5892
+ "'creator'",
5893
+ "'org-admins'",
5894
+ "{users:[userId, \u2026]}",
5895
+ "{role:roleName}",
5896
+ "{group:groupName}",
5897
+ "{governance:{policyId}}"
5898
+ ];
5899
+ APPROVER_WRITTEN_MAX = 120;
5900
+ USER_ID_SHAPED_RE = /^[^\s@]{1,128}$/;
5901
+ __name(describeApproverSpecRefusal, "describeApproverSpecRefusal");
5902
+ __name3(describeApproverSpecRefusal, "describeApproverSpecRefusal");
4805
5903
  BINDING_ROOTS = [
4806
5904
  "initData",
4807
5905
  "stepResults",
@@ -4850,6 +5948,15 @@ var init_dist2 = __esm({
4850
5948
  __name3(ofEntry, "ofEntry");
4851
5949
  __name(estimateGraph, "estimateGraph");
4852
5950
  __name3(estimateGraph, "estimateGraph");
5951
+ isRecord2 = /* @__PURE__ */ __name3((v) => !!v && typeof v === "object" && !Array.isArray(v), "isRecord");
5952
+ __name(singleStepsOf, "singleStepsOf");
5953
+ __name3(singleStepsOf, "singleStepsOf");
5954
+ __name(entriesOf, "entriesOf");
5955
+ __name3(entriesOf, "entriesOf");
5956
+ __name(inheritTargets, "inheritTargets");
5957
+ __name3(inheritTargets, "inheritTargets");
5958
+ __name(needsInheritedWorkspace, "needsInheritedWorkspace");
5959
+ __name3(needsInheritedWorkspace, "needsInheritedWorkspace");
4853
5960
  }
4854
5961
  });
4855
5962
 
@@ -4908,16 +6015,16 @@ function stepNodeOf(s) {
4908
6015
  });
4909
6016
  }
4910
6017
  function materializeEntry(entry, steps) {
4911
- const single = /* @__PURE__ */ __name((n) => {
4912
- if (n.type === "step" && steps[n.step.id]) return stepNodeOf(steps[n.step.id]);
4913
- if (n.type === "workflow" && n.workflowId === WORKFLOW_ARM_SUBRUN_ID && n.graph) return {
4914
- ...n,
6018
+ const single = /* @__PURE__ */ __name((n2) => {
6019
+ if (n2.type === "step" && steps[n2.step.id]) return stepNodeOf(steps[n2.step.id]);
6020
+ if (n2.type === "workflow" && n2.workflowId === WORKFLOW_ARM_SUBRUN_ID && n2.graph) return {
6021
+ ...n2,
4915
6022
  graph: [
4916
- n.graph[0],
4917
- single(n.graph[1])
6023
+ n2.graph[0],
6024
+ single(n2.graph[1])
4918
6025
  ]
4919
6026
  };
4920
- return n;
6027
+ return n2;
4921
6028
  }, "single");
4922
6029
  switch (entry.type) {
4923
6030
  case "step":
@@ -4948,7 +6055,12 @@ function materializeEntry(entry, steps) {
4948
6055
  }
4949
6056
  }
4950
6057
  function graphHasHitl(graph, steps) {
4951
- if (graph.some((e) => e.type === "approval" || e.type === "waitForSignal")) return true;
6058
+ for (const e of graph) {
6059
+ if (isHitlEntry(e)) return true;
6060
+ if (e.type === "parallel" && e.steps.some(isHitlEntry)) return true;
6061
+ if (e.type === "conditional" && (e.steps.some(isHitlEntry) || isHitlEntry(e.otherwise))) return true;
6062
+ if ((e.type === "foreach" || e.type === "loop") && isHitlEntry(e.step)) return true;
6063
+ }
4952
6064
  return Object.values(steps).some((s) => s.suspendSchema !== void 0);
4953
6065
  }
4954
6066
  function createWorkflow(cfg) {
@@ -4962,8 +6074,8 @@ function createWorkflow(cfg) {
4962
6074
  if (v.roles.length > 20 || (v.users?.length ?? 0) > 50) throw new LuaWorkflowBuildError("cap-exceeded", "outputVisibility allows \u2264 20 roles and \u2264 50 users");
4963
6075
  }
4964
6076
  if (cfg.backfillOnEnable?.maxOccurrences !== void 0) {
4965
- const n = cfg.backfillOnEnable.maxOccurrences;
4966
- if (!Number.isInteger(n) || n < 1 || n > 200) throw new LuaWorkflowBuildError("invalid-envelope", "backfillOnEnable.maxOccurrences must be an integer in 1..200 (backfill-max-occurrences-out-of-range; the org maxBatchItems twin is checked at publish/R56)");
6077
+ const n2 = cfg.backfillOnEnable.maxOccurrences;
6078
+ if (!Number.isInteger(n2) || n2 < 1 || n2 > 200) throw new LuaWorkflowBuildError("invalid-envelope", "backfillOnEnable.maxOccurrences must be an integer in 1..200 (backfill-max-occurrences-out-of-range; the org maxBatchItems twin is checked at publish/R56)");
4967
6079
  }
4968
6080
  const keys = /* @__PURE__ */ new Set();
4969
6081
  envRefKeys(cfg.schedule, keys);
@@ -4982,10 +6094,11 @@ function defineWorkflow(cfg, build) {
4982
6094
  if (!(wf instanceof LuaWorkflow)) throw new LuaWorkflowBuildError("invalid-envelope", "defineWorkflow: the build callback must return `wf\u2026.commit()`");
4983
6095
  return wf;
4984
6096
  }
4985
- var init2, state2, lit2, eq2, ne2, gt2, gte2, lt2, lte2, inSet2, notIn2, exists2, notExists2, truthy2, falsy2, and2, or2, not2, fromInit2, fromStep2, value2, template2, fromRequest2, rows2, fromKnowledge2, LuaWorkflowBuildError, STEP_ID_RE, WORKFLOW_NAME_RE, WORKFLOW_MAX_PARALLEL_ARMS, WORKFLOW_MAX_FOREACH_CONCURRENCY, WORKFLOW_MAX_FOREACH_ITEMS, WORKFLOW_WORKER_MAX_TIMEOUT_SECONDS, WORKFLOW_JOB_SEGMENT_MAX_SECONDS, WORKFLOW_JOB_MAX_TIMEOUT_SECONDS, WORKFLOW_LOOP_INTERVAL_MAX_SECONDS, WORKFLOW_FOREACH_RATE_MAX_PER_SECOND, WORKFLOW_SPECIALIST_ROLE_MAX_INSTRUCTIONS, WORKFLOW_DEFAULT_MAX_DURATION_SECONDS, WORKFLOW_HITL_MAX_DURATION_SECONDS, SECRET_KEY_RE, isZod, defined, templateText, assertNoClosure, assertPredicate, assertRetry, assertTimeout, envRefKeys, refToDescriptor, __workflowCommitHook, LuaWorkflow, WorkflowBuilderImpl, EDITABLE_PATH_RE2;
6097
+ var init2, state2, lit2, eq2, ne2, gt2, gte2, lt2, lte2, inSet2, notIn2, exists2, notExists2, truthy2, falsy2, and2, or2, not2, fromInit2, fromStep2, value2, template2, fromRequest2, rows2, fromKnowledge2, LuaWorkflowBuildError, STEP_ID_RE, WORKFLOW_NAME_RE, WORKFLOW_MAX_PARALLEL_ARMS, WORKFLOW_MAX_FOREACH_CONCURRENCY, WORKFLOW_MAX_FOREACH_ITEMS, WORKFLOW_WORKER_MAX_TIMEOUT_SECONDS, WORKFLOW_JOB_SEGMENT_MAX_SECONDS, WORKFLOW_JOB_MAX_TIMEOUT_SECONDS, WORKFLOW_LOOP_INTERVAL_MAX_SECONDS, WORKFLOW_FOREACH_RATE_MAX_PER_SECOND, WORKFLOW_SPECIALIST_ROLE_MAX_INSTRUCTIONS, WORKFLOW_DEFAULT_MAX_DURATION_SECONDS, WORKFLOW_HITL_MAX_DURATION_SECONDS, SECRET_KEY_RE, isZod, defined, templateText, assertNoClosure, assertPredicate, assertRetry, assertTimeout, envRefKeys, refToDescriptor, __workflowCommitHook, LuaWorkflow, isHitlEntry, WorkflowBuilderImpl, EDITABLE_PATH_RE2;
4986
6098
  var init_workflow = __esm({
4987
6099
  "src/types/workflow.ts"() {
4988
6100
  "use strict";
6101
+ init_dist();
4989
6102
  init_dist2();
4990
6103
  __name(createStep, "createStep");
4991
6104
  __name(step2, "step");
@@ -5068,7 +6181,7 @@ var init_workflow = __esm({
5068
6181
  }, "assertPredicate");
5069
6182
  assertRetry = /* @__PURE__ */ __name((r, id) => {
5070
6183
  if (!r) return;
5071
- if (r.backoff !== void 0 && r.backoff !== "fixed" && r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.backoff must be 'fixed' | 'exponential'`);
6184
+ 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(" | ")}`);
5072
6185
  if (r.maxBackoffSeconds !== void 0) {
5073
6186
  if (r.backoff !== "exponential") throw new LuaWorkflowBuildError("backoff-invalid", `"${id}": retry.maxBackoffSeconds is only meaningful with backoff:'exponential'`);
5074
6187
  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`);
@@ -5154,7 +6267,7 @@ var init_workflow = __esm({
5154
6267
  getEnvTemplateKeys() {
5155
6268
  return this.built.envTemplateKeys;
5156
6269
  }
5157
- /** `.workflow(id, ref)` targets by name — `ManifestWorkflow.workflowRefs`. */
6270
+ /** `.workflow(id, ref)` targets by name — `ManifestWorkflow.workflowRefs`; `workspace:'inherit'` marks the inherit children the compiler defers `workspace-not-declared` for. */
5158
6271
  getNestedWorkflowRefs() {
5159
6272
  return this.built.nestedRefs;
5160
6273
  }
@@ -5184,11 +6297,13 @@ var init_workflow = __esm({
5184
6297
  };
5185
6298
  if (cfg.concurrencyPolicy !== void 0) envelope.concurrencyPolicy = cfg.concurrencyPolicy;
5186
6299
  if (cfg.workspace !== void 0) envelope.workspace = cfg.workspace;
6300
+ if (cfg.connections !== void 0) envelope.connections = cfg.connections;
5187
6301
  return withDefaultsFilled(envelope);
5188
6302
  }
5189
6303
  };
5190
6304
  __name(stepNodeOf, "stepNodeOf");
5191
6305
  __name(materializeEntry, "materializeEntry");
6306
+ isHitlEntry = /* @__PURE__ */ __name((n2) => isWorkflowHitlEntryType(n2?.type), "isHitlEntry");
5192
6307
  __name(graphHasHitl, "graphHasHitl");
5193
6308
  WorkflowBuilderImpl = class WorkflowBuilderImpl2 {
5194
6309
  static {
@@ -5219,7 +6334,6 @@ var init_workflow = __esm({
5219
6334
  if (this.steps[s.id] && this.steps[s.id] !== s) throw new LuaWorkflowBuildError("duplicate-step-id", `step id "${s.id}" is declared twice`);
5220
6335
  const tier = s.tier ?? (s.workspace ? "job" : void 0);
5221
6336
  if (s.workspace && s.tier !== void 0 && s.tier !== "job") throw new LuaWorkflowBuildError("workspace-requires-job-tier", `"${s.id}": a step mounting a workspace must be tier:'job'`);
5222
- if (s.workspace && !this.config.workspace) throw new LuaWorkflowBuildError("workspace-not-declared", `"${s.id}" mounts a workspace but createWorkflow declares none`);
5223
6337
  if (s.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${s.id}": jobTools require tier:'job' (job-tools-require-job-tier)`);
5224
6338
  assertTimeout({
5225
6339
  id: s.id,
@@ -5242,7 +6356,6 @@ var init_workflow = __esm({
5242
6356
  const [mapping, target] = arm;
5243
6357
  if (target === void 0 || arm.length < 2) throw new LuaWorkflowBuildError("container-arm-empty", `${where}: a bare mapping arm has nothing to run`);
5244
6358
  if (!mapping || typeof mapping !== "object" || Array.isArray(mapping)) throw new LuaWorkflowBuildError("mapping-placement", `${where}: the arm head must be a map config object`);
5245
- if (mapping.type === "approval" || target.type === "approval") throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
5246
6359
  assertNoClosure(mapping, `${where} arm map`);
5247
6360
  this.recordEnvRefs(mapping);
5248
6361
  const inner = this.armRef(target, where);
@@ -5260,8 +6373,9 @@ var init_workflow = __esm({
5260
6373
  if (typeof arm === "string") return {
5261
6374
  ref: arm
5262
6375
  };
5263
- if (arm && typeof arm === "object" && arm.type === "approval") {
5264
- throw new LuaWorkflowBuildError("approval-inside-container", `${where}: approval / waitForSignal are top-level only in v1`);
6376
+ if (arm && typeof arm === "object" && isHitlEntry(arm)) {
6377
+ const id = arm.id;
6378
+ 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`);
5265
6379
  }
5266
6380
  return {
5267
6381
  node: this.registerStep(arm, where)
@@ -5460,12 +6574,15 @@ var init_workflow = __esm({
5460
6574
  }
5461
6575
  const tier = opts.tier ?? (opts.workspace ? "job" : void 0);
5462
6576
  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'`);
5463
- if (opts.workspace && !this.config.workspace) throw new LuaWorkflowBuildError("workspace-not-declared", `"${id}" mounts a workspace but createWorkflow declares none`);
5464
6577
  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`);
5465
6578
  if (opts.toolScope?.jobTools && tier !== "job") throw new LuaWorkflowBuildError("cap-exceeded", `"${id}": toolScope.jobTools require tier:'job' (job-tools-require-job-tier)`);
5466
- if (opts.maxTurns !== void 0) {
5467
- if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": maxTurns is only legal on a tier:'job' agent step`);
5468
- 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`);
6579
+ for (const m of WORKFLOW_JOB_RANGE_MEMBERS) {
6580
+ if (opts[m] === void 0) continue;
6581
+ if (tier !== "job") throw new LuaWorkflowBuildError("max-turns-requires-job-tier", `"${id}": ${m} is only legal on a tier:'job' agent step`);
6582
+ if (!isWithinWorkflowJobRange(m, opts[m])) {
6583
+ const { min, max } = WORKFLOW_JOB_RANGES[m];
6584
+ throw new LuaWorkflowBuildError("max-turns-invalid", `"${id}": ${m} must be an integer ${min}..${max}`);
6585
+ }
5469
6586
  }
5470
6587
  assertTimeout({
5471
6588
  id,
@@ -5493,7 +6610,9 @@ var init_workflow = __esm({
5493
6610
  workspace: opts.workspace,
5494
6611
  jobResources: opts.jobResources,
5495
6612
  harness: opts.harness,
5496
- maxTurns: opts.maxTurns
6613
+ maxTurns: opts.maxTurns,
6614
+ maxMessages: opts.maxMessages,
6615
+ maxInputTokens: opts.maxInputTokens
5497
6616
  });
5498
6617
  return this.push({
5499
6618
  kind: "declare",
@@ -5615,8 +6734,8 @@ var init_workflow = __esm({
5615
6734
  itemTimeout: opts.itemTimeout
5616
6735
  });
5617
6736
  return this.push({
5618
- kind: "entry",
5619
- entry: node
6737
+ kind: "declare",
6738
+ node
5620
6739
  });
5621
6740
  }
5622
6741
  waitForSignal(id, opts) {
@@ -5634,12 +6753,13 @@ var init_workflow = __esm({
5634
6753
  acceptedSources: opts.acceptedSources
5635
6754
  });
5636
6755
  return this.push({
5637
- kind: "entry",
5638
- entry: node
6756
+ kind: "declare",
6757
+ node
5639
6758
  });
5640
6759
  }
5641
6760
  workflow(id, ref, input, opts) {
5642
6761
  this.assertId(id, "workflow()");
6762
+ assertRetry(opts?.retry, id);
5643
6763
  const name = typeof ref === "string" ? ref : ref instanceof LuaWorkflow ? ref.getName() : void 0;
5644
6764
  if (!name) throw new LuaWorkflowBuildError("invalid-envelope", `workflow("${id}") needs a LuaWorkflow or a workflow name`);
5645
6765
  if (opts?.workspace === "inherit") {
@@ -5648,7 +6768,11 @@ var init_workflow = __esm({
5648
6768
  }
5649
6769
  assertNoClosure(input, `workflow("${id}").input`);
5650
6770
  this.recordEnvRefs(input);
5651
- this.nestedRefs.push({
6771
+ this.nestedRefs.push(opts?.workspace === "inherit" ? {
6772
+ id,
6773
+ name,
6774
+ workspace: "inherit"
6775
+ } : {
5652
6776
  id,
5653
6777
  name
5654
6778
  });
@@ -5657,11 +6781,12 @@ var init_workflow = __esm({
5657
6781
  id,
5658
6782
  workflowId: name,
5659
6783
  input,
5660
- workspace: opts?.workspace
6784
+ workspace: opts?.workspace,
6785
+ retry: opts?.retry
5661
6786
  });
5662
6787
  return this.push({
5663
- kind: "entry",
5664
- entry: node
6788
+ kind: "declare",
6789
+ node
5665
6790
  });
5666
6791
  }
5667
6792
  commit() {
@@ -5671,7 +6796,7 @@ var init_workflow = __esm({
5671
6796
  const { graph, issues } = resolvePlacements(this.calls);
5672
6797
  const fatal = issues[0];
5673
6798
  if (fatal) {
5674
- const hint = fatal.code === "unknown-step-ref" ? "a string StepRef must name an entry declared by agentStep/specialistStep/toolStep/map(\u2026, { id }) somewhere in the chain \u2014 before OR after the reference" : void 0;
6799
+ 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;
5675
6800
  throw new LuaWorkflowBuildError(fatal.code, fatal.message, hint);
5676
6801
  }
5677
6802
  if (graph.length === 0) throw new LuaWorkflowBuildError("empty-graph", `workflow "${this.config.name}" has no entries`);
@@ -5684,7 +6809,8 @@ var init_workflow = __esm({
5684
6809
  if (graphHasHitl(graph, this.steps) && this.config.budget?.maxDurationSeconds === void 0) {
5685
6810
  this.warnings.push({
5686
6811
  code: "hitl-duration-defaulted",
5687
- message: `budget.maxDurationSeconds defaulted to ${WORKFLOW_HITL_MAX_DURATION_SECONDS} s (30 d) because the graph contains an approval / waitForSignal / suspend-capable step \u2014 set it explicitly to silence`
6812
+ // LUA-668: names the workflow two HITL workflows in one project printed two identical lines that read as a duplicate.
6813
+ message: `workflow "${this.config.name}": budget.maxDurationSeconds defaulted to ${WORKFLOW_HITL_MAX_DURATION_SECONDS} s (30 d) because the graph contains an approval / waitForSignal / suspend-capable step \u2014 set it explicitly to silence`
5688
6814
  });
5689
6815
  }
5690
6816
  this.checkScheduleInput();
@@ -6068,7 +7194,7 @@ var init_firebase_session_store = __esm({
6068
7194
  }), "currentFirebaseSessionEnvironment");
6069
7195
  __name(environmentKey, "environmentKey");
6070
7196
  __name(isMissing, "isMissing");
6071
- wait = /* @__PURE__ */ __name((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)), "wait");
7197
+ wait = /* @__PURE__ */ __name((milliseconds) => new Promise((resolve3) => setTimeout(resolve3, milliseconds)), "wait");
6072
7198
  FirebaseSessionStore = class {
6073
7199
  static {
6074
7200
  __name(this, "FirebaseSessionStore");
@@ -6336,13 +7462,13 @@ async function* parseSseStream(body, signal) {
6336
7462
  buffer += decoder.decode(value3, {
6337
7463
  stream: true
6338
7464
  });
6339
- let sep = buffer.search(/\r?\n\r?\n/);
6340
- while (sep !== -1) {
6341
- const block = buffer.slice(0, sep);
6342
- buffer = buffer.slice(sep).replace(/^\r?\n\r?\n/, "");
7465
+ let sep4 = buffer.search(/\r?\n\r?\n/);
7466
+ while (sep4 !== -1) {
7467
+ const block = buffer.slice(0, sep4);
7468
+ buffer = buffer.slice(sep4).replace(/^\r?\n\r?\n/, "");
6343
7469
  const frame = flush(block);
6344
7470
  if (frame) yield frame;
6345
- sep = buffer.search(/\r?\n\r?\n/);
7471
+ sep4 = buffer.search(/\r?\n\r?\n/);
6346
7472
  }
6347
7473
  }
6348
7474
  if (buffer.trim()) {
@@ -6561,7 +7687,7 @@ Check that your Lua login has access to this agent or organization.`);
6561
7687
  if (attempt < maxRetries) {
6562
7688
  const serverDelay = Number(lastResult?.error?.retryAfterSeconds ?? 0) * 1e3;
6563
7689
  const backoff = Math.max(this.calculateBackoff(attempt), serverDelay);
6564
- await new Promise((resolve) => setTimeout(resolve, backoff));
7690
+ await new Promise((resolve3) => setTimeout(resolve3, backoff));
6565
7691
  }
6566
7692
  }
6567
7693
  return lastResult;
@@ -6610,7 +7736,7 @@ Check that your Lua login has access to this agent or organization.`);
6610
7736
  async httpPostCoreDrainRetry(url, data, headers) {
6611
7737
  const first = await this.httpPostOnce(url, data, headers);
6612
7738
  if (!isCoreDrainApiError(first.error)) return first;
6613
- await new Promise((resolve) => setTimeout(resolve, coreDrainApiRetryDelayMs(first.error)));
7739
+ await new Promise((resolve3) => setTimeout(resolve3, coreDrainApiRetryDelayMs(first.error)));
6614
7740
  return this.httpPostOnce(url, data, headers);
6615
7741
  }
6616
7742
  /**
@@ -6984,11 +8110,560 @@ var init_artifact_loader = __esm({
6984
8110
  }
6985
8111
  });
6986
8112
 
8113
+ // ../shared-source-sync/dist/index.mjs
8114
+ import { createHash as createHash4 } from "crypto";
8115
+ import { extname } from "path";
8116
+ import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
8117
+ import { join as join4, sep } from "path";
8118
+ import { gunzipSync, gzipSync } from "zlib";
8119
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync22, statSync as statSync2, writeFileSync } from "fs";
8120
+ import { dirname as dirname2, join as join22, resolve, sep as sep2 } from "path";
8121
+ import { posix } from "path";
8122
+ import { mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync32, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
8123
+ import { dirname as dirname22, join as join32, resolve as resolve2, sep as sep3 } from "path";
8124
+ import { gunzipSync as gunzipSync2, gzipSync as gzipSync2 } from "zlib";
8125
+ function hashContentTruncated(content) {
8126
+ return createHash4("sha256").update(content).digest("hex").slice(0, FILE_HASH_LENGTH);
8127
+ }
8128
+ function sha256Hex(content) {
8129
+ return createHash4("sha256").update(content).digest("hex");
8130
+ }
8131
+ function matchesFileHash(hash, plaintext) {
8132
+ if (!/^(?:[a-f0-9]{16}|[a-f0-9]{64})$/.test(hash)) return false;
8133
+ if (sha256Hex(plaintext).startsWith(hash)) return true;
8134
+ return sha256Hex(plaintext.toString("utf-8")).startsWith(hash);
8135
+ }
8136
+ function combineFileHashes(files) {
8137
+ const sorted = [
8138
+ ...files
8139
+ ].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
8140
+ return sha256Hex(sorted.map((f) => f.hash).join("|"));
8141
+ }
8142
+ function shouldSkipDirectory(name) {
8143
+ return SKIP_DIRECTORIES.has(name);
8144
+ }
8145
+ function shouldSkipFile(filePathOrName) {
8146
+ return filePathOrName.includes("node_modules") || filePathOrName.includes(".test.") || filePathOrName.includes(".spec.") || filePathOrName.includes("__tests__") || filePathOrName.endsWith(".d.ts") || filePathOrName.endsWith(".map") || filePathOrName.startsWith(".") || filePathOrName === "package-lock.json" || filePathOrName === "yarn.lock" || filePathOrName === "pnpm-lock.yaml";
8147
+ }
8148
+ function classifyFile(relPath) {
8149
+ return KIND_BY_EXT[extname(relPath)] ?? "other";
8150
+ }
8151
+ function walkWorkspace(rootDir, opts = {}) {
8152
+ const maxBytes = opts.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
8153
+ const refs = [];
8154
+ const contentByHash = /* @__PURE__ */ new Map();
8155
+ let totalSize = 0;
8156
+ const visit = /* @__PURE__ */ __name4((relPrefix) => {
8157
+ const absDir = relPrefix ? join4(rootDir, relPrefix) : rootDir;
8158
+ let entries;
8159
+ try {
8160
+ entries = readdirSync(absDir, {
8161
+ withFileTypes: true
8162
+ });
8163
+ } catch {
8164
+ return;
8165
+ }
8166
+ for (const entry of entries) {
8167
+ if (entry.isDirectory()) {
8168
+ if (shouldSkipDirectory(entry.name)) continue;
8169
+ if (shouldSkipFile(entry.name)) continue;
8170
+ visit(relPrefix ? `${relPrefix}${sep}${entry.name}` : entry.name);
8171
+ continue;
8172
+ }
8173
+ if (!entry.isFile()) continue;
8174
+ if (shouldSkipFile(entry.name)) continue;
8175
+ const rel = (relPrefix ? `${relPrefix}${sep}${entry.name}` : entry.name).split(sep).join("/");
8176
+ const abs = join4(rootDir, rel);
8177
+ let stats;
8178
+ try {
8179
+ stats = statSync(abs);
8180
+ } catch {
8181
+ continue;
8182
+ }
8183
+ if (stats.size > maxBytes) continue;
8184
+ let content;
8185
+ try {
8186
+ content = readFileSync3(abs);
8187
+ } catch {
8188
+ continue;
8189
+ }
8190
+ const hash = hashContentTruncated(content.toString("utf-8"));
8191
+ refs.push({
8192
+ relativePath: rel,
8193
+ hash,
8194
+ size: stats.size,
8195
+ type: classifyFile(rel)
8196
+ });
8197
+ if (!contentByHash.has(hash)) contentByHash.set(hash, content);
8198
+ totalSize += stats.size;
8199
+ }
8200
+ }, "visit");
8201
+ visit("");
8202
+ refs.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
8203
+ const projectHash = combineFileHashes(refs);
8204
+ return {
8205
+ files: refs,
8206
+ projectHash,
8207
+ totalSize,
8208
+ contentByHash
8209
+ };
8210
+ }
8211
+ async function uploadBlobs(opts) {
8212
+ const fetchFn = opts.fetch ?? fetch;
8213
+ const concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
8214
+ const hashes = Object.keys(opts.uploadUrls);
8215
+ for (let i = 0; i < hashes.length; i += concurrency) {
8216
+ const batch = hashes.slice(i, i + concurrency);
8217
+ await Promise.all(batch.map(async (hash) => {
8218
+ const buf = opts.contentByHash.get(hash);
8219
+ if (!buf) {
8220
+ throw new Error(`uploadBlobs: no content for hash ${hash.slice(0, 12)}\u2026`);
8221
+ }
8222
+ const compressed = gzipSync(buf);
8223
+ const res = await fetchFn(opts.uploadUrls[hash], {
8224
+ method: "PUT",
8225
+ body: compressed,
8226
+ headers: {
8227
+ "Content-Type": "application/octet-stream"
8228
+ }
8229
+ });
8230
+ if (!res.ok) {
8231
+ throw new Error(`S3 upload failed for ${hash.slice(0, 12)}\u2026: ${res.status} ${res.statusText}`);
8232
+ }
8233
+ }));
8234
+ }
8235
+ return hashes.length;
8236
+ }
8237
+ function decodeBlob(buf) {
8238
+ if (buf.length >= 2 && buf[0] === 31 && buf[1] === 139) {
8239
+ try {
8240
+ return gunzipSync(buf);
8241
+ } catch {
8242
+ return buf;
8243
+ }
8244
+ }
8245
+ return buf;
8246
+ }
8247
+ function verifyDownloaded(hash, plaintext) {
8248
+ if (!matchesFileHash(hash, plaintext)) {
8249
+ throw new Error(`Blob ${hash.slice(0, 12)}\u2026 failed integrity verification: its content does not hash to its key (refusing to restore it)`);
8250
+ }
8251
+ return plaintext;
8252
+ }
8253
+ async function downloadBlobs(opts) {
8254
+ const fetchFn = opts.fetch ?? fetch;
8255
+ const concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
8256
+ const out = /* @__PURE__ */ new Map();
8257
+ const entries = Object.entries(opts.urls);
8258
+ for (let i = 0; i < entries.length; i += concurrency) {
8259
+ const batch = entries.slice(i, i + concurrency);
8260
+ const downloaded = await Promise.all(batch.map(async ([hash, url]) => {
8261
+ const res = await fetchFn(url);
8262
+ if (!res.ok) {
8263
+ throw new Error(`S3 download failed for ${hash.slice(0, 12)}\u2026: ${res.status} ${res.statusText}`);
8264
+ }
8265
+ const ab = await res.arrayBuffer();
8266
+ const buf = Buffer.from(ab);
8267
+ return [
8268
+ hash,
8269
+ verifyDownloaded(hash, decodeBlob(buf))
8270
+ ];
8271
+ }));
8272
+ for (const [hash, buf] of downloaded) out.set(hash, buf);
8273
+ }
8274
+ return out;
8275
+ }
8276
+ function resolveBackupFileTarget(file, targetDir) {
8277
+ const base = resolve(targetDir);
8278
+ const sandboxed = file.external ? join22(base, ".lua", "external", file.relativePath) : join22(base, file.relativePath);
8279
+ const resolved = resolve(sandboxed);
8280
+ if (resolved !== base && !resolved.startsWith(base + sep2)) {
8281
+ throw new Error(`Backup entry escapes target directory: ${file.relativePath}`);
8282
+ }
8283
+ return resolved;
8284
+ }
8285
+ function restoreFromBlobs(manifest, blobs, targetDir, opts = {}) {
8286
+ let filesWritten = 0;
8287
+ let filesUnchanged = 0;
8288
+ let filesSkipped = 0;
8289
+ for (const file of manifest.files) {
8290
+ const content = blobs.get(file.hash);
8291
+ if (!content) {
8292
+ throw new Error(`Blob not found for hash: ${file.hash} (${file.relativePath})`);
8293
+ }
8294
+ const targetPath = resolveBackupFileTarget(file, targetDir);
8295
+ if (existsSync2(targetPath)) {
8296
+ const sameSize = statSync2(targetPath).size === content.length;
8297
+ if (sameSize && readFileSync22(targetPath).equals(content)) {
8298
+ filesUnchanged++;
8299
+ continue;
8300
+ }
8301
+ if (!opts.overwrite) {
8302
+ filesSkipped++;
8303
+ continue;
8304
+ }
8305
+ }
8306
+ mkdirSync(dirname2(targetPath), {
8307
+ recursive: true
8308
+ });
8309
+ writeFileSync(targetPath, content);
8310
+ filesWritten++;
8311
+ }
8312
+ return {
8313
+ filesWritten,
8314
+ filesUnchanged,
8315
+ filesSkipped
8316
+ };
8317
+ }
8318
+ function normalizeWorkspaceRelativePath(value3) {
8319
+ if (!value3 || value3.includes("\0")) return void 0;
8320
+ const withPortableSeparators = value3.replace(/\\/g, "/");
8321
+ if (withPortableSeparators.startsWith("/") || WINDOWS_ABSOLUTE_PATH.test(withPortableSeparators)) return void 0;
8322
+ const normalized = posix.normalize(withPortableSeparators);
8323
+ if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../")) return void 0;
8324
+ return normalized;
8325
+ }
8326
+ function isCredentialPersistencePath(value3) {
8327
+ const normalized = normalizeWorkspaceRelativePath(value3);
8328
+ return normalized !== void 0 && CREDENTIAL_PATHS.has(normalized.toLowerCase());
8329
+ }
8330
+ async function pushAgentBackup(opts) {
8331
+ const snapshot = walkWorkspace(opts.workspaceDir, opts.walkOptions);
8332
+ const client = new BackupHttpClient(opts.http);
8333
+ const uniqueHashes = [
8334
+ ...new Set(snapshot.files.map((f) => f.hash))
8335
+ ];
8336
+ let filesUploaded = 0;
8337
+ if (uniqueHashes.length > 0) {
8338
+ const checked = await client.checkBlobsExist(uniqueHashes);
8339
+ if (checked.missing.length > 0) {
8340
+ const urls = await client.getBlobUploadUrls(checked.missing);
8341
+ filesUploaded = await uploadBlobs({
8342
+ uploadUrls: urls.urls,
8343
+ contentByHash: snapshot.contentByHash,
8344
+ fetch: opts.http.fetch,
8345
+ concurrency: opts.concurrency
8346
+ });
8347
+ }
8348
+ }
8349
+ const metadata = await client.saveManifest({
8350
+ projectHash: snapshot.projectHash,
8351
+ files: snapshot.files,
8352
+ version: opts.version,
8353
+ orgId: opts.orgId,
8354
+ createdBy: opts.createdBy ?? "cli",
8355
+ triggeredBy: opts.triggeredBy
8356
+ });
8357
+ return {
8358
+ projectHash: snapshot.projectHash,
8359
+ fileCount: snapshot.files.length,
8360
+ filesUploaded,
8361
+ activeVersion: metadata.activeVersion,
8362
+ metadata,
8363
+ files: snapshot.files
8364
+ };
8365
+ }
8366
+ async function pullAgentBackup(opts) {
8367
+ const client = new BackupHttpClient(opts.http);
8368
+ const manifest = await client.getManifest();
8369
+ const uniqueHashes = [
8370
+ ...new Set(manifest.files.map((f) => f.hash))
8371
+ ];
8372
+ const blobs = uniqueHashes.length ? await (async () => {
8373
+ const urls = await client.getBlobUrls(uniqueHashes);
8374
+ return downloadBlobs({
8375
+ urls: urls.urls,
8376
+ fetch: opts.http.fetch,
8377
+ concurrency: opts.concurrency
8378
+ });
8379
+ })() : /* @__PURE__ */ new Map();
8380
+ const result = restoreFromBlobs(manifest, blobs, opts.targetDir, opts.restore);
8381
+ return {
8382
+ ...result,
8383
+ manifest
8384
+ };
8385
+ }
8386
+ function encodeWorkspaceArchive(workspaceDir) {
8387
+ const files = {};
8388
+ try {
8389
+ walk2(workspaceDir, "", files);
8390
+ } catch {
8391
+ return null;
8392
+ }
8393
+ if (Object.keys(files).length === 0) return null;
8394
+ const gz = gzipSync2(Buffer.from(JSON.stringify(files), "utf8"));
8395
+ return gz.toString("base64");
8396
+ }
8397
+ function decodeWorkspaceArchive(archive, schemaVersion) {
8398
+ if (schemaVersion !== void 0 && schemaVersion !== ARCHIVE_SCHEMA_VERSION) {
8399
+ throw new Error(`Unsupported archive schema version: ${schemaVersion} (expected ${ARCHIVE_SCHEMA_VERSION})`);
8400
+ }
8401
+ const raw = gunzipSync2(Buffer.from(archive, "base64")).toString("utf8");
8402
+ const parsed = JSON.parse(raw);
8403
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
8404
+ throw new Error("Decoded archive is not a JSON object map");
8405
+ }
8406
+ return parsed;
8407
+ }
8408
+ function writeArchiveToWorkspace(workspaceDir, files) {
8409
+ const writes = [];
8410
+ for (const [relPath, content] of Object.entries(files)) {
8411
+ const normalized = normalizeWorkspaceRelativePath(relPath);
8412
+ if (!normalized) throw new Error(`Refusing to write invalid archived path: ${relPath}`);
8413
+ if (isCredentialPersistencePath(normalized)) {
8414
+ throw new Error(`Refusing to restore credential file: ${normalized}`);
8415
+ }
8416
+ writes.push({
8417
+ path: resolve2(workspaceDir, normalized),
8418
+ content
8419
+ });
8420
+ }
8421
+ for (const write of writes) {
8422
+ mkdirSync2(dirname22(write.path), {
8423
+ recursive: true
8424
+ });
8425
+ writeFileSync2(write.path, write.content, "utf8");
8426
+ }
8427
+ }
8428
+ function walk2(root, prefix, out) {
8429
+ const dir = prefix ? join32(root, prefix) : root;
8430
+ const entries = readdirSync2(dir, {
8431
+ withFileTypes: true
8432
+ });
8433
+ for (const entry of entries) {
8434
+ if (entry.isDirectory()) {
8435
+ if (shouldSkipDirectory(entry.name) || ARCHIVE_ONLY_SKIP_DIRECTORIES.has(entry.name) || shouldSkipFile(entry.name)) continue;
8436
+ walk2(root, prefix ? `${prefix}${sep3}${entry.name}` : entry.name, out);
8437
+ continue;
8438
+ }
8439
+ if (!entry.isFile()) continue;
8440
+ if (shouldSkipFile(entry.name)) continue;
8441
+ const relPath = (prefix ? `${prefix}${sep3}${entry.name}` : entry.name).split(sep3).join("/");
8442
+ const abs = join32(dir, entry.name);
8443
+ if (statSync3(abs).size > DEFAULT_MAX_FILE_BYTES) continue;
8444
+ try {
8445
+ out[relPath] = readFileSync32(abs, "utf8");
8446
+ } catch {
8447
+ }
8448
+ }
8449
+ }
8450
+ var __defProp4, __name4, FILE_HASH_LENGTH, SKIP_DIRECTORIES, ARCHIVE_ONLY_SKIP_DIRECTORIES, DEFAULT_MAX_FILE_BYTES, KIND_BY_EXT, CHECK_BLOBS_MAX_HASHES, BackupHttpError, BackupHttpClient, DEFAULT_CONCURRENCY, CREDENTIAL_PATHS, WINDOWS_ABSOLUTE_PATH, ARCHIVE_SCHEMA_VERSION;
8451
+ var init_dist3 = __esm({
8452
+ "../shared-source-sync/dist/index.mjs"() {
8453
+ "use strict";
8454
+ __defProp4 = Object.defineProperty;
8455
+ __name4 = /* @__PURE__ */ __name((target, value3) => __defProp4(target, "name", { value: value3, configurable: true }), "__name");
8456
+ FILE_HASH_LENGTH = 16;
8457
+ __name(hashContentTruncated, "hashContentTruncated");
8458
+ __name4(hashContentTruncated, "hashContentTruncated");
8459
+ __name(sha256Hex, "sha256Hex");
8460
+ __name4(sha256Hex, "sha256Hex");
8461
+ __name(matchesFileHash, "matchesFileHash");
8462
+ __name4(matchesFileHash, "matchesFileHash");
8463
+ __name(combineFileHashes, "combineFileHashes");
8464
+ __name4(combineFileHashes, "combineFileHashes");
8465
+ SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
8466
+ "node_modules",
8467
+ "dist",
8468
+ "dist-v2",
8469
+ ".git",
8470
+ ".lua",
8471
+ ".temp",
8472
+ "coverage",
8473
+ ".next",
8474
+ ".turbo"
8475
+ ]);
8476
+ ARCHIVE_ONLY_SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
8477
+ "build"
8478
+ ]);
8479
+ DEFAULT_MAX_FILE_BYTES = 256 * 1024;
8480
+ __name(shouldSkipDirectory, "shouldSkipDirectory");
8481
+ __name4(shouldSkipDirectory, "shouldSkipDirectory");
8482
+ __name(shouldSkipFile, "shouldSkipFile");
8483
+ __name4(shouldSkipFile, "shouldSkipFile");
8484
+ KIND_BY_EXT = {
8485
+ ".ts": "source",
8486
+ ".tsx": "source",
8487
+ ".js": "source",
8488
+ ".jsx": "source",
8489
+ ".yaml": "config",
8490
+ ".yml": "config",
8491
+ ".json": "config",
8492
+ ".toml": "config"
8493
+ };
8494
+ __name(classifyFile, "classifyFile");
8495
+ __name4(classifyFile, "classifyFile");
8496
+ __name(walkWorkspace, "walkWorkspace");
8497
+ __name4(walkWorkspace, "walkWorkspace");
8498
+ CHECK_BLOBS_MAX_HASHES = 500;
8499
+ BackupHttpError = class extends Error {
8500
+ static {
8501
+ __name(this, "BackupHttpError");
8502
+ }
8503
+ static {
8504
+ __name4(this, "BackupHttpError");
8505
+ }
8506
+ status;
8507
+ endpoint;
8508
+ body;
8509
+ constructor(message, status, endpoint, body) {
8510
+ super(message), this.status = status, this.endpoint = endpoint, this.body = body;
8511
+ this.name = "BackupHttpError";
8512
+ }
8513
+ };
8514
+ BackupHttpClient = class {
8515
+ static {
8516
+ __name(this, "BackupHttpClient");
8517
+ }
8518
+ static {
8519
+ __name4(this, "BackupHttpClient");
8520
+ }
8521
+ options;
8522
+ fetchFn;
8523
+ constructor(options) {
8524
+ this.options = options;
8525
+ this.fetchFn = options.fetch ?? fetch;
8526
+ }
8527
+ url(path3) {
8528
+ const base = this.options.baseUrl.replace(/\/$/, "");
8529
+ return `${base}/developer/agents/${encodeURIComponent(this.options.agentId)}${path3}`;
8530
+ }
8531
+ async json(method, path3, body) {
8532
+ const endpoint = this.url(path3);
8533
+ const res = await this.fetchFn(endpoint, {
8534
+ method,
8535
+ headers: {
8536
+ Authorization: this.options.authHeader,
8537
+ ...body !== void 0 ? {
8538
+ "Content-Type": "application/json"
8539
+ } : {},
8540
+ Accept: "application/json"
8541
+ },
8542
+ body: body !== void 0 ? JSON.stringify(body) : void 0
8543
+ });
8544
+ if (!res.ok) {
8545
+ let text2;
8546
+ try {
8547
+ text2 = await res.text();
8548
+ } catch {
8549
+ text2 = void 0;
8550
+ }
8551
+ throw new BackupHttpError(`Backup request failed: ${method} ${path3} \u2192 ${res.status} ${res.statusText}`, res.status, endpoint, text2);
8552
+ }
8553
+ const text = await res.text();
8554
+ if (!text) return void 0;
8555
+ return JSON.parse(text);
8556
+ }
8557
+ /**
8558
+ * `POST /backup/check-blobs` — returns existing vs missing partition.
8559
+ *
8560
+ * LUA-675: the server caps one request at `CHECK_BLOBS_MAX_HASHES`; a
8561
+ * larger set is sent in sequential chunks and the partitions merged, so a
8562
+ * big workspace still pushes and the client never fans out on its own.
8563
+ */
8564
+ async checkBlobsExist(hashes) {
8565
+ if (hashes.length <= CHECK_BLOBS_MAX_HASHES) {
8566
+ return this.json("POST", "/backup/check-blobs", {
8567
+ hashes
8568
+ });
8569
+ }
8570
+ const merged = {
8571
+ missing: [],
8572
+ existing: [],
8573
+ results: {}
8574
+ };
8575
+ let unverified;
8576
+ for (let i = 0; i < hashes.length; i += CHECK_BLOBS_MAX_HASHES) {
8577
+ const part = await this.json("POST", "/backup/check-blobs", {
8578
+ hashes: hashes.slice(i, i + CHECK_BLOBS_MAX_HASHES)
8579
+ });
8580
+ merged.missing.push(...part.missing);
8581
+ merged.existing.push(...part.existing);
8582
+ Object.assign(merged.results, part.results);
8583
+ if (part.unverified) unverified = [
8584
+ ...unverified ?? [],
8585
+ ...part.unverified
8586
+ ];
8587
+ }
8588
+ return unverified ? {
8589
+ ...merged,
8590
+ unverified
8591
+ } : merged;
8592
+ }
8593
+ /** `POST /backup/blob-upload-urls` — presigned S3 PUT URLs. */
8594
+ getBlobUploadUrls(hashes) {
8595
+ return this.json("POST", "/backup/blob-upload-urls", {
8596
+ hashes
8597
+ });
8598
+ }
8599
+ /** `POST /backup/blob-urls` — presigned S3 GET URLs (for restore). */
8600
+ getBlobUrls(hashes) {
8601
+ return this.json("POST", "/backup/blob-urls", {
8602
+ hashes
8603
+ });
8604
+ }
8605
+ /** `POST /backup/manifest` — final step in a push; returns server metadata. */
8606
+ saveManifest(data) {
8607
+ return this.json("POST", "/backup/manifest", data);
8608
+ }
8609
+ /** `GET /backup` — metadata only, no file list. */
8610
+ getMetadata() {
8611
+ return this.json("GET", "/backup");
8612
+ }
8613
+ /** `GET /backup/manifest` — metadata + file list. */
8614
+ getManifest() {
8615
+ return this.json("GET", "/backup/manifest");
8616
+ }
8617
+ /** `GET /backup/check/:hash` — fast freshness probe. */
8618
+ checkBackupExists(hash) {
8619
+ return this.json("GET", `/backup/check/${encodeURIComponent(hash)}`);
8620
+ }
8621
+ };
8622
+ DEFAULT_CONCURRENCY = 10;
8623
+ __name(uploadBlobs, "uploadBlobs");
8624
+ __name4(uploadBlobs, "uploadBlobs");
8625
+ __name(decodeBlob, "decodeBlob");
8626
+ __name4(decodeBlob, "decodeBlob");
8627
+ __name(verifyDownloaded, "verifyDownloaded");
8628
+ __name4(verifyDownloaded, "verifyDownloaded");
8629
+ __name(downloadBlobs, "downloadBlobs");
8630
+ __name4(downloadBlobs, "downloadBlobs");
8631
+ __name(resolveBackupFileTarget, "resolveBackupFileTarget");
8632
+ __name4(resolveBackupFileTarget, "resolveBackupFileTarget");
8633
+ __name(restoreFromBlobs, "restoreFromBlobs");
8634
+ __name4(restoreFromBlobs, "restoreFromBlobs");
8635
+ CREDENTIAL_PATHS = /* @__PURE__ */ new Set([
8636
+ ".env",
8637
+ ".lua/config.json",
8638
+ ".claude/settings.local.json"
8639
+ ]);
8640
+ WINDOWS_ABSOLUTE_PATH = /^[a-z]:\//i;
8641
+ __name(normalizeWorkspaceRelativePath, "normalizeWorkspaceRelativePath");
8642
+ __name4(normalizeWorkspaceRelativePath, "normalizeWorkspaceRelativePath");
8643
+ __name(isCredentialPersistencePath, "isCredentialPersistencePath");
8644
+ __name4(isCredentialPersistencePath, "isCredentialPersistencePath");
8645
+ __name(pushAgentBackup, "pushAgentBackup");
8646
+ __name4(pushAgentBackup, "pushAgentBackup");
8647
+ __name(pullAgentBackup, "pullAgentBackup");
8648
+ __name4(pullAgentBackup, "pullAgentBackup");
8649
+ ARCHIVE_SCHEMA_VERSION = 1;
8650
+ __name(encodeWorkspaceArchive, "encodeWorkspaceArchive");
8651
+ __name4(encodeWorkspaceArchive, "encodeWorkspaceArchive");
8652
+ __name(decodeWorkspaceArchive, "decodeWorkspaceArchive");
8653
+ __name4(decodeWorkspaceArchive, "decodeWorkspaceArchive");
8654
+ __name(writeArchiveToWorkspace, "writeArchiveToWorkspace");
8655
+ __name4(writeArchiveToWorkspace, "writeArchiveToWorkspace");
8656
+ __name(walk2, "walk");
8657
+ __name4(walk2, "walk");
8658
+ }
8659
+ });
8660
+
6987
8661
  // src/api/backup.api.service.ts
6988
8662
  var init_backup_api_service = __esm({
6989
8663
  "src/api/backup.api.service.ts"() {
6990
8664
  "use strict";
6991
8665
  init_http_client();
8666
+ init_dist3();
6992
8667
  }
6993
8668
  });
6994
8669
 
@@ -11215,7 +12890,7 @@ function createWorkflowsRuntime(getApi) {
11215
12890
  }
11216
12891
  };
11217
12892
  }
11218
- var WORKFLOW_START_MAX_WAIT_SECONDS, WORKFLOW_START_CLIENT_DEADLINE_SLACK_MS, WorkflowApiError, unwrap, assertBoundAgent, unavailable, WorkflowApi;
12893
+ var WORKFLOW_START_MAX_WAIT_SECONDS, WORKFLOW_START_CLIENT_DEADLINE_SLACK_MS, WorkflowApiError, unwrap, assertBoundAgent, pathId, unavailable, WorkflowApi;
11219
12894
  var init_workflow_api_service = __esm({
11220
12895
  "src/api/workflow.api.service.ts"() {
11221
12896
  "use strict";
@@ -11252,6 +12927,7 @@ var init_workflow_api_service = __esm({
11252
12927
  throw new WorkflowApiError("FORBIDDEN", `Workflows.${member}: goals are scoped to the bound agent ${api.agentId}`, 403);
11253
12928
  }
11254
12929
  }, "assertBoundAgent");
12930
+ pathId = /* @__PURE__ */ __name((id) => encodeURIComponent(id), "pathId");
11255
12931
  unavailable = /* @__PURE__ */ __name((member, route) => async () => {
11256
12932
  throw new WorkflowApiError("WORKFLOWS_API_UNAVAILABLE", `Workflows.${member} is not available in this runtime yet (${route} lands with a later wave)`, 501);
11257
12933
  }, "unavailable");
@@ -11377,7 +13053,7 @@ var init_workflow_api_service = __esm({
11377
13053
  /** R5 — one step of a run (`?attempt=n` selects from the attempt history). */
11378
13054
  async getRunStep(runId, stepId, options = {}) {
11379
13055
  const qs = options.attempt !== void 0 ? `?attempt=${options.attempt}` : "";
11380
- return this.httpGet(`${this.runs}/${runId}/steps/${stepId}${qs}`, await this.auth());
13056
+ return this.httpGet(`${this.runs}/${runId}/steps/${pathId(stepId)}${qs}`, await this.auth());
11381
13057
  }
11382
13058
  /** R6 — script-form journal page (404 `NOT_SCRIPT_RUN` for graph runs — IF-16). */
11383
13059
  async getRunJournal(runId, options = {}) {
@@ -11397,7 +13073,7 @@ var init_workflow_api_service = __esm({
11397
13073
  }
11398
13074
  /** R12 — resume a suspended step; the loser of a race gets `{ resumed:false, reason:'already_resumed' }`, never a 4xx. */
11399
13075
  async resumeRun(runId, stepId, data) {
11400
- return this.httpPost(`${this.runs}/${runId}/steps/${stepId}/resume`, data, await this.auth());
13076
+ return this.httpPost(`${this.runs}/${runId}/steps/${pathId(stepId)}/resume`, data, await this.auth());
11401
13077
  }
11402
13078
  /**
11403
13079
  * R36 — re-arm a parked step: a failed row past its retries, a gate-2 park, or a billing hold (the only exit
@@ -11405,15 +13081,15 @@ var init_workflow_api_service = __esm({
11405
13081
  * STEP_NOT_PARKED.
11406
13082
  */
11407
13083
  async retryStep(runId, stepId, data = {}) {
11408
- return this.httpPost(`${this.runs}/${runId}/steps/${stepId}/retry`, data, await this.auth());
13084
+ return this.httpPost(`${this.runs}/${runId}/steps/${pathId(stepId)}/retry`, data, await this.auth());
11409
13085
  }
11410
13086
  /** R13 — resolve an approval (human; `expectedFingerprint` guards against an edited payload — 409 `PAYLOAD_MISMATCH`). */
11411
13087
  async resolveApproval(runId, approvalId, data) {
11412
- return this.httpPost(`${this.runs}/${runId}/approvals/${approvalId}/resolve`, data, await this.auth());
13088
+ return this.httpPost(`${this.runs}/${runId}/approvals/${pathId(approvalId)}/resolve`, data, await this.auth());
11413
13089
  }
11414
13090
  /** R14 — deliver a named signal (`dedupeKey` ⇒ 200 `duplicate:true` on replay). */
11415
13091
  async signalRun(runId, name, data = {}) {
11416
- return this.httpPost(`${this.runs}/${runId}/signals/${encodeURIComponent(name)}`, data, await this.auth());
13092
+ return this.httpPost(`${this.runs}/${runId}/signals/${pathId(name)}`, data, await this.auth());
11417
13093
  }
11418
13094
  /** R31 — `DELETE …/runs/:runId` (`eraseRun`; human principal) → 202 `{ accepted, purgeId }`; 409 `RUN_NOT_TERMINAL { nextAction:'cancel' }`. */
11419
13095
  /** R50 — request an evidence-bundle export (202 `{exportId, status:'pending'}`; 409 `RUN_NOT_TERMINAL` / `EXPORT_IN_PROGRESS`). */
@@ -11470,6 +13146,23 @@ var init_workflow_api_service = __esm({
11470
13146
  async closeGoal(goalId, data = {}) {
11471
13147
  return this.httpPost(`${this.goals}/${encodeURIComponent(goalId)}/close`, data, await this.auth());
11472
13148
  }
13149
+ // ─── Schedules (R4-MF-2 list/get + R28 delete — `/workflows/:agentId/schedules`; LUA-627 stanza) ───
13150
+ /** Schedule tree (09 §9.5 — the write-only R27/R56/R28 family plus the R4-MF-2 read rows). */
13151
+ get schedules() {
13152
+ return `/workflows/${this.agentId}/schedules`;
13153
+ }
13154
+ /** R4-MF-2 — every `Job{kind:'workflow'}` of the agent with the PRO-726 strike fields (`goalId` marks a goal's cadence). */
13155
+ async listSchedules() {
13156
+ return this.httpGet(this.schedules, await this.auth());
13157
+ }
13158
+ /** R4-MF-2 — one schedule row; unknown, cross-agent and non-workflow-kind jobs all 404 `SCHEDULE_NOT_FOUND`. */
13159
+ async getSchedule(jobId) {
13160
+ return this.httpGet(`${this.schedules}/${encodeURIComponent(jobId)}`, await this.auth());
13161
+ }
13162
+ /** R28 — delete a schedule Job (404 `SCHEDULE_NOT_FOUND`). The CLI refuses a goal-owned job BEFORE this call (`goal_schedule`). */
13163
+ async deleteSchedule(jobId) {
13164
+ return this.httpDelete(`${this.schedules}/${encodeURIComponent(jobId)}`, await this.auth());
13165
+ }
11473
13166
  // ─── Events (R7 — SSE `watch`, WF-204) ───
11474
13167
  /**
11475
13168
  * R7 SSE — `GET …/runs/:runId/events` as a frame stream (`id:<seq>` / `event:<type>` /
@@ -11513,7 +13206,7 @@ var init_workflow_api_service = __esm({
11513
13206
  if (options.attempt !== void 0) query.append("attempt", String(options.attempt));
11514
13207
  if (options.tail !== void 0) query.append("tail", String(options.tail));
11515
13208
  const qs = query.toString();
11516
- return this.httpGet(`${this.runs}/${runId}/steps/${stepId}/job${qs ? `?${qs}` : ""}`, await this.auth());
13209
+ return this.httpGet(`${this.runs}/${runId}/steps/${pathId(stepId)}/job${qs ? `?${qs}` : ""}`, await this.auth());
11517
13210
  }
11518
13211
  };
11519
13212
  }