@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/react.js
CHANGED
|
@@ -76,16 +76,21 @@ var LunnoaApiError = class extends Error {
|
|
|
76
76
|
method;
|
|
77
77
|
/** Path of the failed request (without the base URL). */
|
|
78
78
|
path;
|
|
79
|
+
/** Present on definition-sync 409s when the server sends the current revision. */
|
|
80
|
+
currentRevision;
|
|
79
81
|
constructor(args) {
|
|
80
82
|
const detail = extractMessage(args.body);
|
|
83
|
+
const currentRevision = extractCurrentRevision(args.body);
|
|
84
|
+
const shown = [detail, currentRevision ? `currentRevision: ${currentRevision}` : void 0].filter(Boolean).join(" ");
|
|
81
85
|
super(
|
|
82
|
-
`Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${
|
|
86
|
+
`Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${shown ? ` (${shown})` : ""}`
|
|
83
87
|
);
|
|
84
88
|
this.name = "LunnoaApiError";
|
|
85
89
|
this.status = args.status;
|
|
86
90
|
this.body = args.body;
|
|
87
91
|
this.method = args.method;
|
|
88
92
|
this.path = args.path;
|
|
93
|
+
this.currentRevision = currentRevision;
|
|
89
94
|
}
|
|
90
95
|
/** True for 401 responses (missing, revoked, or expired credential). */
|
|
91
96
|
get isUnauthorized() {
|
|
@@ -104,6 +109,11 @@ var LunnoaApiError = class extends Error {
|
|
|
104
109
|
return this.status === 429;
|
|
105
110
|
}
|
|
106
111
|
};
|
|
112
|
+
function extractCurrentRevision(body) {
|
|
113
|
+
if (!body || typeof body !== "object") return void 0;
|
|
114
|
+
const revision = body.currentRevision;
|
|
115
|
+
return typeof revision === "string" ? revision : void 0;
|
|
116
|
+
}
|
|
107
117
|
function extractMessage(body) {
|
|
108
118
|
if (body && typeof body === "object") {
|
|
109
119
|
const record = body;
|
|
@@ -580,10 +590,55 @@ var ActionsResource = class {
|
|
|
580
590
|
}
|
|
581
591
|
};
|
|
582
592
|
|
|
593
|
+
// src/define/files.ts
|
|
594
|
+
function hasFileAgentSignals(record) {
|
|
595
|
+
if (record.instructions && typeof record.instructions === "object") return true;
|
|
596
|
+
if (record.settings && typeof record.settings === "object") return true;
|
|
597
|
+
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) {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
return Array.isArray(record.tools) && record.tools.some(
|
|
601
|
+
(tool) => tool && typeof tool === "object" && !("kind" in tool) && !("use" in tool)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
function isAgentFileShape(config) {
|
|
605
|
+
if (!config || typeof config !== "object") return false;
|
|
606
|
+
return hasFileAgentSignals(config);
|
|
607
|
+
}
|
|
608
|
+
|
|
583
609
|
// src/define/index.ts
|
|
584
610
|
var DEFINE_KINDS = {
|
|
611
|
+
connection: "connection",
|
|
585
612
|
action: "action",
|
|
613
|
+
trigger: "trigger",
|
|
586
614
|
workflow: "workflow"};
|
|
615
|
+
var PATH_CONDITIONS = [
|
|
616
|
+
"contains",
|
|
617
|
+
"does_not_contain",
|
|
618
|
+
"equals",
|
|
619
|
+
"does_not_equal",
|
|
620
|
+
"exists",
|
|
621
|
+
"does_not_exist",
|
|
622
|
+
"starts_with",
|
|
623
|
+
"ends_with",
|
|
624
|
+
"is_empty",
|
|
625
|
+
"is_not_empty",
|
|
626
|
+
"comes_before",
|
|
627
|
+
"comes_after",
|
|
628
|
+
"is_in_the_past",
|
|
629
|
+
"is_in_the_future",
|
|
630
|
+
"is_true",
|
|
631
|
+
"is_false",
|
|
632
|
+
"is_truthy",
|
|
633
|
+
"is_falsey",
|
|
634
|
+
"is_greater_than",
|
|
635
|
+
"is_less_than",
|
|
636
|
+
"is_greater_than_or_equal_to",
|
|
637
|
+
"is_less_than_or_equal_to",
|
|
638
|
+
"matches_regex",
|
|
639
|
+
"does_not_match_regex"
|
|
640
|
+
];
|
|
641
|
+
new Set(PATH_CONDITIONS);
|
|
587
642
|
function resolveActionAppId(action) {
|
|
588
643
|
if (action.appId?.trim()) {
|
|
589
644
|
return action.appId.trim();
|
|
@@ -684,10 +739,24 @@ function unwrapTool(tool) {
|
|
|
684
739
|
}
|
|
685
740
|
return tool;
|
|
686
741
|
}
|
|
742
|
+
function syncFields(options) {
|
|
743
|
+
return {
|
|
744
|
+
...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
|
|
745
|
+
...options?.force ? { force: true } : {}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function idsOf(links) {
|
|
749
|
+
if (!links) return void 0;
|
|
750
|
+
return links.map((link) => link.id);
|
|
751
|
+
}
|
|
687
752
|
function agentDefinitionToUpsert(def, options) {
|
|
753
|
+
if (isAgentFileShape(def)) {
|
|
754
|
+
return fileAgentToUpsert(def, options);
|
|
755
|
+
}
|
|
688
756
|
const tools = [];
|
|
689
757
|
const workflowIds = [...options?.workflowIds ?? []];
|
|
690
|
-
|
|
758
|
+
const authored = def;
|
|
759
|
+
for (const tool of authored.tools ?? []) {
|
|
691
760
|
const unwrapped = unwrapTool(tool);
|
|
692
761
|
if (unwrapped.kind === DEFINE_KINDS.workflow) {
|
|
693
762
|
continue;
|
|
@@ -703,17 +772,64 @@ function agentDefinitionToUpsert(def, options) {
|
|
|
703
772
|
});
|
|
704
773
|
}
|
|
705
774
|
}
|
|
775
|
+
return {
|
|
776
|
+
slug: authored.slug,
|
|
777
|
+
name: authored.name,
|
|
778
|
+
description: authored.description,
|
|
779
|
+
instructions: authored.instructions,
|
|
780
|
+
model: authored.model,
|
|
781
|
+
aiConnectionId: authored.aiConnection?.id,
|
|
782
|
+
connectionIds: authored.connections?.map((c) => c.id).filter((id) => Boolean(id)),
|
|
783
|
+
workflowIds: workflowIds.length ? workflowIds : void 0,
|
|
784
|
+
tools,
|
|
785
|
+
codeSource: options?.codeSource ?? "generated",
|
|
786
|
+
...syncFields(options)
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function fileAgentToUpsert(def, options) {
|
|
790
|
+
if (typeof def.instructions !== "string") {
|
|
791
|
+
throw new Error(
|
|
792
|
+
"Agent instructions are still a file reference. The CLI resolves these before push."
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
const tools = [];
|
|
796
|
+
for (const tool of def.tools ?? []) {
|
|
797
|
+
const action = {
|
|
798
|
+
id: tool.id,
|
|
799
|
+
appId: tool.appId,
|
|
800
|
+
connection: tool.connection?.id ? { id: tool.connection.id } : void 0
|
|
801
|
+
};
|
|
802
|
+
tools.push({
|
|
803
|
+
key: tool.key,
|
|
804
|
+
appId: resolveActionAppId(action),
|
|
805
|
+
actionId: resolveActionId(action),
|
|
806
|
+
connectionId: tool.connection?.id,
|
|
807
|
+
name: tool.name,
|
|
808
|
+
description: tool.description,
|
|
809
|
+
input: tool.input
|
|
810
|
+
});
|
|
811
|
+
}
|
|
706
812
|
return {
|
|
707
813
|
slug: def.slug,
|
|
708
814
|
name: def.name,
|
|
709
|
-
description
|
|
815
|
+
// A file is the whole definition. Omitted description and model are empty.
|
|
816
|
+
description: def.description ?? null,
|
|
710
817
|
instructions: def.instructions,
|
|
711
|
-
model: def.model,
|
|
818
|
+
model: def.model ?? null,
|
|
712
819
|
aiConnectionId: def.aiConnection?.id,
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
820
|
+
...def.settings ? { settings: def.settings } : {},
|
|
821
|
+
...def.tools ? { tools } : {},
|
|
822
|
+
...def.workflows ? { workflows: def.workflows } : {},
|
|
823
|
+
...def.agents ? { agents: def.agents } : {},
|
|
824
|
+
...def.connections ? { connectionIds: idsOf(def.connections) } : {},
|
|
825
|
+
...def.knowledge ? { knowledgeIds: idsOf(def.knowledge) } : {},
|
|
826
|
+
...def.variables ? { variableIds: idsOf(def.variables) } : {},
|
|
827
|
+
...def.objects ? { objectIds: idsOf(def.objects) } : {},
|
|
828
|
+
...def.objectTypes ? { objectTypeIds: idsOf(def.objectTypes) } : {},
|
|
829
|
+
...def.mcpServers ? { mcpServers: def.mcpServers } : {},
|
|
830
|
+
...def.a2aAgents ? { a2aAgentIds: idsOf(def.a2aAgents) } : {},
|
|
831
|
+
codeSource: "synced",
|
|
832
|
+
...syncFields(options)
|
|
717
833
|
};
|
|
718
834
|
}
|
|
719
835
|
var AgentsResource = class {
|
|
@@ -733,15 +849,22 @@ var AgentsResource = class {
|
|
|
733
849
|
query: serializeListQuery(options)
|
|
734
850
|
});
|
|
735
851
|
}
|
|
852
|
+
/** Exported definition for `lunnoa/agents/<slug>.json`. */
|
|
853
|
+
getDefinition(slug) {
|
|
854
|
+
return this.http.get(
|
|
855
|
+
`/api/agents/slug/${encodeURIComponent(slug)}/definition`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
736
858
|
/**
|
|
737
859
|
* Upsert an agent by slug inside the workspace on the API key.
|
|
738
860
|
* Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
|
|
739
861
|
*/
|
|
740
862
|
upsertBySlug(body, options) {
|
|
741
863
|
const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
|
|
742
|
-
return this.http.post(
|
|
743
|
-
|
|
744
|
-
|
|
864
|
+
return this.http.post(
|
|
865
|
+
"/api/agents/upsert",
|
|
866
|
+
{ body: payload }
|
|
867
|
+
);
|
|
745
868
|
}
|
|
746
869
|
};
|
|
747
870
|
|
|
@@ -1843,6 +1966,11 @@ function stepFromDefinition(step) {
|
|
|
1843
1966
|
...step.name ? { name: step.name } : {}
|
|
1844
1967
|
};
|
|
1845
1968
|
}
|
|
1969
|
+
if (step.paths?.length) {
|
|
1970
|
+
throw new Error(
|
|
1971
|
+
"runs.start runs straight lines only. Deploy workflows with paths, then execute them."
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1846
1974
|
const use = step.use;
|
|
1847
1975
|
const input = {
|
|
1848
1976
|
...use.input ?? {},
|
|
@@ -1860,6 +1988,11 @@ function stepFromDefinition(step) {
|
|
|
1860
1988
|
}
|
|
1861
1989
|
function normalizeStartRunInput(input) {
|
|
1862
1990
|
if (isWorkflowDefinition(input)) {
|
|
1991
|
+
if ("format" in input && input.format === "graph") {
|
|
1992
|
+
throw new Error(
|
|
1993
|
+
"runs.start runs straight lines only. Push the graph workflow, then execute it."
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1863
1996
|
return {
|
|
1864
1997
|
steps: input.steps.map(stepFromDefinition),
|
|
1865
1998
|
name: input.name ?? input.slug
|
|
@@ -2051,6 +2184,18 @@ var WorkflowAppsResource = class {
|
|
|
2051
2184
|
}
|
|
2052
2185
|
};
|
|
2053
2186
|
|
|
2187
|
+
// src/resources/code-sync.ts
|
|
2188
|
+
var CodeSyncResource = class {
|
|
2189
|
+
constructor(http) {
|
|
2190
|
+
this.http = http;
|
|
2191
|
+
}
|
|
2192
|
+
http;
|
|
2193
|
+
/** Workspace id and whether synced resources are locked in the UI. */
|
|
2194
|
+
context() {
|
|
2195
|
+
return this.http.get("/api/code-sync/context");
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2054
2199
|
// src/resources/workflows.ts
|
|
2055
2200
|
function triggerToUpsertInput(trigger) {
|
|
2056
2201
|
return {
|
|
@@ -2063,8 +2208,38 @@ function triggerToUpsertInput(trigger) {
|
|
|
2063
2208
|
...trigger.description ? { description: trigger.description } : {}
|
|
2064
2209
|
};
|
|
2065
2210
|
}
|
|
2211
|
+
function asAction(use) {
|
|
2212
|
+
if (use && typeof use === "object" && "kind" in use && use.kind === DEFINE_KINDS.action) {
|
|
2213
|
+
return use;
|
|
2214
|
+
}
|
|
2215
|
+
const fileUse = use;
|
|
2216
|
+
return {
|
|
2217
|
+
kind: DEFINE_KINDS.action,
|
|
2218
|
+
id: fileUse.id,
|
|
2219
|
+
appId: fileUse.appId,
|
|
2220
|
+
connection: fileUse.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileUse.connection.id } : void 0
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
function asTrigger(trigger) {
|
|
2224
|
+
if ("kind" in trigger && trigger.kind === DEFINE_KINDS.trigger) {
|
|
2225
|
+
return trigger;
|
|
2226
|
+
}
|
|
2227
|
+
const fileTrigger = trigger;
|
|
2228
|
+
return {
|
|
2229
|
+
kind: DEFINE_KINDS.trigger,
|
|
2230
|
+
id: fileTrigger.id,
|
|
2231
|
+
appId: fileTrigger.appId,
|
|
2232
|
+
connection: fileTrigger.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileTrigger.connection.id } : void 0,
|
|
2233
|
+
input: fileTrigger.input,
|
|
2234
|
+
inputs: fileTrigger.inputs,
|
|
2235
|
+
name: fileTrigger.name,
|
|
2236
|
+
description: fileTrigger.description
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2066
2239
|
function stepToUpsertInput(step) {
|
|
2067
|
-
const use = step.use;
|
|
2240
|
+
const use = asAction(step.use);
|
|
2241
|
+
const retry = "retrySettings" in step ? step.retrySettings : void 0;
|
|
2242
|
+
const continueOnFailure = "continueOnFailure" in step ? step.continueOnFailure : void 0;
|
|
2068
2243
|
return {
|
|
2069
2244
|
...step.key ? { key: step.key } : {},
|
|
2070
2245
|
appId: resolveActionAppId(use),
|
|
@@ -2075,18 +2250,60 @@ function stepToUpsertInput(step) {
|
|
|
2075
2250
|
...step.input ?? {}
|
|
2076
2251
|
},
|
|
2077
2252
|
name: step.name ?? use.name,
|
|
2078
|
-
description: use.description
|
|
2253
|
+
description: step.description ?? use.description,
|
|
2254
|
+
...retry ? { retrySettings: retry } : {},
|
|
2255
|
+
...continueOnFailure ? { continueOnFailure } : {},
|
|
2256
|
+
...step.paths?.length ? {
|
|
2257
|
+
paths: step.paths.map((path) => ({
|
|
2258
|
+
label: path.label,
|
|
2259
|
+
...path.when ? { when: path.when } : {},
|
|
2260
|
+
steps: path.steps.map(
|
|
2261
|
+
(nested) => stepToUpsertInput(nested)
|
|
2262
|
+
)
|
|
2263
|
+
}))
|
|
2264
|
+
} : {}
|
|
2079
2265
|
};
|
|
2080
2266
|
}
|
|
2081
|
-
function
|
|
2267
|
+
function syncFields2(options) {
|
|
2082
2268
|
return {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2269
|
+
...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
|
|
2270
|
+
...options?.force ? { force: true } : {}
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
function workflowDefinitionToUpsert(def, options) {
|
|
2274
|
+
if ("format" in def && def.format === "graph") {
|
|
2275
|
+
return {
|
|
2276
|
+
slug: def.slug,
|
|
2277
|
+
name: def.name,
|
|
2278
|
+
// A file is the whole definition: an omitted description is empty, not "leave it".
|
|
2279
|
+
description: def.description ?? null,
|
|
2280
|
+
codeSource: "synced",
|
|
2281
|
+
graph: { nodes: def.nodes, edges: def.edges ?? [] },
|
|
2282
|
+
...syncFields2(options)
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
if ("format" in def && def.format === "steps") {
|
|
2286
|
+
const file = def;
|
|
2287
|
+
return {
|
|
2288
|
+
slug: file.slug,
|
|
2289
|
+
name: file.name,
|
|
2290
|
+
description: file.description ?? null,
|
|
2291
|
+
...file.trigger ? { trigger: triggerToUpsertInput(asTrigger(file.trigger)) } : {},
|
|
2292
|
+
codeSource: "synced",
|
|
2293
|
+
steps: file.steps.map((step) => stepToUpsertInput(step)),
|
|
2294
|
+
...syncFields2(options)
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
const authored = def;
|
|
2298
|
+
return {
|
|
2299
|
+
slug: authored.slug,
|
|
2300
|
+
name: authored.name,
|
|
2301
|
+
description: authored.description,
|
|
2302
|
+
isActive: authored.isActive,
|
|
2303
|
+
...authored.trigger ? { trigger: triggerToUpsertInput(authored.trigger) } : {},
|
|
2304
|
+
codeSource: options?.codeSource ?? "generated",
|
|
2305
|
+
...syncFields2(options),
|
|
2306
|
+
steps: authored.steps.map((step) => stepToUpsertInput(step))
|
|
2090
2307
|
};
|
|
2091
2308
|
}
|
|
2092
2309
|
var WorkflowsResource = class {
|
|
@@ -2111,15 +2328,22 @@ var WorkflowsResource = class {
|
|
|
2111
2328
|
query: serializeListQuery(options)
|
|
2112
2329
|
});
|
|
2113
2330
|
}
|
|
2331
|
+
/** Exported definition for `lunnoa/workflows/<slug>.json`. */
|
|
2332
|
+
getDefinition(slug) {
|
|
2333
|
+
return this.http.get(
|
|
2334
|
+
`/api/workflows/slug/${encodeURIComponent(slug)}/definition`
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2114
2337
|
/**
|
|
2115
2338
|
* Upsert a workflow by slug inside the workspace on the API key.
|
|
2116
2339
|
* Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
|
|
2117
2340
|
*/
|
|
2118
|
-
upsertBySlug(body) {
|
|
2119
|
-
const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
|
|
2120
|
-
return this.http.post(
|
|
2121
|
-
|
|
2122
|
-
|
|
2341
|
+
upsertBySlug(body, options) {
|
|
2342
|
+
const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body, options) : body;
|
|
2343
|
+
return this.http.post(
|
|
2344
|
+
"/api/workflows/upsert",
|
|
2345
|
+
{ body: payload }
|
|
2346
|
+
);
|
|
2123
2347
|
}
|
|
2124
2348
|
/**
|
|
2125
2349
|
* Starts a new execution of the workflow with the given trigger inputs.
|
|
@@ -2185,6 +2409,8 @@ var LunnoaClient = class {
|
|
|
2185
2409
|
aiConnections;
|
|
2186
2410
|
workflowApps;
|
|
2187
2411
|
discovery;
|
|
2412
|
+
/** Workspace id for the definitions lock, and the code-sync lock flag. */
|
|
2413
|
+
codeSync;
|
|
2188
2414
|
constructor(options) {
|
|
2189
2415
|
this.http = new HttpClient(options);
|
|
2190
2416
|
this.auth = new AuthResource(this.http, this.http.tokenStore);
|
|
@@ -2208,6 +2434,7 @@ var LunnoaClient = class {
|
|
|
2208
2434
|
this.connections = new ConnectionsResource(this.http);
|
|
2209
2435
|
this.aiConnections = new AiConnectionsResource(this.http);
|
|
2210
2436
|
this.discovery = new DiscoveryResource(this.http);
|
|
2437
|
+
this.codeSync = new CodeSyncResource(this.http);
|
|
2211
2438
|
}
|
|
2212
2439
|
};
|
|
2213
2440
|
|