@lunnoa/client 2.3.0 → 2.5.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 +2 -2
- package/dist/cli.js +43 -1
- package/dist/index.cjs +171 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -6
- package/dist/index.d.ts +84 -6
- package/dist/index.js +170 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +43 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +71 -4
- package/dist/react.d.ts +71 -4
- package/dist/react.js +43 -1
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/skills/lunnoa-client/SKILL.md +76 -2
- package/skills/lunnoa-solution-design/SKILL.md +8 -2
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ Re-run codegen whenever entity types, workflows, or agents change on the deploym
|
|
|
249
249
|
|
|
250
250
|
## Define, run, and CLI deploy
|
|
251
251
|
|
|
252
|
-
Author local definitions with `defineWorkflow` / `defineAgent` / `defineEntityType`, run linear chains with `lunnoa.runs.start`, and upsert with the CLI (prefer CLI over calling upsert from app code). Agent and workflow upsert is by slug inside the workspace on the API key. An entity type slug is unique on the deployment. Deploy grants that type to the API key's workspace when the slug is new, and will not rewrite a type already granted to other workspaces. Instance writes reject unknown attribute keys unless `allowUnknownAttributes` is true — use the reserved `extensions` object for flexible bags:
|
|
252
|
+
Author local definitions with `defineWorkflow` / `defineAgent` / `defineEntityType`, run linear chains with `lunnoa.runs.start`, branch deployed workflows with `paths` on Request Approval or Conditional Paths, and upsert with the CLI (prefer CLI over calling upsert from app code). Agent and workflow upsert is by slug inside the workspace on the API key. An entity type slug is unique on the deployment. Deploy grants that type to the API key's workspace when the slug is new, and will not rewrite a type already granted to other workspaces. Instance writes reject unknown attribute keys unless `allowUnknownAttributes` is true — use the reserved `extensions` object for flexible bags:
|
|
253
253
|
|
|
254
254
|
```bash
|
|
255
255
|
npx @lunnoa/client entity-types deploy ./human.ts --url https://lunnoa.example --api-key lna_...
|
|
@@ -264,7 +264,7 @@ npx @lunnoa/client agents deploy ./agents/triage.ts \
|
|
|
264
264
|
--url https://lunnoa.example --api-key lna_...
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
`defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. `defineWorkflow` takes an optional `defineTrigger`. Omit it for Manually Run with no input fields. `runs.start` ignores the trigger and runs the steps only. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
|
|
267
|
+
`defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. `defineWorkflow` takes an optional `defineTrigger`. Omit it for Manually Run with no input fields. `runs.start` ignores the trigger and runs the steps only. Pass data into step settings with `ref.trigger('caseId')` or `ref.step('load', 'rows', 0)` (give the source step a `key`); the server resolves them on deploy and the builder shows them as mapped fields. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
|
|
268
268
|
|
|
269
269
|
### CLI discovery
|
|
270
270
|
|
package/dist/cli.js
CHANGED
|
@@ -546,6 +546,33 @@ var DEFINE_KINDS = {
|
|
|
546
546
|
agent: "agent",
|
|
547
547
|
entityType: "entityType"
|
|
548
548
|
};
|
|
549
|
+
var PATH_CONDITIONS = [
|
|
550
|
+
"contains",
|
|
551
|
+
"does_not_contain",
|
|
552
|
+
"equals",
|
|
553
|
+
"does_not_equal",
|
|
554
|
+
"exists",
|
|
555
|
+
"does_not_exist",
|
|
556
|
+
"starts_with",
|
|
557
|
+
"ends_with",
|
|
558
|
+
"is_empty",
|
|
559
|
+
"is_not_empty",
|
|
560
|
+
"comes_before",
|
|
561
|
+
"comes_after",
|
|
562
|
+
"is_in_the_past",
|
|
563
|
+
"is_in_the_future",
|
|
564
|
+
"is_true",
|
|
565
|
+
"is_false",
|
|
566
|
+
"is_truthy",
|
|
567
|
+
"is_falsey",
|
|
568
|
+
"is_greater_than",
|
|
569
|
+
"is_less_than",
|
|
570
|
+
"is_greater_than_or_equal_to",
|
|
571
|
+
"is_less_than_or_equal_to",
|
|
572
|
+
"matches_regex",
|
|
573
|
+
"does_not_match_regex"
|
|
574
|
+
];
|
|
575
|
+
var PATH_CONDITION_SET = new Set(PATH_CONDITIONS);
|
|
549
576
|
function resolveActionAppId(action) {
|
|
550
577
|
if (action.appId?.trim()) {
|
|
551
578
|
return action.appId.trim();
|
|
@@ -1805,12 +1832,19 @@ function stepFromDefinition(step) {
|
|
|
1805
1832
|
...step.name ? { name: step.name } : {}
|
|
1806
1833
|
};
|
|
1807
1834
|
}
|
|
1835
|
+
if (step.paths?.length) {
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
"runs.start runs straight lines only. Deploy workflows with paths, then execute them."
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1808
1840
|
const use = step.use;
|
|
1809
1841
|
const input = {
|
|
1810
1842
|
...use.input ?? {},
|
|
1811
1843
|
...step.input ?? {}
|
|
1812
1844
|
};
|
|
1845
|
+
const key = step.key;
|
|
1813
1846
|
return {
|
|
1847
|
+
...key ? { key } : {},
|
|
1814
1848
|
appId: resolveActionAppId(use),
|
|
1815
1849
|
actionId: resolveActionId(use),
|
|
1816
1850
|
...use.connection?.id ? { connectionId: use.connection.id } : {},
|
|
@@ -2026,6 +2060,7 @@ function triggerToUpsertInput(trigger) {
|
|
|
2026
2060
|
function stepToUpsertInput(step) {
|
|
2027
2061
|
const use = step.use;
|
|
2028
2062
|
return {
|
|
2063
|
+
...step.key ? { key: step.key } : {},
|
|
2029
2064
|
appId: resolveActionAppId(use),
|
|
2030
2065
|
actionId: resolveActionId(use),
|
|
2031
2066
|
connectionId: use.connection?.id,
|
|
@@ -2034,7 +2069,14 @@ function stepToUpsertInput(step) {
|
|
|
2034
2069
|
...step.input ?? {}
|
|
2035
2070
|
},
|
|
2036
2071
|
name: step.name ?? use.name,
|
|
2037
|
-
description: use.description
|
|
2072
|
+
description: use.description,
|
|
2073
|
+
...step.paths?.length ? {
|
|
2074
|
+
paths: step.paths.map((path) => ({
|
|
2075
|
+
label: path.label,
|
|
2076
|
+
...path.when ? { when: path.when } : {},
|
|
2077
|
+
steps: path.steps.map(stepToUpsertInput)
|
|
2078
|
+
}))
|
|
2079
|
+
} : {}
|
|
2038
2080
|
};
|
|
2039
2081
|
}
|
|
2040
2082
|
function workflowDefinitionToUpsert(def) {
|
package/dist/index.cjs
CHANGED
|
@@ -542,6 +542,32 @@ var DEFINE_KINDS = {
|
|
|
542
542
|
agent: "agent",
|
|
543
543
|
entityType: "entityType"
|
|
544
544
|
};
|
|
545
|
+
var PATH_CONDITIONS = [
|
|
546
|
+
"contains",
|
|
547
|
+
"does_not_contain",
|
|
548
|
+
"equals",
|
|
549
|
+
"does_not_equal",
|
|
550
|
+
"exists",
|
|
551
|
+
"does_not_exist",
|
|
552
|
+
"starts_with",
|
|
553
|
+
"ends_with",
|
|
554
|
+
"is_empty",
|
|
555
|
+
"is_not_empty",
|
|
556
|
+
"comes_before",
|
|
557
|
+
"comes_after",
|
|
558
|
+
"is_in_the_past",
|
|
559
|
+
"is_in_the_future",
|
|
560
|
+
"is_true",
|
|
561
|
+
"is_false",
|
|
562
|
+
"is_truthy",
|
|
563
|
+
"is_falsey",
|
|
564
|
+
"is_greater_than",
|
|
565
|
+
"is_less_than",
|
|
566
|
+
"is_greater_than_or_equal_to",
|
|
567
|
+
"is_less_than_or_equal_to",
|
|
568
|
+
"matches_regex",
|
|
569
|
+
"does_not_match_regex"
|
|
570
|
+
];
|
|
545
571
|
function defineConnection(config = {}) {
|
|
546
572
|
return { kind: DEFINE_KINDS.connection, ...config };
|
|
547
573
|
}
|
|
@@ -563,7 +589,7 @@ function assertTriggerConfig(config) {
|
|
|
563
589
|
throw new Error("defineTrigger inputs require id");
|
|
564
590
|
}
|
|
565
591
|
}
|
|
566
|
-
if (config.inputs?.length && config.id.trim() !==
|
|
592
|
+
if (config.inputs?.length && config.id.trim() !== MANUAL_TRIGGER_ID) {
|
|
567
593
|
throw new Error(
|
|
568
594
|
"defineTrigger inputs are only valid on flow-control_trigger_manual"
|
|
569
595
|
);
|
|
@@ -573,6 +599,131 @@ function defineTrigger(config) {
|
|
|
573
599
|
assertTriggerConfig(config);
|
|
574
600
|
return { kind: DEFINE_KINDS.trigger, ...config };
|
|
575
601
|
}
|
|
602
|
+
var MANUAL_TRIGGER_ID = "flow-control_trigger_manual";
|
|
603
|
+
var TRIGGER_KEY = "trigger";
|
|
604
|
+
var STEP_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
605
|
+
var STEP_REF_PATTERN = /={{step:([^}]*)}}/g;
|
|
606
|
+
function refString(key, path) {
|
|
607
|
+
const parts = [key, ...path.map(String)];
|
|
608
|
+
for (const part of parts) {
|
|
609
|
+
if (!part || /[,{}]/.test(part)) {
|
|
610
|
+
throw new Error(`ref path parts must be non-empty and contain no , { or }: "${part}"`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return `={{step:${parts.join(",")}}}`;
|
|
614
|
+
}
|
|
615
|
+
var ref = {
|
|
616
|
+
/** A trigger output field, e.g. a Manually Run input id. */
|
|
617
|
+
trigger: (field, ...path) => refString(TRIGGER_KEY, [field, ...path]),
|
|
618
|
+
/** An earlier step's output, by its `key`. Array indexes are numbers. */
|
|
619
|
+
step: (key, ...path) => refString(key, path)
|
|
620
|
+
};
|
|
621
|
+
function collectRefs(value, out) {
|
|
622
|
+
if (typeof value === "string") {
|
|
623
|
+
for (const match of value.matchAll(STEP_REF_PATTERN)) {
|
|
624
|
+
out.push(match[1].split(",").map((part) => part.trim()));
|
|
625
|
+
}
|
|
626
|
+
} else if (Array.isArray(value)) {
|
|
627
|
+
value.forEach((item) => collectRefs(item, out));
|
|
628
|
+
} else if (value && typeof value === "object") {
|
|
629
|
+
Object.values(value).forEach((item) => collectRefs(item, out));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
var CONDITIONAL_PATHS_ID = "flow-control_action_conditional-paths";
|
|
633
|
+
var APPROVAL_ID = "flow-control_action_request-approval";
|
|
634
|
+
var BRANCHING_ACTION_IDS = /* @__PURE__ */ new Set([
|
|
635
|
+
CONDITIONAL_PATHS_ID,
|
|
636
|
+
"flow-control_action_manually-decide-paths",
|
|
637
|
+
APPROVAL_ID
|
|
638
|
+
]);
|
|
639
|
+
var PATH_CONDITION_SET = new Set(PATH_CONDITIONS);
|
|
640
|
+
var SINGLE_REF_PATTERN = /^={{step:[^}]*}}$/;
|
|
641
|
+
function collectKeys(steps, seen) {
|
|
642
|
+
for (const step of steps) {
|
|
643
|
+
if (step.key !== void 0) {
|
|
644
|
+
if (!STEP_KEY_PATTERN.test(step.key) || step.key === TRIGGER_KEY) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
`Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "${TRIGGER_KEY}".`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (seen.has(step.key)) {
|
|
650
|
+
throw new Error(`Step key "${step.key}" is used twice.`);
|
|
651
|
+
}
|
|
652
|
+
seen.add(step.key);
|
|
653
|
+
}
|
|
654
|
+
for (const path of step.paths ?? []) collectKeys(path.steps ?? [], seen);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function assertStepRefs(config) {
|
|
658
|
+
const triggerId = config.trigger?.id.trim() ?? MANUAL_TRIGGER_ID;
|
|
659
|
+
const triggerFields = triggerId === MANUAL_TRIGGER_ID ? new Set((config.trigger?.inputs ?? []).map((f) => f.id.trim())) : void 0;
|
|
660
|
+
const allKeys = /* @__PURE__ */ new Set();
|
|
661
|
+
collectKeys(config.steps, allKeys);
|
|
662
|
+
const checkRefs = (value, earlier, where) => {
|
|
663
|
+
const refs = [];
|
|
664
|
+
collectRefs(value, refs);
|
|
665
|
+
for (const [key = "", ...path] of refs) {
|
|
666
|
+
const shown = `={{step:${[key, ...path].join(",")}}}`;
|
|
667
|
+
if (!earlier.has(key)) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
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.`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
if (key === TRIGGER_KEY && triggerFields && !triggerFields.has(path[0] ?? "")) {
|
|
673
|
+
throw new Error(
|
|
674
|
+
`${where}: ${shown} uses trigger field "${path[0] ?? ""}", which is not declared in trigger inputs.`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const checkChain = (steps, visible) => {
|
|
680
|
+
const earlier = new Set(visible);
|
|
681
|
+
steps.forEach((step, index) => {
|
|
682
|
+
const where = `Step "${step.key ?? step.name ?? index + 1}"`;
|
|
683
|
+
checkRefs([step.use?.input, step.input], earlier, where);
|
|
684
|
+
if (step.key) earlier.add(step.key);
|
|
685
|
+
if (!step.paths?.length) return;
|
|
686
|
+
const actionId = step.use ? resolveActionId(step.use) : "";
|
|
687
|
+
if (!BRANCHING_ACTION_IDS.has(actionId)) {
|
|
688
|
+
throw new Error(`${where}: paths only work on ${[...BRANCHING_ACTION_IDS].join(", ")}.`);
|
|
689
|
+
}
|
|
690
|
+
if (index !== steps.length - 1) {
|
|
691
|
+
throw new Error(
|
|
692
|
+
`${where}: a step with paths must be the last step in its list. Put later steps inside each path.`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
if (actionId === APPROVAL_ID && step.paths.length > 2) {
|
|
696
|
+
throw new Error(`${where}: Request Approval takes at most two paths (approved, then rejected).`);
|
|
697
|
+
}
|
|
698
|
+
const labels = /* @__PURE__ */ new Set();
|
|
699
|
+
for (const path of step.paths) {
|
|
700
|
+
const label = path?.label?.trim();
|
|
701
|
+
if (!label) throw new Error(`${where}: every path needs a label.`);
|
|
702
|
+
if (labels.has(label)) throw new Error(`${where}: path label "${label}" is used twice.`);
|
|
703
|
+
labels.add(label);
|
|
704
|
+
if (path.when !== void 0 && actionId !== CONDITIONAL_PATHS_ID) {
|
|
705
|
+
throw new Error(`${where}: "when" only works on Conditional Paths.`);
|
|
706
|
+
}
|
|
707
|
+
for (const condition of (path.when ?? []).flat()) {
|
|
708
|
+
if (!SINGLE_REF_PATTERN.test(condition?.field?.trim() ?? "")) {
|
|
709
|
+
throw new Error(
|
|
710
|
+
`${where}, path "${label}": a condition's field must be one ref.trigger(...) or ref.step(...).`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
if (!PATH_CONDITION_SET.has(condition.condition)) {
|
|
714
|
+
throw new Error(
|
|
715
|
+
`${where}, path "${label}": condition "${condition.condition}" is not supported. Use one of: ${PATH_CONDITIONS.join(", ")}.`
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
checkRefs(path.when, earlier, `${where}, path "${label}"`);
|
|
720
|
+
if (!path.steps?.length) throw new Error(`${where}: path "${label}" needs at least one step.`);
|
|
721
|
+
checkChain(path.steps, earlier);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
checkChain(config.steps, /* @__PURE__ */ new Set([TRIGGER_KEY]));
|
|
726
|
+
}
|
|
576
727
|
function defineWorkflow(config) {
|
|
577
728
|
if (!config.slug?.trim()) {
|
|
578
729
|
throw new Error("defineWorkflow requires slug");
|
|
@@ -583,6 +734,7 @@ function defineWorkflow(config) {
|
|
|
583
734
|
if (config.trigger) {
|
|
584
735
|
assertTriggerConfig(config.trigger);
|
|
585
736
|
}
|
|
737
|
+
assertStepRefs(config);
|
|
586
738
|
return { kind: DEFINE_KINDS.workflow, ...config };
|
|
587
739
|
}
|
|
588
740
|
function defineAgent(config) {
|
|
@@ -1869,12 +2021,19 @@ function stepFromDefinition(step) {
|
|
|
1869
2021
|
...step.name ? { name: step.name } : {}
|
|
1870
2022
|
};
|
|
1871
2023
|
}
|
|
2024
|
+
if (step.paths?.length) {
|
|
2025
|
+
throw new Error(
|
|
2026
|
+
"runs.start runs straight lines only. Deploy workflows with paths, then execute them."
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
1872
2029
|
const use = step.use;
|
|
1873
2030
|
const input = {
|
|
1874
2031
|
...use.input ?? {},
|
|
1875
2032
|
...step.input ?? {}
|
|
1876
2033
|
};
|
|
2034
|
+
const key = step.key;
|
|
1877
2035
|
return {
|
|
2036
|
+
...key ? { key } : {},
|
|
1878
2037
|
appId: resolveActionAppId(use),
|
|
1879
2038
|
actionId: resolveActionId(use),
|
|
1880
2039
|
...use.connection?.id ? { connectionId: use.connection.id } : {},
|
|
@@ -2090,6 +2249,7 @@ function triggerToUpsertInput(trigger) {
|
|
|
2090
2249
|
function stepToUpsertInput(step) {
|
|
2091
2250
|
const use = step.use;
|
|
2092
2251
|
return {
|
|
2252
|
+
...step.key ? { key: step.key } : {},
|
|
2093
2253
|
appId: resolveActionAppId(use),
|
|
2094
2254
|
actionId: resolveActionId(use),
|
|
2095
2255
|
connectionId: use.connection?.id,
|
|
@@ -2098,7 +2258,14 @@ function stepToUpsertInput(step) {
|
|
|
2098
2258
|
...step.input ?? {}
|
|
2099
2259
|
},
|
|
2100
2260
|
name: step.name ?? use.name,
|
|
2101
|
-
description: use.description
|
|
2261
|
+
description: use.description,
|
|
2262
|
+
...step.paths?.length ? {
|
|
2263
|
+
paths: step.paths.map((path) => ({
|
|
2264
|
+
label: path.label,
|
|
2265
|
+
...path.when ? { when: path.when } : {},
|
|
2266
|
+
steps: path.steps.map(stepToUpsertInput)
|
|
2267
|
+
}))
|
|
2268
|
+
} : {}
|
|
2102
2269
|
};
|
|
2103
2270
|
}
|
|
2104
2271
|
function workflowDefinitionToUpsert(def) {
|
|
@@ -2326,6 +2493,7 @@ exports.HttpClient = HttpClient;
|
|
|
2326
2493
|
exports.KnowledgeResource = KnowledgeResource;
|
|
2327
2494
|
exports.LunnoaApiError = LunnoaApiError;
|
|
2328
2495
|
exports.LunnoaClient = LunnoaClient;
|
|
2496
|
+
exports.PATH_CONDITIONS = PATH_CONDITIONS;
|
|
2329
2497
|
exports.QueueItemsResource = QueueItemsResource;
|
|
2330
2498
|
exports.QueuesResource = QueuesResource;
|
|
2331
2499
|
exports.RunsResource = RunsResource;
|
|
@@ -2355,6 +2523,7 @@ exports.iterateOffset = iterateOffset;
|
|
|
2355
2523
|
exports.iteratePages = iteratePages;
|
|
2356
2524
|
exports.parseSseEvents = parseSseEvents;
|
|
2357
2525
|
exports.parseSseStream = parseSseStream;
|
|
2526
|
+
exports.ref = ref;
|
|
2358
2527
|
exports.resolveActionAppId = resolveActionAppId;
|
|
2359
2528
|
exports.resolveActionId = resolveActionId;
|
|
2360
2529
|
exports.resolveTriggerAppId = resolveTriggerAppId;
|