Skip to content

Commit 0126731

Browse files
authored
fix(uipath-maestro-flow): teach =js: prefix on node wiring (#450)
* fix(uipath-maestro-flow): teach =js: prefix on connector node wiring [MST-9107] Coding Agents (Devon) consistently produce one of three wrong forms when wiring a connector activity output into a downstream activity input: 1. "nodes.X.output.Id" (invented prefix; literal string at runtime) 2. "$vars.X.output.Id" (no =js:; ships as literal "vars.X.output.Id") 3. "=js:$vars.X.output.Id" (correct) The flow-workbench serializer rewrites $vars→vars regardless of the =js: prefix, so a missing prefix fails silently — flow validate passes; the literal-string failure surfaces only at flow debug. There is currently no ensureJsPrefix fallback for connector bodyParameters/queryParameters (only variableUpdates have one). Per the Slack consensus (Aditi/Baishali/Ashish/Rocky on the parent thread), this is a Maestro Flow skill concern — not a per-connector skill concern — so the fix belongs at the uipath-maestro-flow level rather than inside individual connector skills. Changes: - references/node-output-wiring.md (new): canonical single-source-of-truth doc for the =js: rule. Lists the 3 failure modes, the canonical pattern, per-node-type field reference (connector / HTTP / decision / switch / end / variable update / loop / subflow / script), and anti-patterns. - SKILL.md: tighten Critical Rule #13 to enumerate connector bodyParameters/queryParameters/pathParameters explicitly. Add an Anti-Pattern entry. Add Task Navigation and References links. - references/plugins/connector/impl.md: new "Step 5b — Wire outputs from previous nodes" section with the canonical examples for connector inputs.detail. - references/variables-and-expressions.md: promote the buried IS-activity note into a clear "IS Activity Inputs Require =js:" section with a wrong/right comparison table. * docs: clarify Managed HTTP v2 field path in wiring table Address PR #450 review (github-actions[bot]): the table row for core.action.http.v2 listed `path` alongside `url`, but `path` is a connector-mode auto-copy of `url` (cli/packages/flow-tool/.../connector-service.ts:544) and agents do not set it independently — drop it from the listing. Also pre-empt CLI-vs-JSON confusion: the CLI's `--detail '{"url": "..."}'` flag accepts the field at the top level, but the resulting `.flow` JSON stores it under `inputs.detail.bodyParameters.url` (same code path, lines 529-555) for both manual and connector authentication modes. The table now spells this out so agents writing JSON directly hit the right field path.
1 parent 13b3d45 commit 0126731

4 files changed

Lines changed: 175 additions & 1 deletion

File tree

skills/uipath-maestro-flow/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Comprehensive guide for creating, editing, validating, and debugging UiPath Flow
3838
10. **Validate once at the end** — run `uip maestro flow validate` only after all nodes, edges, and configuration are complete (Step 5). Do not validate after each individual node add or edit — intermediate states are expected to be invalid.
3939
11. **Manage variables by editing `.flow` JSON directly** — there are no CLI commands for variable management. Add/remove/update variables in the `variables` section of the `.flow` file. See [references/variables-and-expressions.md](references/variables-and-expressions.md).
4040
12. **Every `out` variable must be mapped on every reachable End node** — missing output mappings cause runtime errors. See [references/variables-and-expressions.md](references/variables-and-expressions.md).
41-
13. **`=js:` prefix rules** — Use `=js:` on value expressions (end node output `source`, variable updates, HTTP input fields). Do NOT use `=js:` on condition expressions (decision `expression`, switch case `expression`, HTTP branch `conditionExpression`) — those are always evaluated as JS automatically. See [references/variables-and-expressions.md](references/variables-and-expressions.md).
41+
13. **`=js:` prefix is REQUIRED on every `$vars`/`$metadata`/`$self` reference in a value field.** That includes connector node `inputs.detail.bodyParameters` / `queryParameters` / `pathParameters`, HTTP `url`/`headers`/`body`, end node output `source`, variable update `expression`, loop `collection`, and subflow `inputs.<id>.source`. Without `=js:`, the BPMN runtime sees a literal string (e.g. `"vars.X.output.Id"`) — `flow validate` does not catch this; it manifests at runtime as the wrong value bound to the activity input (MST-9107). Do NOT use `=js:` on condition expressions (decision `expression`, switch case `expression`, HTTP branch `conditionExpression`) — those are always evaluated as JS automatically. See [references/node-output-wiring.md](references/node-output-wiring.md) for the canonical rule and per-node-type field reference, and [references/variables-and-expressions.md](references/variables-and-expressions.md) for the underlying expression system.
4242
14. **Resource discovery order — search before creating.** When the prompt references an existing resource by name ("use the X agent", "call the Y API workflow", "invoke the Z RPA process"), follow this order strictly before deciding the resource doesn't exist:
4343
1. **Tenant registry search**`uip maestro flow registry search "<name>" --output json`. Requires `uip login`; returns published resources.
4444
2. **In-solution local discovery**`uip maestro flow registry list --local --output json`. No login required; returns sibling projects in the same `.uipx` solution.
@@ -335,6 +335,7 @@ When the build completes, present the next-step dropdown described in the [Compl
335335
- **Never reference parent-scope `$vars` inside a subflow** — subflows have isolated scope. Pass values explicitly via subflow inputs.
336336
- **Never use `core.action.http` (v1) for connector-authenticated requests** — the v1 node's `authenticationType: "connection"` input does not pass IS credentials at runtime. Use `core.action.http.v2` (Managed HTTP Request) instead. See [http/planning.md](references/plugins/http/planning.md).
337337
- **Never hand-write `inputs.detail` for managed HTTP nodes** — run `uip maestro flow node configure` to populate the `inputs.detail` structure, generate `bindings_v2.json`, and create the connection resource file. Hand-written configurations miss the `essentialConfiguration` block and fail at runtime.
338+
- **Never write `$vars.X` (or `$metadata.X`, `$self.X`) without `=js:`** in any connector `bodyParameters`/`queryParameters`/`pathParameters`, HTTP input field, end-node output `source`, variable update, loop collection, or subflow input. The serializer rewrites `$vars``vars` whether or not the prefix is present, so a missing prefix yields a literal string `"vars.X.output.Y"` at runtime — `flow validate` passes, the failure shows up only in `flow debug`. There is no `nodes.X.output.Y` syntax — it is invented and silently produces a literal string. See [references/node-output-wiring.md](references/node-output-wiring.md) for the per-node-type field reference (MST-9107).
338339
- **Never reuse a reference ID (mailbox folder, Slack channel, Jira project, Google Sheet, etc.) from a prior flow or session** — reference IDs are scoped to the specific authenticated account behind the connection. A `parentFolderId` from one Outlook mailbox is invalid in another; a Slack channel ID from one workspace is invalid in another. A reused ID passes `flow validate` and `node configure` cleanly, then faults silently at runtime with no resolvable error. Always re-resolve via `uip is resources execute list <connector-key> <objectName> --connection-id <CURRENT_CONNECTION_ID> --output json` against the connection bound to this flow — do not paste a value you saw in another flow. See [connector/impl.md — Step 4](references/plugins/connector/impl.md) and [connector-trigger/impl.md — Step 3](references/plugins/connector-trigger/impl.md).
339340

340341
## Task Navigation
@@ -355,6 +356,7 @@ When the build completes, present the next-step dropdown described in the [Compl
355356
| **Deploy to Orchestrator** (only if explicitly requested) | [references/flow-commands.md](references/flow-commands.md) + [/uipath:uipath-platform](/uipath:uipath-platform) |
356357
| **Manage variables and expressions** | [references/variables-and-expressions.md](references/variables-and-expressions.md) + [JSON: Variable Operations](references/flow-editing-operations-json.md#variable-operations) |
357358
| **Write `=js:` expressions** | [references/variables-and-expressions.md — Expression System](references/variables-and-expressions.md) |
359+
| **Wire one node's output into another node's input** | [references/node-output-wiring.md](references/node-output-wiring.md) — canonical `=js:$vars.<sourceNodeId>.output.<field>` pattern; per-node-type field reference; covers connector `bodyParameters`/`queryParameters`, HTTP, end-node `source`, variable updates |
358360
| **Orchestrate RPA, agents, apps** | Relevant resource plugin: [rpa](references/plugins/rpa/), [agent](references/plugins/agent/), [agentic-process](references/plugins/agentic-process/), [flow](references/plugins/flow/), [api-workflow](references/plugins/api-workflow/), [hitl](references/plugins/hitl/) |
359361
| **Embed an AI agent tightly coupled to this flow** | [references/plugins/inline-agent/](references/plugins/inline-agent/) — scaffolded via `uip agent init --inline-in-flow`, node type `uipath.agent.autonomous` |
360362
| **Create a resource that doesn't exist yet** | Use `core.logic.mock` placeholder — see [CLI: Replace a mock](references/flow-editing-operations-cli.md#replace-a-mock-with-a-real-resource-node) + relevant plugin's `impl.md` |
@@ -417,6 +419,7 @@ When you finish building or editing a flow, report to the user:
417419
- **[CLI Command Reference](references/flow-commands.md)** — All `uip flow` subcommands with flags and options
418420
- **[Troubleshooting Guide](references/troubleshooting-guide.md)** — Diagnostic workflow for failed flows: incidents, runtime variables, definition correlation, traces, and `instance`/`incident` CLI reference
419421
- **[Variables and Expressions](references/variables-and-expressions.md)** — Variable declaration (in/out/inout), type system, `=js:` Jint expressions, template syntax, scoping rules, output mapping, and variable updates
422+
- **[Node Output Wiring](references/node-output-wiring.md)** — The single rule for wiring one node's output as another node's input. Per-node-type table of where `=js:` is required vs forbidden. Diagnoses the `vars.X.output.Y` literal-string failure mode (MST-9107).
420423
- **[Node Plugins](references/plugins/)** — Each node type has its own plugin folder with `planning.md` (selection heuristics, ports, key inputs) and `impl.md` (registry validation, JSON structure, configuration, debug):
421424
- [connector](references/plugins/connector/) — IS connector nodes: connection binding, enriched metadata, reference resolution, `bindings_v2.json`
422425
- [script](references/plugins/script/) — Custom JavaScript logic via Jint ES2020
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Wiring Node Outputs to Downstream Inputs
2+
3+
How to reference one node's output (or any `$vars.*` value) from another node's input fields. This is the **single source of truth** for `=js:` expression rules in `.flow` JSON. All node-type plugins (connector, http, script, decision, end, etc.) defer to this file.
4+
5+
> **Read this before** writing any field that needs to reference a previous node's output, a workflow variable, or any other `$vars.*` / `$metadata.*` value. Skipping `=js:` is the #1 cause of "the value at runtime was the literal string `vars.X.output.Id`" failures (MST-9107).
6+
7+
---
8+
9+
## The Rule (one sentence)
10+
11+
**Any string that references `$vars.*`, `$metadata.*`, or `$self.*` MUST start with `=js:`. Otherwise it is a literal string at runtime.**
12+
13+
---
14+
15+
## What Goes Wrong Without `=js:`
16+
17+
The Flow runtime serializes string fields into BPMN. The serializer rewrites the canvas-form tokens (`$vars``vars`) **whether or not the `=js:` prefix is present**. This means a missing prefix is silent — `flow validate` passes, the file looks plausible, but at runtime the BPMN engine sees a plain string and never evaluates it.
18+
19+
Three failure modes observed in agent-generated `.flow` files:
20+
21+
| What the agent wrote | What ships to runtime | Result |
22+
|---|---|---|
23+
| `"nodes.createEntityRecord1.output.Id"` | `"nodes.createEntityRecord1.output.Id"` | Literal string — invented `nodes.` prefix has no meaning. |
24+
| `"$vars.createEntityRecord1.output.Id"` | `"vars.createEntityRecord1.output.Id"` | Literal string — `$vars` rewritten to `vars` but no `=js:` so never evaluated. |
25+
| `"=js:$vars.createEntityRecord1.output.Id"` | `"=js:vars.createEntityRecord1.output.Id"` | Evaluates correctly. |
26+
27+
**There is no "nodes.X.output.Y" syntax.** Variable references always use `$vars.*`.
28+
29+
---
30+
31+
## The Canonical Pattern
32+
33+
```
34+
"=js:$vars.<sourceNodeId>.output.<field>"
35+
```
36+
37+
- `<sourceNodeId>` — the `id` value of the node that produces the output
38+
- `output` — the literal string `output` (the standard output port; use `error` for errors)
39+
- `<field>` — the field path within the output object
40+
41+
### Examples
42+
43+
| What you want | Expression |
44+
|---|---|
45+
| The whole output object | `=js:$vars.fetchUser1.output` |
46+
| One field from a single-record output | `=js:$vars.createEntityRecord1.output.Id` |
47+
| A field from the first record of a query result (array) | `=js:$vars.queryEntityRecords1.output[0].Id` |
48+
| Filter on a value from a previous node | `=js:$vars.fetchUser1.output.id === $vars.input.userId` |
49+
| A static value (no variables) | `"HDFC Bank"` (no prefix needed) |
50+
| A built-in metadata value | `=js:$metadata.instanceId` |
51+
| A literal that contains a variable | `` =js:`Hello ${$vars.userName}` `` |
52+
53+
---
54+
55+
## Where the Rule Applies
56+
57+
`=js:` is **required** in every field below when the value references `$vars`, `$metadata`, or `$self`:
58+
59+
| Node / context | Field | Required `=js:`? |
60+
|---|---|---|
61+
| **Connector activity nodes** (`uipath.connector.<connector-key>.<activity>`) | `inputs.detail.bodyParameters.*` (all values) | **YES** |
62+
| | `inputs.detail.queryParameters.*` (all values) | **YES** |
63+
| | `inputs.detail.pathParameters.*` (all values) | **YES** |
64+
| **Managed HTTP** (`core.action.http.v2`) | `inputs.detail.bodyParameters.url` / `headers` / `query` / `body` — both manual and connector mode store dynamic fields here in the `.flow` JSON, regardless of how the CLI's `--detail` flag accepts them at the top level | **YES** |
65+
| **Custom HTTP** (`core.action.http`) | `inputs.url` / `headers` / `body` / `queryParams` (deprecated; prefer `core.action.http.v2`) | **YES** |
66+
| **HTTP branches** | `inputs.branches[].conditionExpression` | **NO** — already JS, do not prefix |
67+
| **Decision** (`core.logic.decision`) | `inputs.expression` | **NO** — already JS, do not prefix |
68+
| **Switch** (`core.logic.switch`) | `inputs.cases[].expression` | **NO** — already JS, do not prefix |
69+
| **End nodes** (`core.control.end`) | `outputs.<varId>.source` | **YES** |
70+
| **Variable updates** | `variables.variableUpdates.<nodeId>[].expression` | **YES** (the CLI auto-prefixes if missing, but write it explicitly) |
71+
| **Loop nodes** (`core.logic.loop`) | `inputs.collection` | **YES** |
72+
| **Subflow nodes** (`core.subflow`) | `inputs.<inputId>.source` | **YES** |
73+
| **Script nodes** (`core.action.script`) | `inputs.script` body — `$vars.*` is read inside JS, no `=js:` wrapping | **NO** — the body is already JS |
74+
| **Native flow text fields** (agent prompt, sticky note) | Any string with `{$vars.X}` template interpolation | **NO** — uses `{ }` template, not `=js:` |
75+
76+
**Rule of thumb:** If the field is *value-typed* (anything other than a hardcoded condition), `=js:` is required for `$vars`/`$metadata`/`$self` references. The two condition fields (Decision, Switch) and the script body are the only exceptions — they are always parsed as JS regardless.
77+
78+
---
79+
80+
## Connector Node Wiring — Quick Reference
81+
82+
For connector nodes (`uipath.connector.*`), the most common wiring patterns:
83+
84+
```jsonc
85+
"inputs": {
86+
"detail": {
87+
"method": "POST",
88+
"endpoint": "/v2/{entityName}/UpdateEntityRecord",
89+
"pathParameters": {
90+
"entityName": "BankDetails"
91+
},
92+
"queryParameters": {
93+
"recordId": "=js:$vars.createEntityRecord1.output.Id",
94+
"expansionLevel": "3"
95+
},
96+
"bodyParameters": {
97+
"BankName": "HDFC Bank",
98+
"AccountId": "=js:$vars.queryAccounts1.output[0].Id",
99+
"Notes": "=js:`Created from flow run ${$metadata.instanceId}`"
100+
}
101+
}
102+
}
103+
```
104+
105+
- **Static values** (`"HDFC Bank"`, `"3"`) — no prefix, written as plain JSON values.
106+
- **Variable references** (`$vars.X.output.Y`) — **always** wrap with `=js:`.
107+
- **Mixed strings** (template literals) — wrap the whole expression in `=js:` and use JS template literal syntax with `${ }`.
108+
109+
---
110+
111+
## Anti-Patterns (Never Write These)
112+
113+
1. **Never invent a `nodes.X.output.Y` syntax.** It does not exist. All variable references use `$vars`.
114+
2. **Never write `$vars.X.output.Y` without `=js:`** in any value field. The `$vars→vars` rewrite happens regardless of prefix, leaving you with a literal string `"vars.X.output.Y"` at runtime — looks like an expression, isn't one.
115+
3. **Never wrap conditions** (Decision, Switch, HTTP branch) in `=js:`. Those are parsed as JS automatically.
116+
4. **Never use `{ }` template interpolation in connector or HTTP activity inputs.** The flow-layer template runner skips these fields. The `$` is stripped and `{vars.X}` ships literally to the IS runtime. Use `=js:` and JS template literals (`` `…${$vars.X}…` ``) instead.
117+
5. **Never quote `=js:` itself in an expression.** `"=js:$vars.X"` is correct. `"\"=js:$vars.X\""` is a string containing the prefix.
118+
119+
---
120+
121+
## Why The Engine Works This Way
122+
123+
The flow-workbench serializer (`expression-transform.ts`) walks every string in node data and rewrites `$vars``vars` so the BPMN runtime sees engine-form tokens. It does **not** check for the `=js:` prefix when doing this rewrite — the prefix is what tells the BPMN engine to actually evaluate the result as JavaScript. Without it, the BPMN engine treats the string as a literal value and binds it to the activity input as-is.
124+
125+
For variable updates only, `bpmn-moddle.ts` has a fallback (`ensureJsPrefix`) that warns and auto-prefixes. There is **no equivalent fallback for connector/HTTP activity inputs** — values pass through verbatim. Always include `=js:` explicitly.
126+
127+
---
128+
129+
## Validation Tip
130+
131+
When debugging a flow whose output is the literal string `vars.X.output.Y` (or `nodes.X.output.Y`, or any other unevaluated expression) instead of the expected value:
132+
133+
1. Open the `.flow` file
134+
2. Search for the literal token in the failed field — `grep '"vars\.' <project>.flow` or `grep '"\$vars\.' <project>.flow`
135+
3. For each match in `bodyParameters`, `queryParameters`, `pathParameters`, end-node `source`, or any value field, prepend `=js:`
136+
4. Re-run `uip maestro flow validate` and re-debug

skills/uipath-maestro-flow/references/plugins/connector/impl.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,29 @@ The `<id>` in `--connection-id "<id>"` MUST be the connection bound to **this**
106106

107107
> **Do NOT guess or skip missing required fields.** A missing required field will cause a runtime error. It is always better to ask than to assume.
108108
109+
### Step 5b — Wire outputs from previous nodes
110+
111+
When a connector node's input field needs a value produced by an upstream node (e.g. the `Id` returned by a Create activity becomes the `recordId` for a Get-by-Id activity), the value MUST use the canonical expression form:
112+
113+
```
114+
"=js:$vars.<sourceNodeId>.output.<field>"
115+
```
116+
117+
Examples in `inputs.detail`:
118+
119+
```jsonc
120+
"queryParameters": {
121+
"recordId": "=js:$vars.createEntityRecord1.output.Id"
122+
},
123+
"bodyParameters": {
124+
"ParentId": "=js:$vars.queryAccounts1.output[0].Id",
125+
"BankName": "HDFC Bank",
126+
"Note": "=js:`Linked from run ${$metadata.instanceId}`"
127+
}
128+
```
129+
130+
> **The `=js:` prefix is REQUIRED on every `$vars`/`$metadata`/`$self` reference inside `bodyParameters`, `queryParameters`, and `pathParameters`.** Without it the BPMN runtime sees a literal string (`"vars.createEntityRecord1.output.Id"`) and binds it as-is to the activity input — `flow validate` passes; the failure surfaces only at `flow debug`. There is no `nodes.X.output.Y` syntax — that is an invention that silently ships as a literal string. See [node-output-wiring.md](../../node-output-wiring.md) for the per-field-type rule and the full failure-mode table (MST-9107).
131+
109132
### Step 6 — Configure the node
110133

111134
**Run `is resources describe` (Step 3) before this step.** The full metadata tells you which fields are required, what types they expect, and which need reference resolution. Do not guess field names or skip the metadata check — required fields missing from `--detail` cause runtime errors that `flow validate` does not catch.

skills/uipath-maestro-flow/references/variables-and-expressions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,18 @@ Order {$vars.orderId} is {$vars.status} — total: {$vars.amount}
317317

318318
> **Brace-templates do NOT work in Integration Service activity inputs.** The flow-layer template runner only processes native flow fields (decision expressions, variable updates, end-node output `source`, script bodies, agent prompt text). Fields inside `inputs.detail.bodyParameters` on `core.action.http.v2` or `uipath.connector.*` activity nodes — `url`, `headers`, `body`, `query` — are passed through to the IS runtime unchanged, so `{$vars.article}` ships literally. Observed behavior: the `$` is stripped and the braces survive (`user/{vars.article}` reaches the service). **For any dynamic value in an IS activity input, use `=js:` instead** — e.g., `` "url": "=js:`https://.../user/${$vars.article}`" `` or `"headers": { "Authorization": "=js:'Bearer ' + $vars.token" }`.
319319
320+
### IS Activity Inputs Require `=js:` (Critical)
321+
322+
Every `$vars` / `$metadata` / `$self` reference inside `inputs.detail.bodyParameters`, `inputs.detail.queryParameters`, or `inputs.detail.pathParameters` on a connector or HTTP activity node MUST be wrapped with `=js:`. Without it the value ships as a literal string at runtime.
323+
324+
| Wrong | Right |
325+
|---|---|
326+
| `"recordId": "$vars.createEntityRecord1.output.Id"` | `"recordId": "=js:$vars.createEntityRecord1.output.Id"` |
327+
| `"recordId": "nodes.createEntityRecord1.output.Id"` | `"recordId": "=js:$vars.createEntityRecord1.output.Id"` |
328+
| `"recordId": "{vars.createEntityRecord1.output.Id}"` | `"recordId": "=js:$vars.createEntityRecord1.output.Id"` |
329+
330+
The serializer rewrites `$vars``vars` whether or not `=js:` is present, so a missing prefix yields a confusing failure: the runtime field is bound to the literal string `"vars.X.output.Id"` (which looks like an unevaluated expression). `flow validate` does not catch this — it only manifests at `flow debug`. See [node-output-wiring.md](node-output-wiring.md) for the full per-node-type field reference (MST-9107).
331+
320332
### Comparison
321333

322334
| Feature | `=js:` | `{ }` template |

0 commit comments

Comments
 (0)