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