@controllerai/cli 0.2.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 +180 -0
- package/dist/api/client.js +330 -0
- package/dist/api/client.js.map +1 -0
- package/dist/appLinks.js +99 -0
- package/dist/appLinks.js.map +1 -0
- package/dist/argv.js +124 -0
- package/dist/argv.js.map +1 -0
- package/dist/commands/agent.js +813 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/auth.js +327 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/branch.js +441 -0
- package/dist/commands/branch.js.map +1 -0
- package/dist/commands/connect.js +148 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/connection.js +148 -0
- package/dist/commands/connection.js.map +1 -0
- package/dist/commands/data.js +441 -0
- package/dist/commands/data.js.map +1 -0
- package/dist/commands/deps.js +63 -0
- package/dist/commands/deps.js.map +1 -0
- package/dist/commands/doctor.js +102 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/events.js +72 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/exec.js +306 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/expr.js +346 -0
- package/dist/commands/expr.js.map +1 -0
- package/dist/commands/flow.js +447 -0
- package/dist/commands/flow.js.map +1 -0
- package/dist/commands/integration.js +102 -0
- package/dist/commands/integration.js.map +1 -0
- package/dist/commands/node.js +885 -0
- package/dist/commands/node.js.map +1 -0
- package/dist/commands/organize.js +234 -0
- package/dist/commands/organize.js.map +1 -0
- package/dist/commands/run.js +437 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/schema.js +269 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/skills.js +1156 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/trigger.js +992 -0
- package/dist/commands/trigger.js.map +1 -0
- package/dist/commands/workflow.js +503 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/context.js +39 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/jsSource.js +32 -0
- package/dist/jsSource.js.map +1 -0
- package/dist/nodeProps.js +177 -0
- package/dist/nodeProps.js.map +1 -0
- package/dist/output.js +85 -0
- package/dist/output.js.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +41 -0
- package/skills/controllerai-agents/SKILL.md +169 -0
- package/skills/controllerai-agents/agents/openai.yaml +4 -0
- package/skills/controllerai-agents/references/instructions-and-versioning.md +76 -0
- package/skills/controllerai-ai-nodes/SKILL.md +83 -0
- package/skills/controllerai-ai-nodes/agents/openai.yaml +4 -0
- package/skills/controllerai-ai-nodes/references/selection-and-testing.md +46 -0
- package/skills/controllerai-data/SKILL.md +121 -0
- package/skills/controllerai-data/agents/openai.yaml +4 -0
- package/skills/controllerai-data/references/data-rails.md +46 -0
- package/skills/controllerai-expressions/SKILL.md +115 -0
- package/skills/controllerai-expressions/agents/openai.yaml +4 -0
- package/skills/controllerai-expressions/references/dsl-and-recipes.md +120 -0
- package/skills/controllerai-flow-control/SKILL.md +132 -0
- package/skills/controllerai-flow-control/agents/openai.yaml +4 -0
- package/skills/controllerai-flow-control/references/patterns.md +61 -0
- package/skills/controllerai-integration-actions/SKILL.md +95 -0
- package/skills/controllerai-integration-actions/agents/openai.yaml +4 -0
- package/skills/controllerai-node-config/SKILL.md +138 -0
- package/skills/controllerai-node-config/agents/openai.yaml +4 -0
- package/skills/controllerai-node-config/references/prop-and-readiness.md +72 -0
- package/skills/controllerai-runs/SKILL.md +109 -0
- package/skills/controllerai-runs/agents/openai.yaml +4 -0
- package/skills/controllerai-runs/references/debugging.md +53 -0
- package/skills/controllerai-start/SKILL.md +44 -0
- package/skills/controllerai-start/agents/openai.yaml +4 -0
- package/skills/controllerai-triggers/SKILL.md +174 -0
- package/skills/controllerai-triggers/agents/openai.yaml +4 -0
- package/skills/controllerai-triggers/references/lifecycle.md +54 -0
- package/skills/controllerai-workflows/SKILL.md +86 -0
- package/skills/controllerai-workflows/agents/openai.yaml +4 -0
- package/skills/controllerai-workflows/references/command-map.md +64 -0
- package/skills/manifest.json +248 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-expressions
|
|
3
|
+
description: Author, validate, persist, and debug Controller AI expressions for node props and conditions. Use when a value comes from a flow input, upstream node, loop item, workflow data, constant, calculation, template, or boolean rule, or when an expression compiles but resolves to null or the wrong input.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Controller AI expressions
|
|
7
|
+
|
|
8
|
+
Controller AI uses a restricted JavaScript-like authoring language that is compiled into the workflow engine's expression AST. It is not general JavaScript. Discover the real scope and compile before every write.
|
|
9
|
+
|
|
10
|
+
Data types are the TypeScript types of workflows: every expression is typed, and an expression can only be written against a known type. Integration types describe what integration actions output (resolved by a successful dev run); `custom.*` types describe shapes for data-store records and AI-node structured outputs. Two wrappers matter: `dataRecord.custom.x` means "an actual stored record of that shape" — the fields plus record identity (`id`, `createdAt`) — while bare `custom.x` is just data in that shape; `list.` prefixes a list of any type. When an expression cannot be authored, the cause is usually a missing type — resolve the upstream node's output type first, don't guess field paths.
|
|
11
|
+
|
|
12
|
+
## How empty values evaluate
|
|
13
|
+
|
|
14
|
+
An empty input is not "nothing" — it evaluates to the type's empty form: empty text `""` for TEXT, null/undefined for objects. Two platform semantics follow, and both fail silently rather than erroring:
|
|
15
|
+
|
|
16
|
+
- **A filter comparing against an empty value still filters — and matches NO records.** This is deliberate (fail-closed; matching everything by accident would be worse). If "empty should mean skip this filter", opt that constraint into ignore-empty mode explicitly (`ignoreEmptyConstraints`, see [references/dsl-and-recipes.md](references/dsl-and-recipes.md)).
|
|
17
|
+
- **An optional TEXT input left empty arrives as `""`**, so an `equals` constraint against it matches nothing — the classic symptom is "search finds zero results only when the optional filter is blank", which then cascades (e.g. a create-if-missing step makes duplicates).
|
|
18
|
+
|
|
19
|
+
When a run unexpectedly returns zero results, check the resolved configs for empty comparison values first (`cai exec ports`).
|
|
20
|
+
|
|
21
|
+
## Required write loop
|
|
22
|
+
|
|
23
|
+
### 1. Wire first
|
|
24
|
+
|
|
25
|
+
An upstream output is available only after it is connected to the target node. If context lists it under `unwiredUpstreamOutputs`, connect it, then fetch context again.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cai node connect --flow <flowId> --from <sourceNodeId> --to <targetNodeId> --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use `cai branch connect` for Router or If/Else outputs.
|
|
32
|
+
|
|
33
|
+
### 2. Read the destination's exact scope
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cai expr context <nodeId> --prop <propName> --json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Copy binding and field names exactly. Important sources include:
|
|
40
|
+
|
|
41
|
+
- `flow.<name>`: flow input
|
|
42
|
+
- `inputs.<name>`: wired node input; the payload is usually already unwrapped
|
|
43
|
+
- `item`: injected current item, only where context exposes it
|
|
44
|
+
- `workflowData("custom.type")`: records from an active workflow collection
|
|
45
|
+
- `now`, `isLiveVersion`: constants shown by context
|
|
46
|
+
|
|
47
|
+
Do not derive a binding from a display label. Context flags duplicate display names and supplies the de-duplicated JS name, input ID, and source node.
|
|
48
|
+
|
|
49
|
+
### 3. Use only advertised syntax
|
|
50
|
+
|
|
51
|
+
Get the current operation catalog when needed:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cai expr ops --type <text|number|date|list|boolean|file|custom> --json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Keep the program to optional `const` declarations followed by one final expression. Do not use loops, assignments, `new`, regular expressions, optional chaining, arbitrary library methods, or object construction unless the current compiler catalog explicitly supports them.
|
|
58
|
+
|
|
59
|
+
Read [references/dsl-and-recipes.md](references/dsl-and-recipes.md) for canonical patterns.
|
|
60
|
+
|
|
61
|
+
### 4. Compile, write, and read back atomically
|
|
62
|
+
|
|
63
|
+
For a node prop, prefer:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cai expr set <nodeId> --prop <propName> --js '<expression>' --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For multiline expressions or shell-sensitive templates, put the exact source in a UTF-8 file and use `--js-file <path>`. Use `--js-file -` to read stdin. It is mutually exclusive with inline `--js` and is available on `expr validate/set`, `node set/only-when`, and `branch add/update`.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cai expr set <nodeId> --prop <propName> --js-file expression.js --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This fetches target metadata, compiles without writing on failure, chooses `valueJs` versus `expressionValueJs`, persists, and verifies read-back.
|
|
76
|
+
|
|
77
|
+
For a side-effect-free check:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cai expr validate <nodeId> --prop <propName> --js '<expression>' --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For conditions:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cai expr validate <nodeId> --mode onlyWhen --js '<boolean expression>' --json
|
|
87
|
+
cai node only-when <nodeId> --js '<boolean expression>' --json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Router and If/Else conditions are written with `cai branch add` or `cai branch update` after the same context/validation discipline.
|
|
91
|
+
|
|
92
|
+
### 5. Verify runtime resolution
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
cai expr decompile <nodeId> --prop <propName> --json
|
|
96
|
+
cai run node <nodeId> --inputs <values.json> --json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `run node` to isolate the destination's own configuration. It never executes wired upstream nodes. If the expression consumes `inputs.<name>`, also run its containing flow:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cai run flow <flowId> --inputs <values.json> --json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Inspect `runScope` and `resolvedConfigs` in the run result. A null or missing dynamic config is an expression failure, not success; a partially empty value is equally suspect. Only the full flow run proves an upstream-derived value. Use `cai exec ports <nodeExecutionId> --json` to compare inputs, resolved configs, and outputs.
|
|
106
|
+
|
|
107
|
+
## Hard rules
|
|
108
|
+
|
|
109
|
+
- Never guess a binding, field, method, or target type.
|
|
110
|
+
- Never author against an unwired output.
|
|
111
|
+
- Never add `.data` automatically. Use the exact path shown by context.
|
|
112
|
+
- Re-read context after rewiring, renaming inputs, changing schemas, changing dynamic selectors, or resolving an output type.
|
|
113
|
+
- Preserve `0` and `false`; they are values, not missing data.
|
|
114
|
+
- Optional filters need an explicit behavior decision. Do not silently change fail-closed filtering to match-all.
|
|
115
|
+
- A compiler success proves syntax and types, not correct source selection. Verify the source node/input IDs when names are ambiguous and inspect runtime values.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Expression DSL and recipes
|
|
2
|
+
|
|
3
|
+
The live compiler and `cai expr context`/`cai expr ops` are authoritative. These examples show structure, not names to copy blindly.
|
|
4
|
+
|
|
5
|
+
## Text template
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
`Hello ${flow.firstName.trim()}, order ${inputs.order.id} is ready`
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use the exact bindings and fields returned for the destination node.
|
|
12
|
+
|
|
13
|
+
## HTTP JSON body
|
|
14
|
+
|
|
15
|
+
An HTTP node's JSON/body prop is stored as JSON text. The expression language does not support object literals or `JSON.stringify`. Build the document as a template and insert complete JSON tokens:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
`{"message":${flow.name.jsonEncode()},"payload":${flow.user.stringify()}}`
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Use `.jsonEncode()` for text or boolean values. It returns a JSON-safe token, including the quotes around text, so do not add another pair of quotes around `${...}`.
|
|
22
|
+
- Use `.stringify()` for a custom object or list.
|
|
23
|
+
- Keep JSON punctuation outside the interpolation and validate against the destination's exact prop before writing.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cai expr validate <httpNodeId> --prop <jsonBodyProp> --js-file body-expression.js --json
|
|
27
|
+
cai expr set <httpNodeId> --prop <jsonBodyProp> --js-file body-expression.js --json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Same-type fallback
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
flow.count ?? 0
|
|
34
|
+
flow.label ?? "unknown"
|
|
35
|
+
flow.enabled ?? false
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Fallbacks must be same-type literals. `0` and `false` remain real values.
|
|
39
|
+
|
|
40
|
+
## Boolean condition
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
flow.email.isNotEmpty()
|
|
44
|
+
inputs.score >= 80
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Validate conditions with `--mode onlyWhen` and without a prop name.
|
|
48
|
+
|
|
49
|
+
## Dates
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
inputs.createdAt.toDate() > now.plusDays(-7)
|
|
53
|
+
inputs.createdAt.toDate().format("YYYY-MM-DD")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Convert text to a date before date math. Use negative amounts to move backward.
|
|
57
|
+
|
|
58
|
+
## Workflow records
|
|
59
|
+
|
|
60
|
+
Filter immediately after `workflowData` so the engine can push supported predicates into storage:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
workflowData("custom.lead")
|
|
64
|
+
.filter(row => row.status === "active")
|
|
65
|
+
.sortBy("created_at", "desc")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The collection must exist on the current dev/live rail. A schema declaration alone does not guarantee active records.
|
|
69
|
+
|
|
70
|
+
## Optional filter inputs
|
|
71
|
+
|
|
72
|
+
Filters are fail-closed by default: a missing right-hand value matches no records. Use optional matching only when the user's requirement explicitly allows an omitted constraint:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
workflowData("custom.lead").filter(
|
|
76
|
+
row => row.ownerId === flow.ownerId,
|
|
77
|
+
{ ignoreEmptyConstraints: true }
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Test both the supplied and omitted cases. Empty text may behave as a real value rather than a missing value; do not assume the opt-in drops it.
|
|
82
|
+
|
|
83
|
+
## Loop item
|
|
84
|
+
|
|
85
|
+
Where context exposes `item`, primitive lists use the item directly:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
item
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Object lists use an advertised field:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
item.email
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Inside the child flow, the item arrives through a flow input and is referenced as `flow.<name>`, not `item`.
|
|
98
|
+
|
|
99
|
+
## Loop output
|
|
100
|
+
|
|
101
|
+
Run-on-list output is a list of iteration records. Child flow outputs live under `outputs`:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
inputs.enrichEach[0].outputs.score
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Confirm the exact binding and field path in context.
|
|
108
|
+
|
|
109
|
+
## Troubleshooting sequence
|
|
110
|
+
|
|
111
|
+
1. `cai expr context <node> --prop <prop> --json`
|
|
112
|
+
2. Confirm the intended source is wired and its `sourceNodeId` is correct.
|
|
113
|
+
3. `cai expr validate ...`
|
|
114
|
+
4. `cai expr set ...`
|
|
115
|
+
5. `cai expr decompile ...`
|
|
116
|
+
6. `cai run node ... --json` for isolated destination configuration
|
|
117
|
+
7. `cai run flow <flowId> ... --json` when the expression consumes a wired upstream input
|
|
118
|
+
8. `cai exec ports <nodeExecutionId> --json`
|
|
119
|
+
|
|
120
|
+
If compilation succeeds but runtime is null or partially empty, inspect `runScope`, the actual input value, and `resolvedConfigs`; do not keep rewriting syntax blindly. A node-only run cannot prove an `inputs.<name>` binding because it does not execute upstream nodes.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-flow-control
|
|
3
|
+
description: Design and configure Controller AI execution paths using only-when rules, Router routes, If/Else branches, for-each loops, and subflows. Use when work must branch, fan out, skip, iterate over a list, call another flow, converge paths, or handle errors without changing the user's intended semantics.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Controller AI flow control
|
|
7
|
+
|
|
8
|
+
Choose the primitive from the intended execution semantics before adding nodes.
|
|
9
|
+
|
|
10
|
+
## Pick the correct primitive
|
|
11
|
+
|
|
12
|
+
| Intent | Primitive | Semantics |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Skip one action when a condition is false | Node `only-when` | No alternate path |
|
|
15
|
+
| Choose exactly one path | If/Else | First matching branch runs; Else is last |
|
|
16
|
+
| Run every matching path | Router | Conditions are independent; multiple routes may run |
|
|
17
|
+
| Apply work to every list item | `flow for-each` | Atomic loop scaffold with typed item flow |
|
|
18
|
+
| Reuse a typed unit of work once | Run flow/subflow node | Explicit input and output contract |
|
|
19
|
+
| Continue after an action error | Error output or continue-on-error | Error behavior is explicit and testable |
|
|
20
|
+
|
|
21
|
+
Do not substitute one primitive merely because its component is easier to find.
|
|
22
|
+
|
|
23
|
+
## Conditions
|
|
24
|
+
|
|
25
|
+
Before writing any condition:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cai expr context <controlNodeId> --json
|
|
29
|
+
cai expr validate <controlNodeId> --mode onlyWhen --js '<boolean expression>' --json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For one guarded action:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cai node only-when <nodeId> --js '<boolean expression>' --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use `--clear` only when the user's intent is to remove the guard.
|
|
39
|
+
|
|
40
|
+
## If/Else and Router
|
|
41
|
+
|
|
42
|
+
Discover and initialize the appropriate control component using `controllerai-node-config`, then inspect its exact branch IDs:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cai branch list <controlNodeId> --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A fresh If/Else already contains one blank `If` branch and an Else branch. The blank condition is a publish/runtime blocker: copy that default branch ID from `branch list` and update it before adding another condition branch. Do not leave the default blank and create a replacement beside it.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cai branch update <ifElseNodeId> \
|
|
52
|
+
--route <defaultIfBranchId> \
|
|
53
|
+
--name "<meaning>" \
|
|
54
|
+
--js '<boolean expression>' \
|
|
55
|
+
--json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Add another condition only when the decision needs another non-Else case. Router routes are independent, so update an existing blank route or add each intended route explicitly:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cai branch add <controlNodeId> --name "<meaning>" --js '<boolean expression>' --json
|
|
62
|
+
cai branch update <controlNodeId> --route <routeOrBranchId> --js '<boolean expression>' --json
|
|
63
|
+
cai branch connect <controlNodeId> --route <routeOrBranchId> --to <targetNodeId> --flow <flowId> --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For If/Else, order is behavior:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cai branch reorder <ifElseNodeId> --order <conditionId1,conditionId2> --json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
List every condition branch exactly once and omit Else; the CLI keeps Else last. Router routes cannot be reordered because every match runs.
|
|
73
|
+
|
|
74
|
+
After edits, run `cai branch list` again. Treat its warnings about missing conditions or unwired paths as blockers: an unwired selected path can end with an apparently green execution.
|
|
75
|
+
|
|
76
|
+
## Loops
|
|
77
|
+
|
|
78
|
+
Never assemble the standard per-item subflow pattern by hand:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cai node get <listProducingNodeId> --json
|
|
82
|
+
cai flow for-each \
|
|
83
|
+
--flow <parentFlowId> \
|
|
84
|
+
--from <listProducingNodeId> \
|
|
85
|
+
--source-output <exactOutputId> \
|
|
86
|
+
--name "<item flow name>" \
|
|
87
|
+
--item-type <text|number|date|boolean|custom.type> \
|
|
88
|
+
--json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use repeatable `--item-field <id:type[:display]>` only when creating a new `custom.*` item type.
|
|
92
|
+
|
|
93
|
+
Copy `--source-output` from the selected node's exact `outputs[].id`. It is an opaque ID, not `displayName`, and there is no first-output default. This matters for Router, If/Else, and error outputs as well as ordinary action nodes.
|
|
94
|
+
|
|
95
|
+
- If the source's whole output is the list, omit both `--list-field` and `--list-js`.
|
|
96
|
+
- If the list is nested in the source payload, use `--list-field` with the dot-delimited path below `.data`; the CLI derives the not-yet-created loop input binding deterministically.
|
|
97
|
+
- Use `--list-js` only for an advanced exact expression. For multiline or shell-sensitive source, use `--js-file <path|->` instead. Both are mutually exclusive with `--list-field` and with each other; copy the wrapper/binding shape from expression context rather than guessing it.
|
|
98
|
+
- The runtime cap defaults to 500 items and accepts 1 through 10000. Set `--max-items` explicitly when the product requirement differs. The CLI creates the core scaffold first, then materializes and verifies this optional prop. The cap is all-or-nothing at runtime: a list larger than the cap throws and NOTHING iterates — there is no partial truncation.
|
|
99
|
+
|
|
100
|
+
Read every returned warning. A list/item type mismatch means the scaffold is not verified even though it was created. Follow the returned `nextSteps` in order.
|
|
101
|
+
|
|
102
|
+
Inside loop mappings, `item` exists only where context exposes it. Inside the body flow, use its `flow.<itemInput>` binding. Downstream loop results are iteration records whose declared child outputs are under `.outputs`.
|
|
103
|
+
|
|
104
|
+
Loops run sequentially. `--continue-on-error` applies to a failed child iteration; a list expression or mapping/config expression that fails before the child starts still aborts the loop. Test empty, one-item, several-item, over-limit, and intended iteration-error behavior.
|
|
105
|
+
|
|
106
|
+
## Subflows and convergence
|
|
107
|
+
|
|
108
|
+
Define the called flow's inputs and outputs first. Then initialize a Run flow component and bind it:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cai node set-flow <runFlowNodeId> --flow <calledFlowId> --json
|
|
112
|
+
cai node props <runFlowNodeId> --json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Setting the target flow creates mapping props from the target's current inputs. If those inputs later change, run `set-flow` again and reconfigure mappings.
|
|
116
|
+
|
|
117
|
+
When paths converge, inspect the concrete graph and execution behavior. Do not assume wiring two paths to one action creates a barrier or waits for both. If the requirement is a true join, discover a join-capable component and verify it with representative branch timing.
|
|
118
|
+
|
|
119
|
+
Return semantics: a flow that should return data needs a Return response node — without one it returns nothing, and callers (including agents invoking it as a tool) see silent success with empty output. If several Return response nodes complete in one execution, their result objects merge in execution order and a later node silently overwrites an earlier value for the same field. Design for exactly one Return per path unless merging is the intent.
|
|
120
|
+
|
|
121
|
+
Deleting a flow is a normal, undoable edit: it removes the flow's nodes, edges, inputs/outputs, and attached triggers, and unlinks run-subflow callers in other flows — the same way deleting a function breaks its call sites. No confirmation ceremony is needed; run `cai workflow validate` afterwards to see every break, fix or remove the callers it lists, and `cai workflow restore` if the deletion was a mistake. Turning `continueOnError` off is the same class: it deletes the node's Error output and every edge from it — validate after, restore if regretted.
|
|
122
|
+
|
|
123
|
+
Read [references/patterns.md](references/patterns.md) for concise canonical patterns.
|
|
124
|
+
|
|
125
|
+
## Completion gate
|
|
126
|
+
|
|
127
|
+
- Every branch has the intended condition and a wired target.
|
|
128
|
+
- If/Else ordering matches priority and Else remains last.
|
|
129
|
+
- Router fan-out is intentional when more than one condition can be true.
|
|
130
|
+
- Loop item type and list expression compile against actual source data.
|
|
131
|
+
- Expected true, false/Else, empty-list, one-item, several-item, over-limit, and error paths are tested.
|
|
132
|
+
- `cai exec tree <workflowExecutionId>` contains no unexplained skipped or missing nodes.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Flow-control patterns
|
|
2
|
+
|
|
3
|
+
## Guard a side effect
|
|
4
|
+
|
|
5
|
+
Use `only-when` when false should mean "skip this action" and nothing else:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Read record -> Send notification [only when status changed]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Test both true and false. A false run should show an intentional skip reason.
|
|
12
|
+
|
|
13
|
+
## Exclusive decision
|
|
14
|
+
|
|
15
|
+
Use If/Else when only one outcome must occur:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Score -> If/Else
|
|
19
|
+
high priority -> Escalate
|
|
20
|
+
normal -> Queue
|
|
21
|
+
Else -> Record unknown
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Put the most specific/highest-priority conditions first. Never write a complement condition for Else.
|
|
25
|
+
|
|
26
|
+
## Independent fan-out
|
|
27
|
+
|
|
28
|
+
Use Router when every applicable action should run:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
New order -> Router
|
|
32
|
+
international -> Customs review
|
|
33
|
+
high value -> Fraud review
|
|
34
|
+
gift -> Gift note
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
One order may take all three routes. If that is not acceptable, use If/Else.
|
|
38
|
+
|
|
39
|
+
## Per-item subflow
|
|
40
|
+
|
|
41
|
+
Use `cai flow for-each` to create the item flow, typed item input, loop node, and list wiring atomically. First run `cai node get <sourceNodeId> --json`, choose the intended `outputs[]` entry, and pass its exact opaque `id` as `--source-output`; never pass its label or assume the first output. Omit list-selection flags when the whole selected output payload is a list; use `--list-field <nested.path>` for a list below `.data`; reserve `--list-js` for an exact advanced expression or `--js-file <path|->` for that expression in a UTF-8 file/stdin. The three selection forms are mutually exclusive.
|
|
42
|
+
|
|
43
|
+
The runtime limit defaults to 500 items and supports 1 through 10000. Use `--max-items` when the intended bound differs, then verify the returned read-back. Put meaningful work inside the generated body flow and declare body outputs before consuming aggregate results.
|
|
44
|
+
|
|
45
|
+
Decide explicitly whether one child-flow failure should stop the sequential loop. Use `--continue-on-error` only when partial completion is acceptable and downstream logic handles failed iteration records. It does not recover a list or mapping expression that fails before an iteration starts.
|
|
46
|
+
|
|
47
|
+
## Error path
|
|
48
|
+
|
|
49
|
+
Continue-on-error is for steps whose failure must not block the main path — notifications, logging, optional enrichment. Enabling it captures the node's error, downstream nodes still execute (receiving null for the failed node's output), and the run ends `completed_with_error`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cai node continue-on-error <nodeId> --enable --json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Wiring the Error output is OPTIONAL — add it when a failure needs explicit handling, not as a precondition:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cai node connect --flow <flowId> --from <nodeId> --to <handlerNodeId> --output error --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Never enable continue-on-error on critical-path steps (validate → charge → confirm must fail fast), and never merely to make the parent execution green. Read back the setting after changing it.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: controllerai-integration-actions
|
|
3
|
+
description: Discover, connect, attach, update, and verify ready integration actions used directly by Controller AI agents. Use when an agent needs one existing app operation without a workflow, when choosing between a direct action and a workflow tool, or when changing an action's connection or per-run approval policy.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Controller AI integration actions
|
|
7
|
+
|
|
8
|
+
Ready integration actions give a hosted agent one app operation without first building a workflow. Use only the `cai` commands below; do not call Controller AI REST or MCP routes directly.
|
|
9
|
+
|
|
10
|
+
## Choose direct action or workflow
|
|
11
|
+
|
|
12
|
+
Use a direct action for one existing operation such as sending one message, creating one record, or adding one row.
|
|
13
|
+
|
|
14
|
+
Use a workflow tool instead when the capability needs multiple operations, transformations, branches, loops, durable state, triggers, a custom typed contract, or reuse outside the agent. Do not build a one-node workflow when the same ready action already fits.
|
|
15
|
+
|
|
16
|
+
## Discover exact identities
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cai integration search "<app or capability>" --json
|
|
20
|
+
cai integration actions <integrationSlug> --search "<operation>" --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Preserve the returned integration `slug` and action `key` exactly. Never derive either from a display label. Continue with `--after <nextCursor>` when pagination reports more results.
|
|
24
|
+
|
|
25
|
+
## Select a connection
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cai connection list --integration <integrationSlug> --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use an active connection whose `nodeSlug` exactly matches the integration. If none exists:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cai connection connect <integrationSlug>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The browser handoff keeps provider credentials out of the agent context. Re-list connections after the user finishes.
|
|
38
|
+
|
|
39
|
+
## Decide approval before attaching
|
|
40
|
+
|
|
41
|
+
The product default is to run without per-call approval. Do not accept that default mechanically. Inspect the action name, description, pricing, and intended use, then decide:
|
|
42
|
+
|
|
43
|
+
- Require confirmation for deletion, purchases or spend, external communications, permission changes, irreversible writes, or an ambiguous destination.
|
|
44
|
+
- Usually omit confirmation for read-only retrieval and low-risk, reversible operations the user clearly asked the agent to perform autonomously.
|
|
45
|
+
|
|
46
|
+
Tell the user which policy was selected and why when it has a meaningful consequence.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cai agent add-action <agentId> \
|
|
50
|
+
--integration <integrationSlug> \
|
|
51
|
+
--action <actionKey> \
|
|
52
|
+
--connection <connectionId> \
|
|
53
|
+
--description "Use when ..." \
|
|
54
|
+
--require-confirmation \
|
|
55
|
+
--json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Omit `--require-confirmation` only after making the decision; the CLI still sends an explicit `false`. Do not attach the same action twice.
|
|
59
|
+
|
|
60
|
+
Read the saved grant back:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cai agent tools <agentId> --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Verify `toolType`, integration, action key, connection, schema tier, description, and confirmation policy. The hosted runtime configures dynamic action properties when it invokes the action; the builder does not preconfigure runtime-only action arguments here.
|
|
67
|
+
|
|
68
|
+
To rebind the live authorization grant or change a versioned policy:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cai agent update-tool <agentId> --tool <toolId> --connection <connectionId> --json
|
|
72
|
+
cai agent update-tool <agentId> --tool <toolId> --require-confirmation --json
|
|
73
|
+
cai agent update-tool <agentId> --tool <toolId> --no-confirmation --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`--connection` can redirect the published action immediately because authorization is live-bound. Description and confirmation-policy edits remain draft changes until the agent is published.
|
|
77
|
+
|
|
78
|
+
## Verify through the hosted agent
|
|
79
|
+
|
|
80
|
+
Use a representative draft conversation:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cai agent test <agentId> --draft --message "<request>" --json
|
|
84
|
+
cai agent conversation history <conversationId> --json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
A direct action runs inline and does not create a workflow execution ID. Evidence is the persisted conversation, its direct-action tool/run events, the returned result, and any observed external side effect. Never invent a workflow execution ID for it.
|
|
88
|
+
|
|
89
|
+
If confirmation is required, inspect the pending action and exact arguments before approving or denying its request. A successful assistant reply alone does not prove the intended action ran.
|
|
90
|
+
|
|
91
|
+
## Publishing and sharing semantics
|
|
92
|
+
|
|
93
|
+
Publishing snapshots the action identity, description, and approval policy into the agent version. The connection and authorization grant remain live security bindings: rebinding can redirect the published action, while breakage, revocation, or detachment can stop it immediately without another agent publish. Detachment also removes the draft tool; publish afterward to remove the unavailable tool from the published list.
|
|
94
|
+
|
|
95
|
+
Organization members use the published agent, never its mutable draft. Direct actions run through the agent owner's connected external account. Controller AI usage draws from the organization's shared usage pool, regardless of which member calls the agent. State that credential boundary before organization sharing.
|