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