@lunnoa/client 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -26,16 +26,21 @@ var LunnoaApiError = class extends Error {
26
26
  method;
27
27
  /** Path of the failed request (without the base URL). */
28
28
  path;
29
+ /** Present on definition-sync 409s when the server sends the current revision. */
30
+ currentRevision;
29
31
  constructor(args) {
30
32
  const detail = extractMessage(args.body);
33
+ const currentRevision = extractCurrentRevision(args.body);
34
+ const shown = [detail, currentRevision ? `currentRevision: ${currentRevision}` : void 0].filter(Boolean).join(" ");
31
35
  super(
32
- `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${detail ? ` (${detail})` : ""}`
36
+ `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${shown ? ` (${shown})` : ""}`
33
37
  );
34
38
  this.name = "LunnoaApiError";
35
39
  this.status = args.status;
36
40
  this.body = args.body;
37
41
  this.method = args.method;
38
42
  this.path = args.path;
43
+ this.currentRevision = currentRevision;
39
44
  }
40
45
  /** True for 401 responses (missing, revoked, or expired credential). */
41
46
  get isUnauthorized() {
@@ -54,6 +59,11 @@ var LunnoaApiError = class extends Error {
54
59
  return this.status === 429;
55
60
  }
56
61
  };
62
+ function extractCurrentRevision(body) {
63
+ if (!body || typeof body !== "object") return void 0;
64
+ const revision = body.currentRevision;
65
+ return typeof revision === "string" ? revision : void 0;
66
+ }
57
67
  function extractMessage(body) {
58
68
  if (body && typeof body === "object") {
59
69
  const record = body;
@@ -530,6 +540,139 @@ var ActionsResource = class {
530
540
  }
531
541
  };
532
542
 
543
+ // src/define/files.ts
544
+ var STEP_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
545
+ function hasFileAgentSignals(record) {
546
+ if (record.instructions && typeof record.instructions === "object") return true;
547
+ if (record.settings && typeof record.settings === "object") return true;
548
+ if ("workflows" in record || "agents" in record || "knowledge" in record || "variables" in record || "objects" in record || "objectTypes" in record || "mcpServers" in record || "a2aAgents" in record) {
549
+ return true;
550
+ }
551
+ return Array.isArray(record.tools) && record.tools.some(
552
+ (tool) => tool && typeof tool === "object" && !("kind" in tool) && !("use" in tool)
553
+ );
554
+ }
555
+ function isAgentFileInput(config) {
556
+ if (!config || typeof config !== "object") return false;
557
+ return hasFileAgentSignals(config);
558
+ }
559
+ function isAgentFileShape(config) {
560
+ if (!config || typeof config !== "object") return false;
561
+ return hasFileAgentSignals(config);
562
+ }
563
+ function acceptAgentFile(config) {
564
+ const slug = config.slug?.trim();
565
+ if (!slug) throw new Error("defineAgent requires slug");
566
+ const instructions = config.instructions;
567
+ const fileRef = instructions && typeof instructions === "object" && typeof instructions.file === "string" && instructions.file.trim().length > 0;
568
+ if (typeof instructions !== "string" && !fileRef) {
569
+ throw new Error("defineAgent requires instructions");
570
+ }
571
+ const copy = { ...config };
572
+ delete copy.$schema;
573
+ delete copy.kind;
574
+ return { ...copy, kind: "agent", slug, instructions };
575
+ }
576
+ function assertStepKeys(steps, seen) {
577
+ for (const step of steps ?? []) {
578
+ if (!step?.use?.id?.trim()) {
579
+ throw new Error("workflow step requires use.id");
580
+ }
581
+ if (step.key !== void 0) {
582
+ if (!STEP_KEY_PATTERN.test(step.key) || step.key === "trigger") {
583
+ throw new Error(
584
+ `Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "trigger".`
585
+ );
586
+ }
587
+ if (seen.has(step.key)) {
588
+ throw new Error(`Step key "${step.key}" is used twice.`);
589
+ }
590
+ seen.add(step.key);
591
+ }
592
+ for (const path of step.paths ?? []) {
593
+ assertStepKeys(path.steps ?? [], seen);
594
+ }
595
+ }
596
+ }
597
+ function acceptWorkflowStepsFile(config) {
598
+ const slug = config.slug?.trim();
599
+ if (!slug) throw new Error("defineWorkflow requires slug");
600
+ if (!config.steps?.length) {
601
+ throw new Error("defineWorkflow requires at least one step");
602
+ }
603
+ if (config.trigger && !config.trigger.id?.trim()) {
604
+ throw new Error("defineWorkflow trigger requires id");
605
+ }
606
+ assertStepKeys(config.steps, /* @__PURE__ */ new Set());
607
+ const copy = { ...config };
608
+ delete copy.$schema;
609
+ delete copy.kind;
610
+ return {
611
+ ...copy,
612
+ kind: "workflow",
613
+ format: "steps",
614
+ slug
615
+ };
616
+ }
617
+ function acceptWorkflowGraphFile(config) {
618
+ const slug = config.slug?.trim();
619
+ if (!slug) throw new Error("defineWorkflow requires slug");
620
+ if (!Array.isArray(config.nodes) || config.nodes.length === 0) {
621
+ throw new Error("defineWorkflow graph requires nodes");
622
+ }
623
+ const keys = /* @__PURE__ */ new Set();
624
+ let triggers = 0;
625
+ for (const node of config.nodes) {
626
+ if (!node?.key || typeof node.key !== "string") {
627
+ throw new Error("graph node requires key");
628
+ }
629
+ if (!STEP_KEY_PATTERN.test(node.key)) {
630
+ throw new Error(
631
+ `Node key "${node.key}" must start with a letter, use letters, digits, - or _, and not be empty.`
632
+ );
633
+ }
634
+ if (keys.has(node.key)) {
635
+ throw new Error(`Node key "${node.key}" is used twice.`);
636
+ }
637
+ keys.add(node.key);
638
+ if (node.nodeType === "trigger") triggers += 1;
639
+ }
640
+ if (triggers !== 1) {
641
+ throw new Error("defineWorkflow graph requires exactly one trigger");
642
+ }
643
+ for (const edge of config.edges ?? []) {
644
+ if (!keys.has(edge?.source) || !keys.has(edge?.target)) {
645
+ throw new Error(
646
+ `Edge from "${edge?.source ?? ""}" to "${edge?.target ?? ""}" references an unknown key.`
647
+ );
648
+ }
649
+ }
650
+ const copy = { ...config };
651
+ delete copy.$schema;
652
+ delete copy.kind;
653
+ return {
654
+ ...copy,
655
+ kind: "workflow",
656
+ format: "graph",
657
+ slug,
658
+ edges: config.edges ?? []
659
+ };
660
+ }
661
+ function isWorkflowGraphInput(config) {
662
+ return !!config && typeof config === "object" && config.format === "graph";
663
+ }
664
+ function isWorkflowStepsFile(config) {
665
+ if (!config || typeof config !== "object") return false;
666
+ const record = config;
667
+ if (record.format === "steps") return true;
668
+ if (record.trigger && typeof record.trigger === "object" && record.trigger.kind !== "trigger" && typeof record.trigger.id === "string") {
669
+ return true;
670
+ }
671
+ return Array.isArray(record.steps) && record.steps.some(
672
+ (step) => step && typeof step === "object" && step.use && step.use?.kind !== "action"
673
+ );
674
+ }
675
+
533
676
  // src/define/index.ts
534
677
  var DEFINE_KINDS = {
535
678
  connection: "connection",
@@ -540,6 +683,32 @@ var DEFINE_KINDS = {
540
683
  agent: "agent",
541
684
  entityType: "entityType"
542
685
  };
686
+ var PATH_CONDITIONS = [
687
+ "contains",
688
+ "does_not_contain",
689
+ "equals",
690
+ "does_not_equal",
691
+ "exists",
692
+ "does_not_exist",
693
+ "starts_with",
694
+ "ends_with",
695
+ "is_empty",
696
+ "is_not_empty",
697
+ "comes_before",
698
+ "comes_after",
699
+ "is_in_the_past",
700
+ "is_in_the_future",
701
+ "is_true",
702
+ "is_false",
703
+ "is_truthy",
704
+ "is_falsey",
705
+ "is_greater_than",
706
+ "is_less_than",
707
+ "is_greater_than_or_equal_to",
708
+ "is_less_than_or_equal_to",
709
+ "matches_regex",
710
+ "does_not_match_regex"
711
+ ];
543
712
  function defineConnection(config = {}) {
544
713
  return { kind: DEFINE_KINDS.connection, ...config };
545
714
  }
@@ -573,7 +742,7 @@ function defineTrigger(config) {
573
742
  }
574
743
  var MANUAL_TRIGGER_ID = "flow-control_trigger_manual";
575
744
  var TRIGGER_KEY = "trigger";
576
- var STEP_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
745
+ var STEP_KEY_PATTERN2 = /^[A-Za-z][A-Za-z0-9_-]*$/;
577
746
  var STEP_REF_PATTERN = /={{step:([^}]*)}}/g;
578
747
  function refString(key, path) {
579
748
  const parts = [key, ...path.map(String)];
@@ -601,32 +770,44 @@ function collectRefs(value, out) {
601
770
  Object.values(value).forEach((item) => collectRefs(item, out));
602
771
  }
603
772
  }
773
+ var CONDITIONAL_PATHS_ID = "flow-control_action_conditional-paths";
774
+ var APPROVAL_ID = "flow-control_action_request-approval";
775
+ var BRANCHING_ACTION_IDS = /* @__PURE__ */ new Set([
776
+ CONDITIONAL_PATHS_ID,
777
+ "flow-control_action_manually-decide-paths",
778
+ APPROVAL_ID
779
+ ]);
780
+ var PATH_CONDITION_SET = new Set(PATH_CONDITIONS);
781
+ var SINGLE_REF_PATTERN = /^={{step:[^}]*}}$/;
782
+ function collectKeys(steps, seen) {
783
+ for (const step of steps) {
784
+ if (step.key !== void 0) {
785
+ if (!STEP_KEY_PATTERN2.test(step.key) || step.key === TRIGGER_KEY) {
786
+ throw new Error(
787
+ `Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "${TRIGGER_KEY}".`
788
+ );
789
+ }
790
+ if (seen.has(step.key)) {
791
+ throw new Error(`Step key "${step.key}" is used twice.`);
792
+ }
793
+ seen.add(step.key);
794
+ }
795
+ for (const path of step.paths ?? []) collectKeys(path.steps ?? [], seen);
796
+ }
797
+ }
604
798
  function assertStepRefs(config) {
605
799
  const triggerId = config.trigger?.id.trim() ?? MANUAL_TRIGGER_ID;
606
800
  const triggerFields = triggerId === MANUAL_TRIGGER_ID ? new Set((config.trigger?.inputs ?? []).map((f) => f.id.trim())) : void 0;
607
801
  const allKeys = /* @__PURE__ */ new Set();
608
- for (const step of config.steps) {
609
- if (step.key === void 0) continue;
610
- if (!STEP_KEY_PATTERN.test(step.key) || step.key === TRIGGER_KEY) {
611
- throw new Error(
612
- `Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "${TRIGGER_KEY}".`
613
- );
614
- }
615
- if (allKeys.has(step.key)) {
616
- throw new Error(`Step key "${step.key}" is used twice.`);
617
- }
618
- allKeys.add(step.key);
619
- }
620
- const earlier = /* @__PURE__ */ new Set([TRIGGER_KEY]);
621
- config.steps.forEach((step, index) => {
802
+ collectKeys(config.steps, allKeys);
803
+ const checkRefs = (value, earlier, where) => {
622
804
  const refs = [];
623
- collectRefs([step.use?.input, step.input], refs);
624
- const where = `Step "${step.key ?? step.name ?? index + 1}"`;
805
+ collectRefs(value, refs);
625
806
  for (const [key = "", ...path] of refs) {
626
807
  const shown = `={{step:${[key, ...path].join(",")}}}`;
627
808
  if (!earlier.has(key)) {
628
809
  throw new Error(
629
- allKeys.has(key) ? `${where}: ${shown} refers to itself or a later step. Use the trigger or an earlier step.` : `${where}: ${shown} names unknown key "${key}". Use "trigger" or an earlier step's key.`
810
+ allKeys.has(key) ? `${where}: ${shown} refers to itself, a later step, or a step on another path. Use the trigger or an earlier step on the same path.` : `${where}: ${shown} names unknown key "${key}". Use "trigger" or an earlier step's key.`
630
811
  );
631
812
  }
632
813
  if (key === TRIGGER_KEY && triggerFields && !triggerFields.has(path[0] ?? "")) {
@@ -635,33 +816,93 @@ function assertStepRefs(config) {
635
816
  );
636
817
  }
637
818
  }
638
- if (step.key) earlier.add(step.key);
639
- });
819
+ };
820
+ const checkChain = (steps, visible) => {
821
+ const earlier = new Set(visible);
822
+ steps.forEach((step, index) => {
823
+ const where = `Step "${step.key ?? step.name ?? index + 1}"`;
824
+ checkRefs([step.use?.input, step.input], earlier, where);
825
+ if (step.key) earlier.add(step.key);
826
+ if (!step.paths?.length) return;
827
+ const actionId = step.use ? resolveActionId(step.use) : "";
828
+ if (!BRANCHING_ACTION_IDS.has(actionId)) {
829
+ throw new Error(`${where}: paths only work on ${[...BRANCHING_ACTION_IDS].join(", ")}.`);
830
+ }
831
+ if (index !== steps.length - 1) {
832
+ throw new Error(
833
+ `${where}: a step with paths must be the last step in its list. Put later steps inside each path.`
834
+ );
835
+ }
836
+ if (actionId === APPROVAL_ID && step.paths.length > 2) {
837
+ throw new Error(`${where}: Request Approval takes at most two paths (approved, then rejected).`);
838
+ }
839
+ const labels = /* @__PURE__ */ new Set();
840
+ for (const path of step.paths) {
841
+ const label = path?.label?.trim();
842
+ if (!label) throw new Error(`${where}: every path needs a label.`);
843
+ if (labels.has(label)) throw new Error(`${where}: path label "${label}" is used twice.`);
844
+ labels.add(label);
845
+ if (path.when !== void 0 && actionId !== CONDITIONAL_PATHS_ID) {
846
+ throw new Error(`${where}: "when" only works on Conditional Paths.`);
847
+ }
848
+ for (const condition of (path.when ?? []).flat()) {
849
+ if (!SINGLE_REF_PATTERN.test(condition?.field?.trim() ?? "")) {
850
+ throw new Error(
851
+ `${where}, path "${label}": a condition's field must be one ref.trigger(...) or ref.step(...).`
852
+ );
853
+ }
854
+ if (!PATH_CONDITION_SET.has(condition.condition)) {
855
+ throw new Error(
856
+ `${where}, path "${label}": condition "${condition.condition}" is not supported. Use one of: ${PATH_CONDITIONS.join(", ")}.`
857
+ );
858
+ }
859
+ }
860
+ checkRefs(path.when, earlier, `${where}, path "${label}"`);
861
+ if (!path.steps?.length) throw new Error(`${where}: path "${label}" needs at least one step.`);
862
+ checkChain(path.steps, earlier);
863
+ }
864
+ });
865
+ };
866
+ checkChain(config.steps, /* @__PURE__ */ new Set([TRIGGER_KEY]));
640
867
  }
641
868
  function defineWorkflow(config) {
642
- if (!config.slug?.trim()) {
869
+ const record = config;
870
+ if (isWorkflowGraphInput(config) || Array.isArray(record.nodes) && !Array.isArray(record.steps)) {
871
+ return acceptWorkflowGraphFile(config);
872
+ }
873
+ if (isWorkflowStepsFile(config)) {
874
+ return acceptWorkflowStepsFile(config);
875
+ }
876
+ const authored = { ...config };
877
+ delete authored.$schema;
878
+ if (!authored.slug?.trim()) {
643
879
  throw new Error("defineWorkflow requires slug");
644
880
  }
645
- if (!config.steps?.length) {
881
+ if (!authored.steps?.length) {
646
882
  throw new Error("defineWorkflow requires at least one step");
647
883
  }
648
- if (config.trigger) {
649
- assertTriggerConfig(config.trigger);
884
+ if (authored.trigger) {
885
+ assertTriggerConfig(authored.trigger);
650
886
  }
651
- assertStepRefs(config);
652
- return { kind: DEFINE_KINDS.workflow, ...config };
887
+ assertStepRefs(authored);
888
+ return { kind: DEFINE_KINDS.workflow, ...authored };
653
889
  }
654
890
  function defineAgent(config) {
655
- if (!config.slug?.trim()) {
891
+ if (isAgentFileInput(config)) {
892
+ return acceptAgentFile(config);
893
+ }
894
+ const authored = { ...config };
895
+ delete authored.$schema;
896
+ if (!authored.slug?.trim()) {
656
897
  throw new Error("defineAgent requires slug");
657
898
  }
658
- if (!config.model?.trim()) {
899
+ if (!authored.model?.trim()) {
659
900
  throw new Error("defineAgent requires model");
660
901
  }
661
- if (config.instructions == null) {
902
+ if (authored.instructions == null) {
662
903
  throw new Error("defineAgent requires instructions");
663
904
  }
664
- return { kind: DEFINE_KINDS.agent, ...config };
905
+ return { kind: DEFINE_KINDS.agent, ...authored };
665
906
  }
666
907
  function defineEntityType(config) {
667
908
  if (!config.slug?.trim()) {
@@ -776,10 +1017,24 @@ function unwrapTool(tool) {
776
1017
  }
777
1018
  return tool;
778
1019
  }
1020
+ function syncFields(options) {
1021
+ return {
1022
+ ...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
1023
+ ...options?.force ? { force: true } : {}
1024
+ };
1025
+ }
1026
+ function idsOf(links) {
1027
+ if (!links) return void 0;
1028
+ return links.map((link) => link.id);
1029
+ }
779
1030
  function agentDefinitionToUpsert(def, options) {
1031
+ if (isAgentFileShape(def)) {
1032
+ return fileAgentToUpsert(def, options);
1033
+ }
780
1034
  const tools = [];
781
1035
  const workflowIds = [...options?.workflowIds ?? []];
782
- for (const tool of def.tools ?? []) {
1036
+ const authored = def;
1037
+ for (const tool of authored.tools ?? []) {
783
1038
  const unwrapped = unwrapTool(tool);
784
1039
  if (unwrapped.kind === DEFINE_KINDS.workflow) {
785
1040
  continue;
@@ -795,17 +1050,65 @@ function agentDefinitionToUpsert(def, options) {
795
1050
  });
796
1051
  }
797
1052
  }
1053
+ return {
1054
+ slug: authored.slug,
1055
+ name: authored.name,
1056
+ description: authored.description,
1057
+ instructions: authored.instructions,
1058
+ model: authored.model,
1059
+ aiConnectionId: authored.aiConnection?.id,
1060
+ connectionIds: authored.connections?.map((c) => c.id).filter((id) => Boolean(id)),
1061
+ workflowIds: workflowIds.length ? workflowIds : void 0,
1062
+ tools,
1063
+ codeSource: options?.codeSource ?? "generated",
1064
+ ...syncFields(options)
1065
+ };
1066
+ }
1067
+ function fileAgentToUpsert(def, options) {
1068
+ if (typeof def.instructions !== "string") {
1069
+ throw new Error(
1070
+ "Agent instructions are still a file reference. The CLI resolves these before push."
1071
+ );
1072
+ }
1073
+ const tools = [];
1074
+ for (const tool of def.tools ?? []) {
1075
+ const action = {
1076
+ kind: DEFINE_KINDS.action,
1077
+ id: tool.id,
1078
+ appId: tool.appId,
1079
+ connection: tool.connection?.id ? { kind: DEFINE_KINDS.connection, id: tool.connection.id } : void 0
1080
+ };
1081
+ tools.push({
1082
+ key: tool.key,
1083
+ appId: resolveActionAppId(action),
1084
+ actionId: resolveActionId(action),
1085
+ connectionId: tool.connection?.id,
1086
+ name: tool.name,
1087
+ description: tool.description,
1088
+ input: tool.input
1089
+ });
1090
+ }
798
1091
  return {
799
1092
  slug: def.slug,
800
1093
  name: def.name,
801
- description: def.description,
1094
+ // A file is the whole definition. Omitted description and model are empty.
1095
+ description: def.description ?? null,
802
1096
  instructions: def.instructions,
803
- model: def.model,
1097
+ model: def.model ?? null,
804
1098
  aiConnectionId: def.aiConnection?.id,
805
- connectionIds: def.connections?.map((c) => c.id).filter((id) => Boolean(id)),
806
- workflowIds: workflowIds.length ? workflowIds : void 0,
807
- tools,
808
- managedByCode: true
1099
+ ...def.settings ? { settings: def.settings } : {},
1100
+ ...def.tools ? { tools } : {},
1101
+ ...def.workflows ? { workflows: def.workflows } : {},
1102
+ ...def.agents ? { agents: def.agents } : {},
1103
+ ...def.connections ? { connectionIds: idsOf(def.connections) } : {},
1104
+ ...def.knowledge ? { knowledgeIds: idsOf(def.knowledge) } : {},
1105
+ ...def.variables ? { variableIds: idsOf(def.variables) } : {},
1106
+ ...def.objects ? { objectIds: idsOf(def.objects) } : {},
1107
+ ...def.objectTypes ? { objectTypeIds: idsOf(def.objectTypes) } : {},
1108
+ ...def.mcpServers ? { mcpServers: def.mcpServers } : {},
1109
+ ...def.a2aAgents ? { a2aAgentIds: idsOf(def.a2aAgents) } : {},
1110
+ codeSource: "synced",
1111
+ ...syncFields(options)
809
1112
  };
810
1113
  }
811
1114
  var AgentsResource = class {
@@ -825,15 +1128,22 @@ var AgentsResource = class {
825
1128
  query: serializeListQuery(options)
826
1129
  });
827
1130
  }
1131
+ /** Exported definition for `lunnoa/agents/<slug>.json`. */
1132
+ getDefinition(slug) {
1133
+ return this.http.get(
1134
+ `/api/agents/slug/${encodeURIComponent(slug)}/definition`
1135
+ );
1136
+ }
828
1137
  /**
829
1138
  * Upsert an agent by slug inside the workspace on the API key.
830
1139
  * Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
831
1140
  */
832
1141
  upsertBySlug(body, options) {
833
1142
  const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
834
- return this.http.post("/api/agents/upsert", {
835
- body: payload
836
- });
1143
+ return this.http.post(
1144
+ "/api/agents/upsert",
1145
+ { body: payload }
1146
+ );
837
1147
  }
838
1148
  };
839
1149
 
@@ -1935,6 +2245,11 @@ function stepFromDefinition(step) {
1935
2245
  ...step.name ? { name: step.name } : {}
1936
2246
  };
1937
2247
  }
2248
+ if (step.paths?.length) {
2249
+ throw new Error(
2250
+ "runs.start runs straight lines only. Deploy workflows with paths, then execute them."
2251
+ );
2252
+ }
1938
2253
  const use = step.use;
1939
2254
  const input = {
1940
2255
  ...use.input ?? {},
@@ -1952,6 +2267,11 @@ function stepFromDefinition(step) {
1952
2267
  }
1953
2268
  function normalizeStartRunInput(input) {
1954
2269
  if (isWorkflowDefinition(input)) {
2270
+ if ("format" in input && input.format === "graph") {
2271
+ throw new Error(
2272
+ "runs.start runs straight lines only. Push the graph workflow, then execute it."
2273
+ );
2274
+ }
1955
2275
  return {
1956
2276
  steps: input.steps.map(stepFromDefinition),
1957
2277
  name: input.name ?? input.slug
@@ -2143,6 +2463,18 @@ var WorkflowAppsResource = class {
2143
2463
  }
2144
2464
  };
2145
2465
 
2466
+ // src/resources/code-sync.ts
2467
+ var CodeSyncResource = class {
2468
+ constructor(http) {
2469
+ this.http = http;
2470
+ }
2471
+ http;
2472
+ /** Workspace id and whether synced resources are locked in the UI. */
2473
+ context() {
2474
+ return this.http.get("/api/code-sync/context");
2475
+ }
2476
+ };
2477
+
2146
2478
  // src/resources/workflows.ts
2147
2479
  function triggerToUpsertInput(trigger) {
2148
2480
  return {
@@ -2155,8 +2487,38 @@ function triggerToUpsertInput(trigger) {
2155
2487
  ...trigger.description ? { description: trigger.description } : {}
2156
2488
  };
2157
2489
  }
2490
+ function asAction(use) {
2491
+ if (use && typeof use === "object" && "kind" in use && use.kind === DEFINE_KINDS.action) {
2492
+ return use;
2493
+ }
2494
+ const fileUse = use;
2495
+ return {
2496
+ kind: DEFINE_KINDS.action,
2497
+ id: fileUse.id,
2498
+ appId: fileUse.appId,
2499
+ connection: fileUse.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileUse.connection.id } : void 0
2500
+ };
2501
+ }
2502
+ function asTrigger(trigger) {
2503
+ if ("kind" in trigger && trigger.kind === DEFINE_KINDS.trigger) {
2504
+ return trigger;
2505
+ }
2506
+ const fileTrigger = trigger;
2507
+ return {
2508
+ kind: DEFINE_KINDS.trigger,
2509
+ id: fileTrigger.id,
2510
+ appId: fileTrigger.appId,
2511
+ connection: fileTrigger.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileTrigger.connection.id } : void 0,
2512
+ input: fileTrigger.input,
2513
+ inputs: fileTrigger.inputs,
2514
+ name: fileTrigger.name,
2515
+ description: fileTrigger.description
2516
+ };
2517
+ }
2158
2518
  function stepToUpsertInput(step) {
2159
- const use = step.use;
2519
+ const use = asAction(step.use);
2520
+ const retry = "retrySettings" in step ? step.retrySettings : void 0;
2521
+ const continueOnFailure = "continueOnFailure" in step ? step.continueOnFailure : void 0;
2160
2522
  return {
2161
2523
  ...step.key ? { key: step.key } : {},
2162
2524
  appId: resolveActionAppId(use),
@@ -2167,18 +2529,60 @@ function stepToUpsertInput(step) {
2167
2529
  ...step.input ?? {}
2168
2530
  },
2169
2531
  name: step.name ?? use.name,
2170
- description: use.description
2532
+ description: step.description ?? use.description,
2533
+ ...retry ? { retrySettings: retry } : {},
2534
+ ...continueOnFailure ? { continueOnFailure } : {},
2535
+ ...step.paths?.length ? {
2536
+ paths: step.paths.map((path) => ({
2537
+ label: path.label,
2538
+ ...path.when ? { when: path.when } : {},
2539
+ steps: path.steps.map(
2540
+ (nested) => stepToUpsertInput(nested)
2541
+ )
2542
+ }))
2543
+ } : {}
2171
2544
  };
2172
2545
  }
2173
- function workflowDefinitionToUpsert(def) {
2546
+ function syncFields2(options) {
2174
2547
  return {
2175
- slug: def.slug,
2176
- name: def.name,
2177
- description: def.description,
2178
- isActive: def.isActive,
2179
- ...def.trigger ? { trigger: triggerToUpsertInput(def.trigger) } : {},
2180
- managedByCode: true,
2181
- steps: def.steps.map(stepToUpsertInput)
2548
+ ...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
2549
+ ...options?.force ? { force: true } : {}
2550
+ };
2551
+ }
2552
+ function workflowDefinitionToUpsert(def, options) {
2553
+ if ("format" in def && def.format === "graph") {
2554
+ return {
2555
+ slug: def.slug,
2556
+ name: def.name,
2557
+ // A file is the whole definition: an omitted description is empty, not "leave it".
2558
+ description: def.description ?? null,
2559
+ codeSource: "synced",
2560
+ graph: { nodes: def.nodes, edges: def.edges ?? [] },
2561
+ ...syncFields2(options)
2562
+ };
2563
+ }
2564
+ if ("format" in def && def.format === "steps") {
2565
+ const file = def;
2566
+ return {
2567
+ slug: file.slug,
2568
+ name: file.name,
2569
+ description: file.description ?? null,
2570
+ ...file.trigger ? { trigger: triggerToUpsertInput(asTrigger(file.trigger)) } : {},
2571
+ codeSource: "synced",
2572
+ steps: file.steps.map((step) => stepToUpsertInput(step)),
2573
+ ...syncFields2(options)
2574
+ };
2575
+ }
2576
+ const authored = def;
2577
+ return {
2578
+ slug: authored.slug,
2579
+ name: authored.name,
2580
+ description: authored.description,
2581
+ isActive: authored.isActive,
2582
+ ...authored.trigger ? { trigger: triggerToUpsertInput(authored.trigger) } : {},
2583
+ codeSource: options?.codeSource ?? "generated",
2584
+ ...syncFields2(options),
2585
+ steps: authored.steps.map((step) => stepToUpsertInput(step))
2182
2586
  };
2183
2587
  }
2184
2588
  var WorkflowsResource = class {
@@ -2203,15 +2607,22 @@ var WorkflowsResource = class {
2203
2607
  query: serializeListQuery(options)
2204
2608
  });
2205
2609
  }
2610
+ /** Exported definition for `lunnoa/workflows/<slug>.json`. */
2611
+ getDefinition(slug) {
2612
+ return this.http.get(
2613
+ `/api/workflows/slug/${encodeURIComponent(slug)}/definition`
2614
+ );
2615
+ }
2206
2616
  /**
2207
2617
  * Upsert a workflow by slug inside the workspace on the API key.
2208
2618
  * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
2209
2619
  */
2210
- upsertBySlug(body) {
2211
- const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
2212
- return this.http.post("/api/workflows/upsert", {
2213
- body: payload
2214
- });
2620
+ upsertBySlug(body, options) {
2621
+ const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body, options) : body;
2622
+ return this.http.post(
2623
+ "/api/workflows/upsert",
2624
+ { body: payload }
2625
+ );
2215
2626
  }
2216
2627
  /**
2217
2628
  * Starts a new execution of the workflow with the given trigger inputs.
@@ -2277,6 +2688,8 @@ var LunnoaClient = class {
2277
2688
  aiConnections;
2278
2689
  workflowApps;
2279
2690
  discovery;
2691
+ /** Workspace id for the definitions lock, and the code-sync lock flag. */
2692
+ codeSync;
2280
2693
  constructor(options) {
2281
2694
  this.http = new HttpClient(options);
2282
2695
  this.auth = new AuthResource(this.http, this.http.tokenStore);
@@ -2300,6 +2713,7 @@ var LunnoaClient = class {
2300
2713
  this.connections = new ConnectionsResource(this.http);
2301
2714
  this.aiConnections = new AiConnectionsResource(this.http);
2302
2715
  this.discovery = new DiscoveryResource(this.http);
2716
+ this.codeSync = new CodeSyncResource(this.http);
2303
2717
  }
2304
2718
  };
2305
2719
 
@@ -2375,6 +2789,6 @@ function createServerClient(options) {
2375
2789
  });
2376
2790
  }
2377
2791
 
2378
- export { API_KEY_PREFIX, ActionsResource, AgentChatClient, AgentChatStream, AgentsResource, AiConnectionsResource, ApprovalsResource, AuthResource, ConnectionsResource, DEFINE_KINDS, DiscoveryResource, EntitiesResource, EntityTypesResource, ExecutionProgressStream, ExecutionTimeoutError, ExecutionsResource, HttpClient, KnowledgeResource, LunnoaApiError, LunnoaClient, QueueItemsResource, QueuesResource, RunsResource, ScorecardsResource, SsoAuthResource, TablesResource, TasksResource, VariablesResource, WorkflowAppsResource, WorkflowsResource, assertApiKeyAllowedInEnvironment, createBrowserClient, createLocalStorageTokenStore, createMemoryTokenStore, createServerClient, defineAction, defineAgent, defineAiConnection, defineConnection, defineEntityType, defineTrigger, defineWorkflow, entityTypeDefinitionToUpsert, isBrowserEnvironment, isLoginRequires2FA, iterateOffset, iteratePages, parseSseEvents, parseSseStream, ref, resolveActionAppId, resolveActionId, resolveTriggerAppId, resolveTriggerId };
2792
+ export { API_KEY_PREFIX, ActionsResource, AgentChatClient, AgentChatStream, AgentsResource, AiConnectionsResource, ApprovalsResource, AuthResource, CodeSyncResource, ConnectionsResource, DEFINE_KINDS, DiscoveryResource, EntitiesResource, EntityTypesResource, ExecutionProgressStream, ExecutionTimeoutError, ExecutionsResource, HttpClient, KnowledgeResource, LunnoaApiError, LunnoaClient, PATH_CONDITIONS, QueueItemsResource, QueuesResource, RunsResource, ScorecardsResource, SsoAuthResource, TablesResource, TasksResource, VariablesResource, WorkflowAppsResource, WorkflowsResource, assertApiKeyAllowedInEnvironment, createBrowserClient, createLocalStorageTokenStore, createMemoryTokenStore, createServerClient, defineAction, defineAgent, defineAiConnection, defineConnection, defineEntityType, defineTrigger, defineWorkflow, entityTypeDefinitionToUpsert, isAgentFileShape, isBrowserEnvironment, isLoginRequires2FA, iterateOffset, iteratePages, parseSseEvents, parseSseStream, ref, resolveActionAppId, resolveActionId, resolveTriggerAppId, resolveTriggerId };
2379
2793
  //# sourceMappingURL=index.js.map
2380
2794
  //# sourceMappingURL=index.js.map