@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/README.md +24 -1
- package/dist/cli.js +1242 -107
- package/dist/index.cjs +473 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1294 -195
- package/dist/index.d.ts +1294 -195
- package/dist/index.js +471 -57
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +253 -26
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1051 -143
- package/dist/react.d.ts +1051 -143
- package/dist/react.js +253 -26
- package/dist/react.js.map +1 -1
- package/package.json +4 -2
- package/schemas/agent.schema.json +128 -0
- package/schemas/workflow.schema.json +156 -0
- package/skills/lunnoa-client/SKILL.md +178 -9
- package/skills/lunnoa-solution-design/SKILL.md +12 -5
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}${
|
|
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
|
|
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
|
-
|
|
611
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
883
|
+
if (!authored.steps?.length) {
|
|
648
884
|
throw new Error("defineWorkflow requires at least one step");
|
|
649
885
|
}
|
|
650
|
-
if (
|
|
651
|
-
assertTriggerConfig(
|
|
886
|
+
if (authored.trigger) {
|
|
887
|
+
assertTriggerConfig(authored.trigger);
|
|
652
888
|
}
|
|
653
|
-
assertStepRefs(
|
|
654
|
-
return { kind: DEFINE_KINDS.workflow, ...
|
|
889
|
+
assertStepRefs(authored);
|
|
890
|
+
return { kind: DEFINE_KINDS.workflow, ...authored };
|
|
655
891
|
}
|
|
656
892
|
function defineAgent(config) {
|
|
657
|
-
if (
|
|
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 (!
|
|
901
|
+
if (!authored.model?.trim()) {
|
|
661
902
|
throw new Error("defineAgent requires model");
|
|
662
903
|
}
|
|
663
|
-
if (
|
|
904
|
+
if (authored.instructions == null) {
|
|
664
905
|
throw new Error("defineAgent requires instructions");
|
|
665
906
|
}
|
|
666
|
-
return { kind: DEFINE_KINDS.agent, ...
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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(
|
|
837
|
-
|
|
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
|
|
2548
|
+
function syncFields2(options) {
|
|
2176
2549
|
return {
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
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(
|
|
2215
|
-
|
|
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;
|