From 08e5fad67e77c8a49e56cf084366355251959449 Mon Sep 17 00:00:00 2001 From: rares-baesu-uipath Date: Mon, 21 Sep 2026 14:54:26 +0300 Subject: [PATCH 1/5] docs(api-workflow): use a placeholder for the example email recipient Example connector payloads hardcoded a real personal email address as the recipient. Replace every occurrence with . Per .claude/rules/content-quality.md, any example value carrying the shape of an identifier or personal data must be a placeholder: customer content- inspection gates match on shape alone and block the whole published package. Co-Authored-By: Claude Opus 5 (1M context) --- .../references/connector-activity-discovery.md | 14 +++++++------- .../references/troubleshooting.md | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/skills/uipath-api-workflow/references/connector-activity-discovery.md b/skills/uipath-api-workflow/references/connector-activity-discovery.md index ea0d4c5488..aa4fdef3c4 100644 --- a/skills/uipath-api-workflow/references/connector-activity-discovery.md +++ b/skills/uipath-api-workflow/references/connector-activity-discovery.md @@ -534,7 +534,7 @@ The connector schema lists fields like `message.toRecipients`, `message.subject` // so the message block disappears the next time the file is saved: "bodyParameters": { "message": { - "toRecipients": "andrei.hodoroaga@uipath.com", + "toRecipients": "", "subject": "this is a test", "body": { "content": "

hi

", "contentType": "Html" } }, @@ -543,7 +543,7 @@ The connector schema lists fields like `message.toRecipients`, `message.subject` // ✓ CORRECT — flat dotted keys match the connector's field names verbatim: "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "this is a test", "message.body.content": "

hi

", "message.body.contentType": "Html", @@ -561,13 +561,13 @@ The Assign / Response literal-wrap rule (SKILL.md rule 5) does NOT apply here. T // ✗ WRONG — designer reads "${'andrei...'}" as an expression, not a literal, // and clears the field on save: "bodyParameters": { - "message.toRecipients": "${'andrei.hodoroaga@uipath.com'}", + "message.toRecipients": "${''}", "message.subject": "${'this is a claude skill test'}" } // ✓ CORRECT — bare literals: "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "this is a claude skill test" } ``` @@ -643,7 +643,7 @@ The stub emits both `bodyParameters` (with the flat dotted keys per rule (a)) AN ... "endpoint": "/hubs/productivity/send-mail-v2", "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "...", "message.body.content": "...", "message.body.contentType": "Text", @@ -789,7 +789,7 @@ uip api-workflow registry resolve "send mail v2" --output json uip api-workflow registry stub \ --connection-id a8e592a5-76bb-4062-b712-3c364e4a1128 \ --inputs '{ - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "this is a claude skill test", "message.body.content": "${$context.variables.titleLabel}", "message.body.contentType": "Text", @@ -812,7 +812,7 @@ The stub detects multipart from IS Elements (`parameters[].type === "multipart"` "method": "POST", "endpoint": "/hubs/productivity/send-mail-v2", "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "this is a claude skill test", "message.body.content": "${$context.variables.titleLabel}", "message.body.contentType": "Text", diff --git a/skills/uipath-api-workflow/references/troubleshooting.md b/skills/uipath-api-workflow/references/troubleshooting.md index ea085bd8eb..b523c492dc 100644 --- a/skills/uipath-api-workflow/references/troubleshooting.md +++ b/skills/uipath-api-workflow/references/troubleshooting.md @@ -362,7 +362,7 @@ These are issues that surface only when a workflow is opened or run in **StudioW // ✗ Wrong — dropped on save "bodyParameters": { "message": { - "toRecipients": "andrei.hodoroaga@uipath.com", + "toRecipients": "", "subject": "test", "body": { "content": "

hi

", "contentType": "Html" } }, @@ -371,7 +371,7 @@ These are issues that surface only when a workflow is opened or run in **StudioW // ✓ Correct — survives roundtrip "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "test", "message.body.content": "

hi

", "message.body.contentType": "Html", @@ -383,20 +383,20 @@ These are issues that surface only when a workflow is opened or run in **StudioW ### Connector `bodyParameters` literal cleared after StudioWeb save (`${'literal'}` read as expression) -- **Symptom:** Authored connector body with literals wrapped per the Assign rule — `"message.toRecipients": "${'andrei.hodoroaga@uipath.com'}"`. Workflow runs locally. After StudioWeb save, the field becomes empty (or shows a non-literal expression marker in the designer); the email goes out with no recipient. +- **Symptom:** Authored connector body with literals wrapped per the Assign rule — `"message.toRecipients": "${''}"`. Workflow runs locally. After StudioWeb save, the field becomes empty (or shows a non-literal expression marker in the designer); the email goes out with no recipient. - **Cause:** SKILL.md rule 5 (literal-wrap as `${'foo'}`) applies to **Assign / Response / If `when`**, NOT to connector params. StudioWeb's connector field detector treats `${...}` as a non-literal expression — it's looking for either a bare literal value or a real reference. `${'foo'}` looks like neither (it's a literal-disguised-as-expression), so the value isn't bound as a field literal and is dropped. - **Fix:** Bare literals in connector params: ```json // ✗ Wrong — cleared on save "bodyParameters": { - "message.toRecipients": "${'andrei.hodoroaga@uipath.com'}", + "message.toRecipients": "${''}", "message.subject": "${'this is a claude skill test'}" } // ✓ Correct — bare literals "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "this is a claude skill test" } ``` @@ -437,7 +437,7 @@ These are issues that surface only when a workflow is opened or run in **StudioW "with": { "endpoint": "/hubs/productivity/send-mail-v2", "bodyParameters": { - "message.toRecipients": "andrei.hodoroaga@uipath.com", + "message.toRecipients": "", "message.subject": "...", "message.body.content": "...", "message.body.contentType": "Text", From 502e9169ce435647a7372361873f8c1bb2ee1e21 Mon Sep 17 00:00:00 2001 From: rares-baesu-uipath Date: Mon, 21 Sep 2026 14:54:53 +0300 Subject: [PATCH 2/5] feat(api-workflow): teach connector-event triggers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An API workflow can start from a connector event (Slack button clicked, new Outlook calendar entry, new Salesforce record) instead of an HTTP call. The skill had no guidance for this and actively told agents triggers could not be authored at all. A trigger is an activity with call: "UiPath.IntSvcEvent" placed first in the root sequence, AND a matching "EventTrigger" entry in bindings_v2.json. That entry is what registers the Orchestrator event trigger on deploy: without it the workflow validates, packs, publishes and deploys cleanly and then never fires, with every gate reporting success. That silent failure is the mistake this guidance exists to prevent. Adds: - references/trigger-authoring-guide.md — discovery flow, the activity shape, the EventTrigger binding contract, JMESPath filter composition, and what polling vs webhooks means for a local run - Critical Rule 16a, a When-to-Use entry, and reference/template navigation - assets/templates/trigger-workflow-template.json (passes validate) - two coder-eval tasks: an offline edit task that grades the stale-binding trap, and an authed e2e that grades the full authoring path Corrects two now-stale claims that trigger types cannot be stubbed, and one retired CLI verb (solution resource refresh -> solution resources refresh, retired at uip 1.196.0). Studio Web derives the binding itself and cannot run the local CLI write commands, so the bindings and local-run passages are enclosed in flavor blocks with sparse studioweb overrides. The flavor contract test swept a fixed list of built references that excluded new files, so the new reference is added to that list; verified non-vacuous. Requires the matching uip CLI support (registry resolve --kind trigger, registry stub for trigger types, bindings sync EventTrigger generation). The e2e task cannot pass until that ships. Co-Authored-By: Claude Opus 5 (1M context) --- .../studioweb/uipath-api-workflow/SKILL.md | 8 + .../references/trigger-authoring-guide.md | 42 ++++ skills/uipath-api-workflow/SKILL.md | 16 +- .../templates/trigger-workflow-template.json | 85 ++++++++ .../references/cli-reference.md | 29 ++- .../connector-activity-discovery.md | 4 +- .../references/trigger-authoring-guide.md | 189 ++++++++++++++++++ tests/scripts/compose-skill-flavor.test.mjs | 1 + .../trigger-author/author_slack_trigger.yaml | 108 ++++++++++ .../trigger-edit/_setup/seed.py | 182 +++++++++++++++++ .../retarget_trigger_channel.yaml | 86 ++++++++ 11 files changed, 737 insertions(+), 13 deletions(-) create mode 100644 skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md create mode 100644 skills/uipath-api-workflow/assets/templates/trigger-workflow-template.json create mode 100644 skills/uipath-api-workflow/references/trigger-authoring-guide.md create mode 100644 tests/tasks/uipath-api-workflow/trigger-author/author_slack_trigger.yaml create mode 100644 tests/tasks/uipath-api-workflow/trigger-edit/_setup/seed.py create mode 100644 tests/tasks/uipath-api-workflow/trigger-edit/retarget_trigger_channel.yaml diff --git a/skill-flavors/studioweb/uipath-api-workflow/SKILL.md b/skill-flavors/studioweb/uipath-api-workflow/SKILL.md index e6e198a325..ae1a625e9b 100644 --- a/skill-flavors/studioweb/uipath-api-workflow/SKILL.md +++ b/skill-flavors/studioweb/uipath-api-workflow/SKILL.md @@ -123,3 +123,11 @@ Fix failures in category order — **Structure > Expression > Activity Config > In Studio Web the run goes through the consent-gated, schema-inspected `RunProject` host operation; file arguments are supplied through the fields it declares for `JobAttachment` inputs (the host uploads them), and returned files appear as attachment references in the host result. Validation with `uip api-workflow validate` works offline as usual. + + + - **Studio Web owns the trigger's deployment registration.** The host derives it from the saved workflow, so treat the host-generated bindings and solution resources as authoritative, as in rule 16. You own the workflow side: keep the trigger as the first activity, and keep its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card** — a plain connector card signals wrong event metadata, and the registration the host derives from it will be equally wrong. Re-stub to correct it. + + + + - **A `webhooks` trigger is exercised by supplying the payload yourself.** A webhook event arrives only from the vendor, so verify the workflow body by passing the event payload as the execution input through the host `RunProject` operation, shaped like the event's output fields. A `polling` trigger behaves differently: it replays the most recent real matching event, which reaches the live vendor connection and is therefore side-effecting under rule 21. + diff --git a/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md new file mode 100644 index 0000000000..63abaa8ab6 --- /dev/null +++ b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md @@ -0,0 +1,42 @@ + +2. **Studio Web owns the trigger's deployment registration.** The host derives it from the saved workflow, so treat the host-generated bindings and solution resources as authoritative (rule 16). You own the workflow side: keep the trigger first, and keep its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card** — a plain connector card signals wrong event metadata, and what the host derives from it will be equally wrong. Re-stub to correct it. + + + +4. **Connector events are the only trigger activities that live in the workflow file.** A manual run is the process being invoked; a schedule is an Orchestrator trigger on the deployed process, managed outside the workflow file. See [operating-published-workflows.md](operating-published-workflows.md). + + + +1. `uip api-workflow registry resolve "" --kind trigger --output json` → trigger type id +2. `uip is connections list --output json` → connection UUID, then `uip is connections ping --output json` — REQUIRED +3. `GenericTrigger` only: `uip is triggers objects --connection-id --output json` → object name +4. `uip is triggers describe --connection-id --output json` → event parameters +5. `uip api-workflow registry stub --connection-id [--object-name ] [--inputs ''] --output json` +6. Insert `Data.Activity` as the first entry after `WorkflowStart` in `/solution//Workflow.json` and save. +7. From that project directory, `uip api-workflow validate Workflow.json --output json` until `Data.Status` is `Valid`. + + + +If `resolve` answers `unknown option '--kind'`, the embedded CLI predates trigger support. Report that exact host capability gap and ask how the user wants to proceed — the `uiPathActivityTypeId` and `metadata.configuration` still come from `stub` alone (fact 3). + + + +Studio Web writes it, derived from the saved workflow. Treat the host-generated bindings and solution resources as authoritative (rule 16). + +You own the workflow side. After saving, confirm the activity renders as a **trigger card**: a plain connector card signals wrong event metadata, and what the host derives from it will be equally wrong. Re-stub to correct it. + + + +A subscription fires only from the vendor, so a pre-deploy check supplies the payload itself: + +| `eventMode` | How to exercise it | +|---|---| +| `webhooks` | Supply the event payload as the execution input through the consent-gated, schema-inspected `RunProject` host operation, shaped like the event's `outputFields`. The trigger passes it straight through; the rest of the workflow runs on it. | +| `polling` | The same input-supplied path applies. With no input the trigger replays the most recent real matching event, which reaches the live vendor connection and is therefore side-effecting under rule 21 — get the user's consent first. | + +Offline `uip api-workflow validate` stays the autonomous pre-flight either way. + + + +- **Treat a clean `uip api-workflow validate` as proof the workflow is well formed, and only that.** Whether the deployed process subscribes to the intended event follows from the saved trigger activity (fact 2), so re-read it before calling the work done. + diff --git a/skills/uipath-api-workflow/SKILL.md b/skills/uipath-api-workflow/SKILL.md index dc3aa70b19..3f24573f25 100644 --- a/skills/uipath-api-workflow/SKILL.md +++ b/skills/uipath-api-workflow/SKILL.md @@ -27,6 +27,7 @@ Build, run, and publish UiPath API Workflows — JSON files conforming to the CN - User asks about **nested control flow** — If inside ForEach, TryCatch around a loop, conditional Break, multi-way branching, etc. - User asks for an **Integration Service connector activity** (Gmail Send Email, Outlook Get Newest Email, GitHub Search Issues, Slack Send Message, etc.) — follow the discovery flow in [references/connector-activity-discovery.md](references/connector-activity-discovery.md) - User asks for a **generic HTTP Request** that needs to render in StudioWeb's designer — same discovery flow +- User wants the workflow to **start from a connector event** ("when a Slack button is clicked", "on a new Outlook calendar entry"), or wants to inspect/change an existing trigger — separate catalog, separate activity shape (`call: "UiPath.IntSvcEvent"`). See [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md) - User asks about **JavaScript expressions, `$context`, `$input`, `$workflow`, `WorkflowStart`, or the `export.as` pattern** - User asks how to **debug** a failing API workflow run — the local `validate` → `run --no-auth` loop, or a **post-publish cloud run** (job logs/traces). See [references/operating-published-workflows.md](references/operating-published-workflows.md) @@ -93,8 +94,19 @@ Do NOT use for: `.flow` Maestro flows (→ `uipath-maestro-flow`), `.xaml` / cod - **Connector params use flat dotted keys and BARE literals.** `"message.toRecipients": "..."`, not nested objects; plain `"x@y.com"`, not `"${'x@y.com'}"` — rule 5's wrap is **inverted** here (`${'...'}` clears the field on save). Real references (`${$context...}`) stay wrapped. - **NEVER use Http kind with a vendor connection UUID** (401 "Invalid Element token"). IntSvc output is wrapped: read `$context.outputs..content.`. - - **(Solutions-mode + IntSvc only)** sync the connection into the catalogue: `uip api-workflow bindings sync --workflow ` then `uip solution resource refresh --solution-folder `. Skip for Http kind, non-connector activities, and standalone (no `Solution/`) projects. + - **(Solutions-mode + IntSvc only)** sync the connection into the catalogue: `uip api-workflow bindings sync --workflow ` then `uip solution resources refresh --solution-folder `. Skip for Http kind, non-connector activities, and standalone (no `Solution/`) projects. +16a. **Triggers are a separate catalog, a separate activity shape, and a mandatory second artifact.** When the workflow must start from a connector event (Slack button clicked, new Outlook calendar entry, new Salesforce record), author it with `uip api-workflow registry resolve "" --kind trigger` then `registry stub` — never by hand. Full flow, filter syntax and anti-patterns: [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md). Non-negotiables: + - **`--kind trigger` is required to find one.** Triggers live in a different TypeCache catalog; the default activity search returns none of them, and a miss there is NOT proof the trigger doesn't exist. + - **The trigger is the FIRST activity** in the root sequence, right after `WorkflowStart`, and there is at most one. It compiles to `call: "UiPath.IntSvcEvent"` — not `UiPath.IntSvc`. + + - **Run `uip api-workflow bindings sync` after every trigger add or edit.** It writes the `EventTrigger` entry in `bindings_v2.json`, which is what registers the Orchestrator event trigger on deploy. **Without it the workflow validates, packs, publishes and deploys clean — and then never fires.** Every gate reports success, so nothing else will catch this. In Solutions mode follow with `uip solution resources refresh` as in rule 16. + + - **A `GenericTrigger` needs `--object-name`** (a `CuratedTrigger` pins its object). Discover with `uip is triggers objects --output json`. + - **Slot key and export bucket differ for triggers** — slot `Button_Clicked_1`, bucket `button_1`. Read the payload as `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. + + - **`eventMode: "webhooks"` cannot be debugged locally.** `uip api-workflow run` only replays a real event for `polling` triggers; for webhooks, pass `--input-arguments` to feed a simulated payload and exercise the rest of the workflow. A polling replay hits the live vendor connection — treat it as side-effecting under rule 21. + 17. **Pass input as a JSON string.** `--input-arguments '{"key":"value"}'`. Invalid JSON exits 1. 18. **Always `--output json`** when parsing CLI output programmatically. Success → `{ "Result": "Success", "Code": "WorkflowRun", "Data": {...} }`. Failure → `{ "Result": "Failure", "Message": "...", "Instructions": "..." }` with exit 1. @@ -317,6 +329,7 @@ uip solution publish ./build/MyApiSolution_1.0.0.zip --tenant MyTenant --output | [references/task-types.md](references/task-types.md) | Adding/editing any single activity — exact JSON shape, required fields, export pattern, common mistakes, basic nesting hints per type | | [references/control-flow-patterns.md](references/control-flow-patterns.md) | Combining activities into hierarchical structures — nested If, ForEach inside DoWhile, TryCatch around/inside loops, conditional Break, multi-way branching, key uniqueness rules | | [references/connector-activity-discovery.md](references/connector-activity-discovery.md) | Authoring HTTP Request / Gmail / Outlook / GitHub / Slack / etc. activities via `uip api-workflow registry resolve` + `stub` — three-step flow, sample stub output, field-shape rules, multipart subsection, worked examples | +| [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md) | **Triggers** — starting a workflow from a connector event: `registry resolve --kind trigger` + `stub`, the `UiPath.IntSvcEvent` shape, the mandatory `EventTrigger` binding, JMESPath filters, polling vs webhooks | | [references/expressions-and-context.md](references/expressions-and-context.md) | Writing JS expressions, propagating outputs via `export.as`, accessing `$context` / `$input` / `$workflow`, JS_Invoke argument passing, strict-mode gotchas, key patterns | | [references/files-and-base64.md](references/files-and-base64.md) | **Files & base64** — `JobAttachment` references, the File to Base64 / Base64 to File activities (exact JSON, `$helpers.file.*`), `serializeData()` for inline bodies/Responses, passing local files in and getting files out of a run, pitfalls | @@ -340,6 +353,7 @@ uip solution publish ./build/MyApiSolution_1.0.0.zip --tenant MyTenant --output | [assets/templates/loop-aggregation-example.json](assets/templates/loop-aggregation-example.json) | DoWhile + ForEach + Assign accumulation — pure-compute aggregation pattern | | [assets/templates/nested-control-flow-example.json](assets/templates/nested-control-flow-example.json) | Heavy nesting demo — TryCatch around DoWhile around If with conditional Break | | [assets/templates/file-base64-roundtrip-example.json](assets/templates/file-base64-roundtrip-example.json) | **Files** — a `document` file input → File to Base64 → Base64 to File → Response returning both references. The exact `run.script` shape Studio Web writes for the two activities (rule 23). Verified end-to-end with a signed-in run: local file in → `.base64` reference → decoded file out, bytes identical. | +| [assets/templates/trigger-workflow-template.json](assets/templates/trigger-workflow-template.json) | **Trigger** — event-driven skeleton: `WorkflowStart` → `UiPath.IntSvcEvent` trigger → Response reading the payload. The `` connection UUID and trigger type id are sentinels — re-stub for the real values (rules 16, 16a). Passes `uip api-workflow validate`. | | [assets/templates/connector-call-example.json](assets/templates/connector-call-example.json) | **Http kind** — HTTP Request curated activity (`call: "UiPath.Http"`) for arbitrary REST calls. Generated by `registry stub` against the catfacts URL. Shows the canonical shape: `connectionId: "ImplicitConnection"`, `unifiedTypesCompatible: true`, `savedJitInputFieldId: "in_http-request"`, URL in `bodyParameters.url`. Verified end-to-end with `uip api-workflow run --no-auth`. | diff --git a/skills/uipath-api-workflow/assets/templates/trigger-workflow-template.json b/skills/uipath-api-workflow/assets/templates/trigger-workflow-template.json new file mode 100644 index 0000000000..15b4d7eeb0 --- /dev/null +++ b/skills/uipath-api-workflow/assets/templates/trigger-workflow-template.json @@ -0,0 +1,85 @@ +{ + "document": { + "dsl": "1.0.0", + "name": "Workflow", + "tags": {}, + "version": "0.0.1", + "namespace": "default", + "metadata": { + "variables": [] + } + }, + "do": [ + { + "Sequence_1": { + "do": [ + { + "WorkflowStart": { + "set": "${ { ...Object.entries($workflow.definition?.document?.metadata?.variables?.schema?.document?.properties || {}).reduce((acc, [name, def]) => ({ ...acc, [name]: def?.default }), {}), ...($workflow.input || {}) } }", + "output": { + "as": "${$input}" + }, + "export": { + "as": "{ ...$context, variables: { ...$context.variables, ...$output } }" + }, + "metadata": { + "activityType": "Assign", + "displayName": "Workflow start", + "fullName": "Assign", + "isTransparent": true + } + } + }, + { + "Button_Clicked_1": { + "call": "UiPath.IntSvcEvent", + "with": { + "connector": "uipath-salesforce-slack", + "connectionId": "", + "connectionResourceId": "", + "eventParameters": { + "channel_id": "" + }, + "objectName": "button", + "eventType": "BUTTON_CLICKED", + "eventMode": "webhooks", + "filterExpression": "(channel_id == '')" + }, + "export": { + "as": "{ ...$context, outputs: { ...$context?.outputs, \"button_1\": $output } }" + }, + "metadata": { + "activityType": "Connector", + "fullName": "Connector", + "displayName": "Button Clicked", + "uiPathActivityTypeId": "", + "configuration": "{\"essentialConfiguration\": {\"connectorVersion\": \"\", \"executionType\": null, \"scriptRef\": null, \"customFieldsRequestDetails\": null, \"instanceParameters\": {\"connectorKey\": \"uipath-salesforce-slack\", \"objectName\": \"button\", \"activityType\": \"CuratedTrigger\", \"version\": \"1.0.0\", \"eventOperation\": \"BUTTON_CLICKED\", \"eventMode\": \"webhooks\", \"supportsStreaming\": false}, \"objectName\": \"button\", \"packageVersion\": \"1.0.0\", \"httpMethod\": null, \"path\": null, \"filter\": null}}" + } + } + }, + { + "Response_1": { + "response": "${{ clicked: $context.outputs.button_1?.content }}", + "markJobAsFailed": false, + "then": "end", + "metadata": { + "activityType": "Response", + "displayName": "Response", + "fullName": "Response" + } + } + } + ], + "metadata": { + "activityType": "Sequence", + "displayName": "Sequence", + "fullName": "Sequence" + } + } + } + ], + "evaluate": { + "mode": "strict", + "language": "javascript" + } +} diff --git a/skills/uipath-api-workflow/references/cli-reference.md b/skills/uipath-api-workflow/references/cli-reference.md index 7726462545..dc60ed0e05 100644 --- a/skills/uipath-api-workflow/references/cli-reference.md +++ b/skills/uipath-api-workflow/references/cli-reference.md @@ -179,17 +179,20 @@ Look up DAP / connector activities (StudioWeb TypeCache, `projectType=Api`) and ### `uip api-workflow registry resolve` -Search the API-workflow-compatible TypeCache by keyword. Returns candidate activities with the GUID, connector key, object name, and HTTP method needed for `stub`. +Search the API-workflow-compatible TypeCache by keyword. Returns candidate activities and triggers with the GUID, connector key, object name, and HTTP method or event needed for `stub`. ```bash -uip api-workflow registry resolve [--limit ] --output json +uip api-workflow registry resolve [--kind ] [--limit ] --output json ``` | Argument / Flag | Required | Description | |--|--|--| -| `` | yes | Whitespace-tokenized; every token must substring-match somewhere in `displayName`, `connectorKey`, `objectName`, `fullName`. Case-insensitive. Combined queries narrow: `"github list records"` matches GitHub's "List Records". | +| `` | yes | Whitespace-tokenized; every token must substring-match somewhere in `displayName`, `connectorKey`, `objectName`, `eventOperation`, `fullName`. Case-insensitive. Combined queries narrow: `"github list records"` matches GitHub's "List Records". | +| `--kind ` | no | `activity`, `trigger`, or `all` (default). **Activities and triggers live in separate TypeCache catalogs**; `all` queries both (one request each), `trigger` is the fast path when you know you want an event. | | `-l, --limit ` | no | Max results (default: 50). | +Each match carries `Kind` (`"activity"` or `"trigger"`). Trigger matches add `EventOperation` (`CREATED`, `BUTTON_CLICKED`, …) and `EventMode` (`polling` / `webhooks`), and their `ActivityType` is `CuratedTrigger` or `GenericTrigger`. Authoring one is a different flow — see [trigger-authoring-guide.md](trigger-authoring-guide.md). + Success output (keys are PascalCased by the output formatter): ```json { @@ -225,7 +228,9 @@ Failure modes: ### `uip api-workflow registry stub` -Emit a ready-to-paste activity object for a known `uiPathActivityTypeId`. Combines the TypeCache entry (GUID + `InstanceParameters`) with Integration Service Elements metadata (full path, request/response fields, multipart signal) and picks Http kind (`UiPath.Http`) or IntSvc kind (`UiPath.IntSvc`) by `connectorKey`. +Emit a ready-to-paste activity object for a known `uiPathActivityTypeId`. Combines the TypeCache entry (GUID + `InstanceParameters`) with Integration Service Elements metadata (full path, request/response fields, multipart signal) and picks Http kind (`UiPath.Http`), IntSvc kind (`UiPath.IntSvc`), or — for `CuratedTrigger` / `GenericTrigger` — IntSvcEvent kind (`UiPath.IntSvcEvent`) by `connectorKey` and activity type. `Data.Kind` reports which. + +For a trigger the enrichment comes from the IS **event** endpoints instead: no verb, no path, no request body. `--inputs` fills `with.eventParameters` and composes the mandatory half of `filterExpression`, and the result adds `Data.EventParameters` + `Data.FilterFields`. See [trigger-authoring-guide.md](trigger-authoring-guide.md). ```bash uip api-workflow registry stub \ @@ -241,7 +246,7 @@ uip api-workflow registry stub \ |--|--|--| | `` | yes | The `uiPathActivityTypeId` GUID from `resolve`. | | `--connection-id ` | IntSvc kind only | Pinged vendor connection UUID. IntSvc kind leaves `` placeholders if omitted. Ignored for Http kind (HTTP). | -| `--object-name ` | Generic activities only | Target connector object for a Generic activity ("List Records" of *what*). Discover names with `uip is resources list --connection-id `. Defaults to the object pinned in the activity definition, when present. Ignored (with a warning) for Curated activities — their object is fixed by the activity definition. | +| `--object-name ` | Generic activities and GenericTrigger | Target connector object for a Generic activity ("List Records" of *what*). Discover names with `uip is resources list --connection-id `, or `uip is triggers objects ` for a GenericTrigger. Defaults to the object pinned in the definition, when present — a `GenericTrigger` pins none, so it is **required** there. Ignored (with a warning) for Curated activities and CuratedTrigger. | | `--instance ` | no | Suffix for slot/export bucket key. Default `1`. `--instance 2` produces `_2` keys. | | `--slot-key ` | no | Override the auto-derived PascalCase slot key. The export bucket key always derives from `objectName + "_"` (both Curated and Generic) and is not affected by this flag. | | `-i, --inputs ` | no | JSON object mapping field names to values. Field names match the IS schema (flat dotted keys — `"message.subject"`, not `{message:{subject:…}}`). Pass bare strings for literals; `${...}` for expression references. | @@ -277,8 +282,9 @@ Success output: Failure modes: - `"Activity '' not found in the Api-compatible TypeCache"` — re-run `resolve` to find a valid GUID. -- `"Activity type '' is not supported"` — trigger flavors (`CuratedTrigger`, `GenericTrigger`, `GenericPersistence`, …) are event subscriptions, not callable tasks; they cannot be stubbed. Curated and Generic activities are both supported. +- `"Activity type '' is not supported"` — the event-shaped flavors that are NOT triggers (`CuratedWaitFor`, `GenericWaitFor`, `GenericPersistence`, …) cannot be stubbed. `Curated`, `Generic`, `CuratedTrigger` and `GenericTrigger` are all supported; triggers stub into `call: "UiPath.IntSvcEvent"` — see [trigger-authoring-guide.md](trigger-authoring-guide.md). - `"Generic activity '' needs a target object"` — Generic activities require `--object-name`. Discover candidates with `uip is resources list --connection-id `. +- `"Trigger '' is a GenericTrigger and needs an object name"` — same for a GenericTrigger. Discover candidates with `uip is triggers objects --connection-id --output json`. - `"Could not resolve operation '' on object '' …"` — the object doesn't exist or doesn't support this operation (Generic stubs hard-require IS metadata; there is no fallback path/verb). Check the object with `uip is resources describe --connection-id `. - `"Invalid --inputs JSON"` — `--inputs` must be a JSON object (`'{"key":"value"}'`). @@ -326,13 +332,15 @@ See [connector-activity-discovery.md](connector-activity-discovery.md) for the f ## `uip api-workflow bindings sync` -Walk a `Workflow.json`, extract IntSvc-kind connector activities, and emit the canonical `bindings_v2.json` file next to it. Connection bindings are derived locally; **Solution-resource bindings** (process/queue/asset fields like Run Job's `ReleaseName`) are derived by querying IS metadata for each activity's object — when IS is unreachable, generation is skipped and any pre-existing entries of those kinds are preserved rather than dropped. This mirrors what StudioWeb computes in-memory via `computeBindings$` when a workflow is opened in the designer, and what `solution pack` writes at pack time. The output is the **required input** to `uip solution resources refresh`, which is what actually writes the Solution catalogue file AND per-user debug overwrites (the two artefacts StudioWeb's properties panel reads to resolve `connectionId` on activity click). +Walk a `Workflow.json`, extract IntSvc-kind connector activities **and the `UiPath.IntSvcEvent` trigger**, and emit the canonical `bindings_v2.json` file next to it. Connection bindings are derived locally; **Solution-resource bindings** (process/queue/asset fields like Run Job's `ReleaseName`) are derived by querying IS metadata for each activity's object — when IS is unreachable, generation is skipped and any pre-existing entries of those kinds are preserved rather than dropped. This mirrors what StudioWeb computes in-memory via `computeBindings$` when a workflow is opened in the designer, and what `solution pack` writes at pack time. The output is the **required input** to `uip solution resources refresh`, which is what actually writes the Solution catalogue file AND per-user debug overwrites (the two artefacts StudioWeb's properties panel reads to resolve `connectionId` on activity click). **When to run.** After every `registry stub --connection-id ` that adds an IntSvc activity to a workflow inside a `Solution/` tree. Always paired with `uip solution resources refresh` (the next step in the typical sequence). +**A workflow with a `UiPath.IntSvcEvent` trigger makes this mandatory in every mode** — the `EventTrigger` entry is the only thing that registers the subscription, and omitting it fails silently (rule 16a). The entry is regenerated on every sync, so re-run after changing the trigger's object, event, filter or connection; otherwise the binding keeps deploying the previous configuration. + **When to skip:** - **Http-kind-only workflows** — no IntSvc activities to bind. The command will still succeed with `ResourceCount: 0`, but the empty `bindings_v2.json` it writes serves no purpose. -- **Standalone projects** (no `Solution/` wrapper). StudioWeb doesn't consult a Solution resource tree in this mode; the downstream `solution resources refresh` has no solution to operate on. +- **Standalone projects** (no `Solution/` wrapper) **that have no trigger**. StudioWeb doesn't consult a Solution resource tree in this mode; the downstream `solution resources refresh` has no solution to operate on. ```bash uip api-workflow bindings sync \ @@ -355,19 +363,20 @@ Success output: "ActivitiesVisited": 1, "IntSvcActivities": 1, "DuplicatesCollapsed": 0, + "EventTriggers": 0, "ResourceBindings": 1, "PreservedResources": 0 } } ``` -`ResourceCount` is the total entries written (connections + resource bindings + preserved). `DuplicatesCollapsed` reports activities that shared a connection — two Outlook activities reading the same mailbox count as 1 binding, with `DuplicatesCollapsed: 1`. `ResourceBindings` counts Solution-resource entries generated from IS metadata (e.g. `process | RPA Workflow` for a Run Job activity); `PreservedResources` counts pre-existing non-connection entries carried over because this run did not regenerate them. +`ResourceCount` is the total entries written (connections + event trigger + resource bindings + preserved). `EventTriggers` counts the `EventTrigger` entries generated — `1` for a trigger workflow, `0` otherwise. A trigger is **not** counted under `IntSvcActivities`: it is a different call kind and yields a different binding. `DuplicatesCollapsed` reports activities that shared a connection — two Outlook activities reading the same mailbox count as 1 binding, with `DuplicatesCollapsed: 1`. `ResourceBindings` counts Solution-resource entries generated from IS metadata (e.g. `process | RPA Workflow` for a Run Job activity); `PreservedResources` counts pre-existing non-connection entries carried over because this run did not regenerate them. Failure modes: - `"Workflow file not found: "` — `--workflow` does not exist. Pass an existing path. - `"Workflow file is not valid JSON: "` — the file exists but won't parse. Fix the JSON syntax. -**Idempotency.** Always overwrites the existing `bindings_v2.json`. The output is a pure function of the workflow's IntSvc activities — re-running with the same workflow produces the same file byte-for-byte (modulo trailing newline). +**Idempotency.** Always overwrites the existing `bindings_v2.json`. The output is a pure function of the workflow's IntSvc activities and trigger — re-running with the same workflow produces the same file byte-for-byte (modulo trailing newline). Verified against two StudioWeb-authored solutions (a Slack `CuratedTrigger`/webhooks and an Outlook `GenericTrigger`/polling): regenerating from the workflow alone reproduces StudioWeb's own `bindings_v2.json` exactly. ## `uip solution resources refresh` diff --git a/skills/uipath-api-workflow/references/connector-activity-discovery.md b/skills/uipath-api-workflow/references/connector-activity-discovery.md index aa4fdef3c4..6a0beb7369 100644 --- a/skills/uipath-api-workflow/references/connector-activity-discovery.md +++ b/skills/uipath-api-workflow/references/connector-activity-discovery.md @@ -558,7 +558,7 @@ Same rule applies to `queryParameters` and `pathParameters`. The IS proxy unflat The Assign / Response literal-wrap rule (SKILL.md rule 5) does NOT apply here. The opposite is true. StudioWeb's connector deserializer treats `${'foo'}` as a non-literal expression and refuses to bind it as a field value — the field becomes empty after save. ```json -// ✗ WRONG — designer reads "${'andrei...'}" as an expression, not a literal, +// ✗ WRONG — designer reads "${'...'}" as an expression, not a literal, // and clears the field on save: "bodyParameters": { "message.toRecipients": "${''}", @@ -843,7 +843,7 @@ When the user asks to change a value, add a field, or copy a stubbed activity to ## Limits of this approach -1. **Trigger activity types cannot be stubbed** (`"CuratedTrigger"`, `"GenericTrigger"`, `"GenericPersistence"`, …) — they are event subscriptions, not callable tasks, and `stub` rejects them with `Activity type 'X' is not supported`. `Curated` and `Generic` activities are both supported; Generic additionally requires `--object-name` (see [Generic activities](#generic-activities----object-name-required-list-all-records-of-what)). For triggers, escalate to manual authoring. +1. **Triggers need `--kind trigger` on `resolve`, and stub into a different shape.** `"CuratedTrigger"` / `"GenericTrigger"` are event subscriptions, not callable tasks: they live in a separate TypeCache catalog that the default activity search never returns, and `stub` emits `call: "UiPath.IntSvcEvent"` plus a mandatory `EventTrigger` binding rather than a callable task. Author them with [trigger-authoring-guide.md](trigger-authoring-guide.md), not this flow. `GenericTrigger` takes `--object-name` like `Generic` does. Other event-shaped flavors (`"CuratedWaitFor"`, `"GenericWaitFor"`, `"GenericPersistence"`, …) are still rejected with `Activity type 'X' is not supported` — escalate those to manual authoring. 2. **Stub doesn't validate `--inputs` against the IS schema.** Field names not in `requestFields` / `parameters` are silently dropped on the way through `pickFields`. Check `Data.ResponseFields` and the IS schema if a value goes missing. diff --git a/skills/uipath-api-workflow/references/trigger-authoring-guide.md b/skills/uipath-api-workflow/references/trigger-authoring-guide.md new file mode 100644 index 0000000000..1892549bf8 --- /dev/null +++ b/skills/uipath-api-workflow/references/trigger-authoring-guide.md @@ -0,0 +1,189 @@ +# Trigger Authoring + +Start an API workflow from a connector event — a Slack button click, a new Outlook calendar entry, a new Salesforce record — rather than from an HTTP call or a schedule. + +A trigger is an **event subscription**, not a callable activity. It compiles to `call: "UiPath.IntSvcEvent"` and is the workflow's first activity. + +## Critical facts + +1. **The trigger is the FIRST activity in the root sequence**, immediately after `WorkflowStart`. A workflow has at most one trigger. + +2. **`bindings_v2.json` must carry an `EventTrigger` entry, and `uip api-workflow bindings sync` is the command that writes it.** Run it after every trigger add or edit. That entry is what registers the Orchestrator event trigger on deploy. Without it the workflow validates, packs, publishes and deploys clean — and then never fires. Every gate reports success, so nothing else will catch this. It is the most expensive mistake on this page. + +3. **Never hand-author a trigger.** Rule 16 applies unchanged: `registry resolve --kind trigger`, then `registry stub`. The `uiPathActivityTypeId` and `metadata.configuration` are not guessable. + +4. **Only connector events are trigger activities here.** A manual run is just invoking the process; a schedule is an Orchestrator trigger on the deployed process (`uip or triggers`). Neither appears in the workflow file. See [operating-published-workflows.md](operating-published-workflows.md). + + +## Authoring flow + + +1. `uip api-workflow registry resolve "" --kind trigger --output json` → trigger type id +2. `uip is connections list --output json` → connection UUID, then `uip is connections ping --output json` — REQUIRED +3. `GenericTrigger` only: `uip is triggers objects --connection-id --output json` → object name +4. `uip is triggers describe --connection-id --output json` → event parameters +5. `uip api-workflow registry stub --connection-id [--object-name ] [--inputs ''] --output json` +6. Insert `Data.Activity` as the first entry after `WorkflowStart`. +7. `uip api-workflow bindings sync --workflow --output json` +8. `uip api-workflow validate --output json` + + +Steps 2 and 4 follow the same rules as any Integration Service activity: a listing is folder-scoped, and `ping` is not optional. See [connector-activity-discovery.md](connector-activity-discovery.md#step-2--verify-a-vendor-connection-intsvc-kind-only). + +### Step 1 — resolve the trigger + +Triggers live in a **separate TypeCache catalog** from activities: an activity-only search never returns one, and the reverse holds too. `--kind` takes `activity`, `trigger`, or `all` (default). + +```bash +uip api-workflow registry resolve "button clicked" --kind trigger --output json +``` + + +If `resolve` answers `unknown option '--kind'`, the installed CLI predates trigger support — upgrade it before authoring a trigger. An older `registry stub` cannot emit `UiPath.IntSvcEvent` either, and hand-authoring is not the fallback (fact 3). + + +Every match carries `Kind`. Trigger matches add: + +| Field | Meaning | +|---|---| +| `ActivityType` | `CuratedTrigger` (object fixed by the definition) or `GenericTrigger` (you pick the object) | +| `EventOperation` | The connector event — `CREATED`, `BUTTON_CLICKED`, `UPDATED`, … | +| `EventMode` | `polling` or `webhooks` — see [Exercising a trigger before deploy](#exercising-a-trigger-before-deploy) | + +The keyword also matches `EventOperation`, so `resolve "button_clicked" --kind trigger` works when you know the event name but not the display name. + +### Step 3 — the object, for `GenericTrigger` only + +A `CuratedTrigger` pins its object. A `GenericTrigger` applies one event to an object you choose, exactly like a Generic activity — and its TypeCache definition carries no `objectName`, so `stub` fails until `--object-name` supplies one (exact message in [cli-reference.md](cli-reference.md#uip-api-workflow-registry-stub)). Pass the chosen `name` from: + +```bash +uip is triggers objects CREATED --connection-id --output json +``` + +### Steps 4–5 — event parameters and the filter + +`uip is triggers describe` reports three groups. Only the first is an input: + +| Group | What it is | +|---|---| +| `eventParameters` | Values that **scope the subscription** (which Slack channel, which folder). Supply via `--inputs`; they land in `with.eventParameters`. | +| `filterFields` | Payload fields a **user filter** can test. Not inputs — see [Filter expressions](#filter-expressions). | +| `outputFields` | The event payload downstream activities read. | + +```bash +uip api-workflow registry stub --connection-id \ + --inputs '{"channel_id":""}' --output json +``` + +`stub` returns `Data.EventParameters` and `Data.FilterFields` alongside the usual `Data.Activity`. Required event parameters you did not supply come back as a warning — heed it, because an unscoped subscription fires on everything. + +## The trigger activity shape + +```jsonc +{ + "Button_Clicked_1": { + "call": "UiPath.IntSvcEvent", + "with": { + "connector": "uipath-salesforce-slack", + "connectionId": "", + "connectionResourceId": "", + "eventParameters": { "channel_id": "" }, + "objectName": "button", + "eventType": "BUTTON_CLICKED", + "eventMode": "webhooks", + "filterExpression": "(channel_id == '')" + }, + "export": { "as": "{ ...$context, outputs: { ...$context?.outputs, \"button_1\": $output } }" }, + "metadata": { + "activityType": "Connector", + "fullName": "Connector", + "displayName": "Button Clicked", + "uiPathActivityTypeId": "", + "configuration": "{\"essentialConfiguration\":{...}}" + } + } +} +``` + +Differences from a regular connector activity, all load-bearing: + +- `call` is `UiPath.IntSvcEvent`, not `UiPath.IntSvc`. +- No `method`, no `endpoint` — an event has no verb or path. Inside `metadata.configuration`, `httpMethod` and `path` are `null`. +- `instanceParameters.activityType` is `CuratedTrigger` / `GenericTrigger` and carries `eventOperation` + `eventMode`. A `GenericTrigger` omits `instanceParameters.objectName`, keeping the object on the configuration's top level only. +- `eventParameters` values are connector fields: **bare literals**, never `${'...'}`-wrapped — same as `bodyParameters` (rule 16, the inversion of rule 5). +- The slot key keeps the display name's word boundaries — `Button_Clicked_1`, not `ButtonClicked_1` — while the export bucket derives from the object name (`button_1`), so **slot key and export bucket differ**. Read the payload as `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. + +Copy-paste skeleton: [../assets/templates/trigger-workflow-template.json](../assets/templates/trigger-workflow-template.json). + +## The EventTrigger binding + + +`uip api-workflow bindings sync` writes it, regenerating it on every sync so an edit to the object, event, filter or connection reaches it: + +```json +{ + "resource": "EventTrigger", + "key": "", + "activityId": "Button_Clicked_1", + "activityDisplayName": "Button Clicked", + "value": { "ConnectionId": { "defaultValue": "", "isExpression": false } }, + "metadata": { + "UseConnectionService": "true", + "Connector": "uipath-salesforce-slack", + "ActivityName": "Button Clicked", + "BindingsVersion": "2.2", + "ObjectName": "button", + "Operation": "BUTTON_CLICKED", + "FilterExpression": "(channel_id == '')", + "SolutionsSupport": "true" + } +} +``` + +In Solutions mode, follow it with `uip solution resources refresh --solution-folder ` as for any connector activity (rule 16). + + +> Not generated yet: the companion `Property` binding StudioWeb emits for triggers whose event fields sit in `path` / `query` locations (`design.exposeAsSubBinding`). Most triggers have none. If deployment does not rebind an event parameter per environment, open the workflow once in StudioWeb and let the designer write that entry. + +## Filter expressions + +`filterExpression` is JMESPath with **two halves joined by `&&`**: + +| Half | Source | Who writes it | +|---|---|---| +| Mandatory | The event parameters you supplied | `registry stub`, automatically | +| User | A condition on payload fields (`filterFields`) | You, by hand | + +Quote literals the JMESPath way, not the JavaScript way: strings in single quotes (`(channel_id == 'C123')`), booleans and numbers as backtick-wrapped JSON literals (``(isAllDay == `true`)``, ``(count == `3`)``). Double quotes are not JMESPath string literals. + +An all-day-only user filter on an Outlook calendar event, combined with its mandatory half: + +``` +(calendar_id == '') && ((isAllDay==`true`)) +``` + +Add the user half by editing `with.filterExpression`; the trigger's registration is regenerated from it (fact 2). Bad quoting does not fail validation — it fails at subscription time, or silently matches nothing. + +## Exercising a trigger before deploy + + +`uip api-workflow run` behaves differently depending on what you give it: + +| Situation | What happens | +|---|---| +| `--input-arguments ''` with data | The trigger **passes the input straight through** as the event payload. No connector call. This is how to exercise the rest of the workflow offline. | +| No input, `eventMode: polling` | Calls Integration Service and **replays the most recent real event** matching the filter. Needs auth and a healthy connection. No match → `": Trigger activity could not find any matches"`. | +| No input, `eventMode: webhooks` | Cannot be debugged. The run fails by design — a webhook event only arrives from the vendor. | + +For a webhooks trigger, always test with `--input-arguments`, shaping the payload like the event's `outputFields`. + +> The polling path fires against the **live vendor connection** and consumes a real event. Treat it as a side-effecting run under rule 21 — get the user's consent before looping on it. + + +## Anti-patterns + +- **Do NOT put the trigger anywhere but first, and do NOT add a second one.** It is the workflow's entry point; an event subscription in the middle of a sequence is meaningless. +- **Do NOT hand-edit `metadata.configuration`** to change the event or object. Re-stub instead — `eventOperation`, `eventMode` and `objectName` appear in several places that must agree. + +- **Do NOT treat a clean `validate` / `pack` / `publish` / `deploy` as proof the trigger works.** It only proves the workflow is well formed; see fact 2 for the artifact that makes it fire. + diff --git a/tests/scripts/compose-skill-flavor.test.mjs b/tests/scripts/compose-skill-flavor.test.mjs index 7063e63662..692496a59e 100644 --- a/tests/scripts/compose-skill-flavor.test.mjs +++ b/tests/scripts/compose-skill-flavor.test.mjs @@ -471,6 +471,7 @@ test("Studio Web inherits API Workflow authoring guidance and applies its host c "references/expressions-and-context.md", "references/operating-published-workflows.md", "references/task-types.md", + "references/trigger-authoring-guide.md", "references/troubleshooting.md", "references/workflow-file-format.md", ].map((relativePath) => [ diff --git a/tests/tasks/uipath-api-workflow/trigger-author/author_slack_trigger.yaml b/tests/tasks/uipath-api-workflow/trigger-author/author_slack_trigger.yaml new file mode 100644 index 0000000000..6385f6b198 --- /dev/null +++ b/tests/tasks/uipath-api-workflow/trigger-author/author_slack_trigger.yaml @@ -0,0 +1,108 @@ +task_id: skill-api-workflow-trigger-author-slack +description: > + Skill-guided e2e: agent authors an event-driven API workflow from scratch — + a Slack "Button Clicked" CuratedTrigger as the workflow's entry point. + Tests the trigger discovery path end to end: 'registry resolve --kind + trigger' (triggers live in a catalog the default activity search never + returns), REQUIRED connection ping, 'registry stub' emitting + call:"UiPath.IntSvcEvent", the trigger placed FIRST, and the EventTrigger + entry in bindings_v2.json that makes a deployment actually fire. + AUTHORING ONLY — the agent must not run the workflow, so no vendor event is + consumed. REQUIRES cloud auth AND a working Slack connection in the runner's + tenant; runs under the authed (nightly) experiment, never the no-auth smoke + gate. +tags: [uipath-api-workflow, e2e, mode:build, lifecycle:generate, connector, feature:trigger, feature:registry] + +sandbox: + python: {} + +initial_prompt: | + Author — but do NOT run — a UiPath API Workflow that starts when a button is + clicked in Slack. Create the project named `SlackButton` (its workflow file + will be `SlackButton/Workflow.json`), wire the trigger to a working Slack + connection, and return the clicked payload from the workflow. + + It is not complete until the project is in a state where deploying it would + actually subscribe to the Slack event — the workflow file alone is not + enough. Stop once it validates; leave no placeholders. + + Do NOT ask for approval, confirmation, or feedback. + +success_criteria: + - type: run_command + description: "A Workflow.json exists inside a project folder (not a bare root file)" + command: 'WF=$(find . -name Workflow.json -not -path "./Workflow.json" -not -path "*/node_modules/*" | head -1); test -n "$WF"' + timeout: 10 + expected_exit_code: 0 + weight: 1.0 + pass_threshold: 1.0 + + - type: command_executed + description: "Agent stubbed the trigger from the registry (rule 16a — never hand-authored)" + tool_name: "Bash" + command_pattern: 'uip\s+api-workflow\s+registry\s+stub' + min_count: 1 + weight: 1.5 + pass_threshold: 1.0 + + # Advisory only: `--kind all` is the default and also returns triggers, so an + # agent that omits `--kind trigger` can still reach a correct result. Records + # convention adherence without docking the run. + - type: command_executed + description: "ADVISORY — agent scoped the search to the trigger catalog" + tool_name: "Bash" + command_pattern: 'uip\s+api-workflow\s+registry\s+resolve[^\n]*--kind\s+trigger' + min_count: 1 + weight: 1.0 + pass_threshold: 0 + + - type: command_executed + description: "Agent verified the connection with a ping (rule 16 — REQUIRED)" + tool_name: "Bash" + command_pattern: 'uip\s+is\s+connections\s+ping' + min_count: 1 + weight: 1.5 + pass_threshold: 1.0 + + - type: run_command + description: "The trigger is the FIRST activity after WorkflowStart and uses the IntSvcEvent call" + command: 'WF=$(find . -name Workflow.json -not -path "./Workflow.json" -not -path "*/node_modules/*" | head -1); python3 -c "import json,sys; d=json.load(open(sys.argv[1])); root=d[\"do\"][0]; seq=root[list(root)[0]][\"do\"]; sys.exit(0 if list(seq[0])[0]==\"WorkflowStart\" and seq[1][list(seq[1])[0]].get(\"call\")==\"UiPath.IntSvcEvent\" else 1)" "$WF"' + timeout: 15 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "PRIMARY — bindings_v2.json carries the EventTrigger entry that registers the subscription on deploy" + command: 'B=$(find . -name bindings_v2.json -not -path "*/node_modules/*" | head -1); test -n "$B" && python3 -c "import json,sys; d=json.load(open(sys.argv[1])); t=[r for r in d.get(\"resources\",[]) if r.get(\"resource\")==\"EventTrigger\"]; sys.exit(0 if len(t)==1 and t[0][\"metadata\"].get(\"Operation\") and t[0].get(\"key\") else 1)" "$B"' + timeout: 15 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "No unresolved connection placeholder survived (rule 16 — sentinel must be replaced)" + command: 'WF=$(find . -name Workflow.json -not -path "./Workflow.json" -not -path "*/node_modules/*" | head -1); ! grep -q ''REPLACE_WITH'' "$WF"' + timeout: 10 + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: command_not_executed + description: "Agent did not run the workflow — a polling replay consumes a real vendor event (rule 21; prompt forbids it)" + tool_name: "Bash" + command_pattern: '(uip|\$UIP)\s+api-workflow\s+run(?![^\n]*--help)' + weight: 1.5 + pass_threshold: 1.0 + + - type: run_command + description: "Workflow passes offline validation" + command: 'WF=$(find . -name Workflow.json -not -path "./Workflow.json" -not -path "*/node_modules/*" | head -1); uip api-workflow validate "$WF" --output json | grep -q ''"Status": "Valid"''' + timeout: 30 + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + +run_limits: + expected_turns: 14 + max_turns: 40 diff --git a/tests/tasks/uipath-api-workflow/trigger-edit/_setup/seed.py b/tests/tasks/uipath-api-workflow/trigger-edit/_setup/seed.py new file mode 100644 index 0000000000..baeee6fc56 --- /dev/null +++ b/tests/tasks/uipath-api-workflow/trigger-edit/_setup/seed.py @@ -0,0 +1,182 @@ +"""Seed a Slack-trigger API workflow project whose trigger listens to C_OLD. + +Shaped after what Studio Web writes for a CuratedTrigger: the trigger is the +first activity after WorkflowStart, and bindings_v2.json carries the matching +EventTrigger entry. Both name the OLD channel, so the task is only complete +when both have moved to the new one. +""" + +import json +import os + +PROJECT = "SlackAlert" +OLD_CHANNEL = "C_OLD_CHANNEL_ID" +CONNECTION = "00000000-0000-4000-8000-000000000001" + +os.makedirs(PROJECT, exist_ok=True) + +configuration = json.dumps( + { + "essentialConfiguration": { + "connectorVersion": "", + "executionType": None, + "scriptRef": None, + "customFieldsRequestDetails": None, + "instanceParameters": { + "connectorKey": "uipath-salesforce-slack", + "objectName": "button", + "activityType": "CuratedTrigger", + "version": "1.0.0", + "eventOperation": "BUTTON_CLICKED", + "eventMode": "webhooks", + "supportsStreaming": False, + }, + "objectName": "button", + "packageVersion": "1.0.0", + "httpMethod": None, + "path": None, + "filter": None, + } + } +) + +workflow = { + "document": { + "dsl": "1.0.0", + "name": "Workflow", + "tags": {}, + "version": "0.0.1", + "namespace": "default", + "metadata": {"variables": []}, + }, + "do": [ + { + "Sequence_1": { + "do": [ + { + "WorkflowStart": { + "set": "${ { ...Object.entries($workflow.definition?.document?.metadata?.variables?.schema?.document?.properties || {}).reduce((acc, [name, def]) => ({ ...acc, [name]: def?.default }), {}), ...($workflow.input || {}) } }", + "output": {"as": "${$input}"}, + "export": { + "as": "{ ...$context, variables: { ...$context.variables, ...$output } }" + }, + "metadata": { + "activityType": "Assign", + "displayName": "Workflow start", + "fullName": "Assign", + "isTransparent": True, + }, + } + }, + { + "Button_Clicked_1": { + "call": "UiPath.IntSvcEvent", + "with": { + "connector": "uipath-salesforce-slack", + "connectionId": CONNECTION, + "connectionResourceId": CONNECTION, + "eventParameters": {"channel_id": OLD_CHANNEL}, + "objectName": "button", + "eventType": "BUTTON_CLICKED", + "eventMode": "webhooks", + "filterExpression": f"(channel_id == '{OLD_CHANNEL}')", + }, + "export": { + "as": '{ ...$context, outputs: { ...$context?.outputs, "button_1": $output } }' + }, + "metadata": { + "activityType": "Connector", + "fullName": "Connector", + "displayName": "Button Clicked", + "uiPathActivityTypeId": "00000000-0000-4000-8000-0000000000a1", + "configuration": configuration, + }, + } + }, + { + "Response_1": { + "response": "${{ clicked: $context.outputs.button_1?.content }}", + "markJobAsFailed": False, + "then": "end", + "metadata": { + "activityType": "Response", + "displayName": "Response", + "fullName": "Response", + }, + } + }, + ], + "metadata": { + "activityType": "Sequence", + "displayName": "Sequence", + "fullName": "Sequence", + }, + } + } + ], + "evaluate": {"mode": "strict", "language": "javascript"}, +} + +bindings = { + "version": "2.0", + "resources": [ + { + "resource": "EventTrigger", + "key": CONNECTION, + "activityId": "Button_Clicked_1", + "activityDisplayName": "Button Clicked", + "value": { + "ConnectionId": {"defaultValue": CONNECTION, "isExpression": False} + }, + "metadata": { + "UseConnectionService": "true", + "Connector": "uipath-salesforce-slack", + "ActivityName": "Button Clicked", + "BindingsVersion": "2.2", + "ObjectName": "button", + "Operation": "BUTTON_CLICKED", + "FilterExpression": f"(channel_id == '{OLD_CHANNEL}')", + "SolutionsSupport": "true", + }, + } + ], +} + +with open(f"{PROJECT}/Workflow.json", "w") as handle: + json.dump(workflow, handle, indent=2) + +with open(f"{PROJECT}/bindings_v2.json", "w") as handle: + json.dump(bindings, handle, indent=2) + +with open(f"{PROJECT}/project.uiproj", "w") as handle: + json.dump( + { + "ProjectType": "Api", + "Name": PROJECT, + "Description": None, + "MainFile": "Workflow.json", + }, + handle, + indent=2, + ) + +with open(f"{PROJECT}/entry-points.json", "w") as handle: + json.dump( + { + "$schema": "https://cloud.uipath.com/draft/2024-12/entry-point", + "$id": "entry-points.json", + "entryPoints": [ + { + "filePath": "content/Workflow.json", + "uniqueId": "00000000-0000-4000-8000-0000000000e1", + "type": "Api", + "input": None, + "output": None, + } + ], + }, + handle, + indent=2, + ) + +print(f"Seeded {PROJECT} listening to {OLD_CHANNEL}") diff --git a/tests/tasks/uipath-api-workflow/trigger-edit/retarget_trigger_channel.yaml b/tests/tasks/uipath-api-workflow/trigger-edit/retarget_trigger_channel.yaml new file mode 100644 index 0000000000..3b424b7c06 --- /dev/null +++ b/tests/tasks/uipath-api-workflow/trigger-edit/retarget_trigger_channel.yaml @@ -0,0 +1,86 @@ +task_id: skill-api-workflow-trigger-retarget-channel +description: > + Skill-guided brown-field edit of a connector-event trigger, fully offline. + The seeded project has a Slack CuratedTrigger listening to one channel, plus + the matching EventTrigger entry in bindings_v2.json. The agent must retarget + it to a different channel. Tests the single most expensive trigger mistake: + editing only Workflow.json leaves the EventTrigger binding stale, so the + deployed process keeps subscribing to the OLD channel while every gate + (validate / pack / publish / deploy) still reports success. Grades the two + artifacts, not the commands — regenerating via 'bindings sync' and editing + the binding by hand are both acceptable routes to a correct result. + No cloud auth required: the trigger's binding is derived locally. +tags: [uipath-api-workflow, smoke, mode:build, lifecycle:generate, feature:trigger] + +sandbox: + python: {} + template_sources: + - type: template_dir + path: _setup + mount_point: _setup + +pre_run: + - command: "python3 _setup/seed.py" + timeout: 60 + +initial_prompt: | + The `SlackAlert` API workflow project currently fires when a button is + clicked in the Slack channel `C_OLD_CHANNEL_ID`. + + Retarget it to the channel `C_NEW_CHANNEL_ID` instead. It is not complete + until a deployment of this project would subscribe to the new channel — + the workflow alone is not enough. + + Do NOT run the workflow. Do NOT ask for approval, confirmation, or feedback. + +success_criteria: + - type: run_command + description: "The trigger in Workflow.json now targets the new channel" + command: 'grep -q "C_NEW_CHANNEL_ID" SlackAlert/Workflow.json' + timeout: 10 + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: run_command + description: "PRIMARY — the EventTrigger binding tracks the new channel (without it the deploy still subscribes to the old one)" + command: 'python3 -c "import json,sys; d=json.load(open(sys.argv[1])); t=[r for r in d[\"resources\"] if r.get(\"resource\")==\"EventTrigger\"]; sys.exit(0 if len(t)==1 and \"C_NEW_CHANNEL_ID\" in t[0][\"metadata\"].get(\"FilterExpression\",\"\") else 1)" SlackAlert/bindings_v2.json' + timeout: 15 + expected_exit_code: 0 + weight: 3.0 + pass_threshold: 1.0 + + - type: run_command + description: "No reference to the old channel survives in either artifact" + command: '! grep -rq "C_OLD_CHANNEL_ID" SlackAlert/Workflow.json SlackAlert/bindings_v2.json' + timeout: 10 + expected_exit_code: 0 + weight: 2.0 + pass_threshold: 1.0 + + - type: run_command + description: "The trigger is still the first activity after WorkflowStart, still an IntSvcEvent call" + command: 'python3 -c "import json,sys; d=json.load(open(sys.argv[1])); seq=d[\"do\"][0][\"Sequence_1\"][\"do\"]; sys.exit(0 if list(seq[0])[0]==\"WorkflowStart\" and seq[1][list(seq[1])[0]].get(\"call\")==\"UiPath.IntSvcEvent\" else 1)" SlackAlert/Workflow.json' + timeout: 15 + expected_exit_code: 0 + weight: 1.5 + pass_threshold: 1.0 + + - type: command_not_executed + description: "Agent did not run the workflow (offline sandbox; the prompt forbids it)" + tool_name: "Bash" + command_pattern: '(uip|\$UIP)\s+api-workflow\s+run(?![^\n]*--help)' + weight: 1.0 + pass_threshold: 1.0 + + - type: run_command + description: "Workflow still passes offline validation" + command: 'uip api-workflow validate SlackAlert/Workflow.json --output json | grep -q ''"Status": "Valid"''' + timeout: 30 + expected_exit_code: 0 + weight: 1.5 + pass_threshold: 1.0 + +run_limits: + expected_turns: 8 + max_turns: 25 From b20a57846e202d1b3eb336aa45f655bc299300ea Mon Sep 17 00:00:00 2001 From: rares-baesu-uipath Date: Fri, 25 Sep 2026 12:10:00 +0300 Subject: [PATCH 3/5] docs(api-workflow): slim the trigger guide and fix Studio Web debug claims - halve the trigger reference and rule 16a; drop repeated facts - Studio Web debug falls back to debug polling for webhooks triggers - document path/query routing and the Property companion binding Co-Authored-By: Claude Fable 5.1 --- .../studioweb/uipath-api-workflow/SKILL.md | 4 +- .../references/trigger-authoring-guide.md | 22 ++- skills/uipath-api-workflow/SKILL.md | 16 +- .../references/cli-reference.md | 18 +-- .../connector-activity-discovery.md | 2 +- .../references/trigger-authoring-guide.md | 145 ++++-------------- 6 files changed, 61 insertions(+), 146 deletions(-) diff --git a/skill-flavors/studioweb/uipath-api-workflow/SKILL.md b/skill-flavors/studioweb/uipath-api-workflow/SKILL.md index ae1a625e9b..20bf5a19cf 100644 --- a/skill-flavors/studioweb/uipath-api-workflow/SKILL.md +++ b/skill-flavors/studioweb/uipath-api-workflow/SKILL.md @@ -125,9 +125,9 @@ Fix failures in category order — **Structure > Expression > Activity Config > - - **Studio Web owns the trigger's deployment registration.** The host derives it from the saved workflow, so treat the host-generated bindings and solution resources as authoritative, as in rule 16. You own the workflow side: keep the trigger as the first activity, and keep its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card** — a plain connector card signals wrong event metadata, and the registration the host derives from it will be equally wrong. Re-stub to correct it. + - **Studio Web owns the trigger's deployment registration.** It derives the `EventTrigger` binding from the saved workflow; treat host-generated bindings and solution resources as authoritative (rule 16). Keep the trigger's `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm it renders as a **trigger card**; a plain connector card means wrong event metadata — re-stub. - - **A `webhooks` trigger is exercised by supplying the payload yourself.** A webhook event arrives only from the vendor, so verify the workflow body by passing the event payload as the execution input through the host `RunProject` operation, shaped like the event's output fields. A `polling` trigger behaves differently: it replays the most recent real matching event, which reaches the live vendor connection and is therefore side-effecting under rule 21. + - **Exercise a trigger by supplying the payload.** Pass an execution input shaped like the event's output fields through the host `RunProject` operation; the trigger passes it through. With no input the runtime fetches a recent event through the live connection (`polling` always, `webhooks` only where the connector supports debug polling) — side-effecting under rule 21. diff --git a/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md index 63abaa8ab6..8eb0f64713 100644 --- a/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md +++ b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md @@ -1,9 +1,9 @@ -2. **Studio Web owns the trigger's deployment registration.** The host derives it from the saved workflow, so treat the host-generated bindings and solution resources as authoritative (rule 16). You own the workflow side: keep the trigger first, and keep its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card** — a plain connector card signals wrong event metadata, and what the host derives from it will be equally wrong. Re-stub to correct it. +2. **Studio Web owns the trigger's deployment registration.** It derives the `EventTrigger` binding from the saved workflow; treat host-generated bindings and solution resources as authoritative (rule 16). Keep the trigger first and its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card** — a plain connector card means wrong event metadata, and the derived registration is equally wrong. Re-stub to correct it. -4. **Connector events are the only trigger activities that live in the workflow file.** A manual run is the process being invoked; a schedule is an Orchestrator trigger on the deployed process, managed outside the workflow file. See [operating-published-workflows.md](operating-published-workflows.md). +4. **Only connector events live in the workflow file.** A schedule is an Orchestrator trigger on the deployed process, managed outside the workflow. See [operating-published-workflows.md](operating-published-workflows.md). @@ -17,26 +17,24 @@ -If `resolve` answers `unknown option '--kind'`, the embedded CLI predates trigger support. Report that exact host capability gap and ask how the user wants to proceed — the `uiPathActivityTypeId` and `metadata.configuration` still come from `stub` alone (fact 3). +If `resolve` answers `unknown option '--kind'`, the embedded CLI predates trigger support. Report that host capability gap and ask how the user wants to proceed — hand-authoring is not the fallback (fact 3). -Studio Web writes it, derived from the saved workflow. Treat the host-generated bindings and solution resources as authoritative (rule 16). - -You own the workflow side. After saving, confirm the activity renders as a **trigger card**: a plain connector card signals wrong event metadata, and what the host derives from it will be equally wrong. Re-stub to correct it. +Studio Web writes it from the saved workflow, including the `Property` companion for path/query event parameters. Treat host-generated bindings and solution resources as authoritative (rule 16). After saving, confirm the activity renders as a **trigger card**; re-stub if it renders as a plain connector card. -A subscription fires only from the vendor, so a pre-deploy check supplies the payload itself: +Run through the consent-gated, schema-inspected `RunProject` host operation: -| `eventMode` | How to exercise it | +| Input | Result | |---|---| -| `webhooks` | Supply the event payload as the execution input through the consent-gated, schema-inspected `RunProject` host operation, shaped like the event's `outputFields`. The trigger passes it straight through; the rest of the workflow runs on it. | -| `polling` | The same input-supplied path applies. With no input the trigger replays the most recent real matching event, which reaches the live vendor connection and is therefore side-effecting under rule 21 — get the user's consent first. | +| Execution input shaped like `outputFields` | The trigger passes it through as the event payload; no connector call. The safe way to exercise the body. | +| No input | The runtime fetches a recent matching event through the live connection: always for `polling`; for `webhooks` only when the connector has a debug-polling configuration, otherwise it fails with publish guidance. Reaches the vendor — side-effecting under rule 21. | -Offline `uip api-workflow validate` stays the autonomous pre-flight either way. +Studio Web's **Test trigger** panel checks filter matches against recent events without running the workflow. Offline `uip api-workflow validate` stays the autonomous pre-flight. -- **Treat a clean `uip api-workflow validate` as proof the workflow is well formed, and only that.** Whether the deployed process subscribes to the intended event follows from the saved trigger activity (fact 2), so re-read it before calling the work done. +- **Treat a clean `uip api-workflow validate` as proof the file is well formed, and only that.** The subscription follows from the saved trigger activity (fact 2), so re-read it before calling the work done. diff --git a/skills/uipath-api-workflow/SKILL.md b/skills/uipath-api-workflow/SKILL.md index 3f24573f25..3c8ddc61b9 100644 --- a/skills/uipath-api-workflow/SKILL.md +++ b/skills/uipath-api-workflow/SKILL.md @@ -27,7 +27,7 @@ Build, run, and publish UiPath API Workflows — JSON files conforming to the CN - User asks about **nested control flow** — If inside ForEach, TryCatch around a loop, conditional Break, multi-way branching, etc. - User asks for an **Integration Service connector activity** (Gmail Send Email, Outlook Get Newest Email, GitHub Search Issues, Slack Send Message, etc.) — follow the discovery flow in [references/connector-activity-discovery.md](references/connector-activity-discovery.md) - User asks for a **generic HTTP Request** that needs to render in StudioWeb's designer — same discovery flow -- User wants the workflow to **start from a connector event** ("when a Slack button is clicked", "on a new Outlook calendar entry"), or wants to inspect/change an existing trigger — separate catalog, separate activity shape (`call: "UiPath.IntSvcEvent"`). See [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md) +- User wants the workflow to **start from a connector event** ("when a Slack button is clicked"), or to change an existing trigger — see [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md) - User asks about **JavaScript expressions, `$context`, `$input`, `$workflow`, `WorkflowStart`, or the `export.as` pattern** - User asks how to **debug** a failing API workflow run — the local `validate` → `run --no-auth` loop, or a **post-publish cloud run** (job logs/traces). See [references/operating-published-workflows.md](references/operating-published-workflows.md) @@ -96,16 +96,12 @@ Do NOT use for: `.flow` Maestro flows (→ `uipath-maestro-flow`), `.xaml` / cod - **(Solutions-mode + IntSvc only)** sync the connection into the catalogue: `uip api-workflow bindings sync --workflow ` then `uip solution resources refresh --solution-folder `. Skip for Http kind, non-connector activities, and standalone (no `Solution/`) projects. -16a. **Triggers are a separate catalog, a separate activity shape, and a mandatory second artifact.** When the workflow must start from a connector event (Slack button clicked, new Outlook calendar entry, new Salesforce record), author it with `uip api-workflow registry resolve "" --kind trigger` then `registry stub` — never by hand. Full flow, filter syntax and anti-patterns: [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md). Non-negotiables: - - **`--kind trigger` is required to find one.** Triggers live in a different TypeCache catalog; the default activity search returns none of them, and a miss there is NOT proof the trigger doesn't exist. - - **The trigger is the FIRST activity** in the root sequence, right after `WorkflowStart`, and there is at most one. It compiles to `call: "UiPath.IntSvcEvent"` — not `UiPath.IntSvc`. +16a. **A connector-event trigger is a separate catalog, a separate activity shape, and a second artifact.** When the workflow must start from an event (Slack button clicked, new Outlook calendar entry), run `uip api-workflow registry resolve "" --kind trigger` then `registry stub` — never hand-author. The stub is the workflow's FIRST activity after `WorkflowStart`, at most one, `call: "UiPath.IntSvcEvent"`. A `GenericTrigger` needs `--object-name`. Full flow, filter syntax, anti-patterns: [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md). - - **Run `uip api-workflow bindings sync` after every trigger add or edit.** It writes the `EventTrigger` entry in `bindings_v2.json`, which is what registers the Orchestrator event trigger on deploy. **Without it the workflow validates, packs, publishes and deploys clean — and then never fires.** Every gate reports success, so nothing else will catch this. In Solutions mode follow with `uip solution resources refresh` as in rule 16. + - **Run `uip api-workflow bindings sync` after every trigger add or edit.** It writes the `EventTrigger` entry in `bindings_v2.json` that registers the Orchestrator event trigger on deploy. **Without it the workflow validates, packs, publishes and deploys clean — and never fires.** No gate catches this. In Solutions mode follow with `uip solution resources refresh` (rule 16). - - **A `GenericTrigger` needs `--object-name`** (a `CuratedTrigger` pins its object). Discover with `uip is triggers objects --output json`. - - **Slot key and export bucket differ for triggers** — slot `Button_Clicked_1`, bucket `button_1`. Read the payload as `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. - - **`eventMode: "webhooks"` cannot be debugged locally.** `uip api-workflow run` only replays a real event for `polling` triggers; for webhooks, pass `--input-arguments` to feed a simulated payload and exercise the rest of the workflow. A polling replay hits the live vendor connection — treat it as side-effecting under rule 21. + - **A `webhooks` trigger cannot run locally without input.** `uip api-workflow run` replays a live event only for `polling` (side-effecting under rule 21); for either mode, `--input-arguments` shaped like the event payload exercises the rest of the workflow offline. 17. **Pass input as a JSON string.** `--input-arguments '{"key":"value"}'`. Invalid JSON exits 1. @@ -329,7 +325,7 @@ uip solution publish ./build/MyApiSolution_1.0.0.zip --tenant MyTenant --output | [references/task-types.md](references/task-types.md) | Adding/editing any single activity — exact JSON shape, required fields, export pattern, common mistakes, basic nesting hints per type | | [references/control-flow-patterns.md](references/control-flow-patterns.md) | Combining activities into hierarchical structures — nested If, ForEach inside DoWhile, TryCatch around/inside loops, conditional Break, multi-way branching, key uniqueness rules | | [references/connector-activity-discovery.md](references/connector-activity-discovery.md) | Authoring HTTP Request / Gmail / Outlook / GitHub / Slack / etc. activities via `uip api-workflow registry resolve` + `stub` — three-step flow, sample stub output, field-shape rules, multipart subsection, worked examples | -| [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md) | **Triggers** — starting a workflow from a connector event: `registry resolve --kind trigger` + `stub`, the `UiPath.IntSvcEvent` shape, the mandatory `EventTrigger` binding, JMESPath filters, polling vs webhooks | +| [references/trigger-authoring-guide.md](references/trigger-authoring-guide.md) | **Triggers** — starting from a connector event: `resolve --kind trigger` + `stub`, the `EventTrigger` binding, JMESPath filters, polling vs webhooks | | [references/expressions-and-context.md](references/expressions-and-context.md) | Writing JS expressions, propagating outputs via `export.as`, accessing `$context` / `$input` / `$workflow`, JS_Invoke argument passing, strict-mode gotchas, key patterns | | [references/files-and-base64.md](references/files-and-base64.md) | **Files & base64** — `JobAttachment` references, the File to Base64 / Base64 to File activities (exact JSON, `$helpers.file.*`), `serializeData()` for inline bodies/Responses, passing local files in and getting files out of a run, pitfalls | @@ -353,7 +349,7 @@ uip solution publish ./build/MyApiSolution_1.0.0.zip --tenant MyTenant --output | [assets/templates/loop-aggregation-example.json](assets/templates/loop-aggregation-example.json) | DoWhile + ForEach + Assign accumulation — pure-compute aggregation pattern | | [assets/templates/nested-control-flow-example.json](assets/templates/nested-control-flow-example.json) | Heavy nesting demo — TryCatch around DoWhile around If with conditional Break | | [assets/templates/file-base64-roundtrip-example.json](assets/templates/file-base64-roundtrip-example.json) | **Files** — a `document` file input → File to Base64 → Base64 to File → Response returning both references. The exact `run.script` shape Studio Web writes for the two activities (rule 23). Verified end-to-end with a signed-in run: local file in → `.base64` reference → decoded file out, bytes identical. | -| [assets/templates/trigger-workflow-template.json](assets/templates/trigger-workflow-template.json) | **Trigger** — event-driven skeleton: `WorkflowStart` → `UiPath.IntSvcEvent` trigger → Response reading the payload. The `` connection UUID and trigger type id are sentinels — re-stub for the real values (rules 16, 16a). Passes `uip api-workflow validate`. | +| [assets/templates/trigger-workflow-template.json](assets/templates/trigger-workflow-template.json) | **Trigger** — `WorkflowStart` → `UiPath.IntSvcEvent` → Response reading the payload. `` values are sentinels: re-stub for the real ones (rule 16a). | | [assets/templates/connector-call-example.json](assets/templates/connector-call-example.json) | **Http kind** — HTTP Request curated activity (`call: "UiPath.Http"`) for arbitrary REST calls. Generated by `registry stub` against the catfacts URL. Shows the canonical shape: `connectionId: "ImplicitConnection"`, `unifiedTypesCompatible: true`, `savedJitInputFieldId: "in_http-request"`, URL in `bodyParameters.url`. Verified end-to-end with `uip api-workflow run --no-auth`. | diff --git a/skills/uipath-api-workflow/references/cli-reference.md b/skills/uipath-api-workflow/references/cli-reference.md index dc60ed0e05..d29fc86459 100644 --- a/skills/uipath-api-workflow/references/cli-reference.md +++ b/skills/uipath-api-workflow/references/cli-reference.md @@ -191,7 +191,7 @@ uip api-workflow registry resolve [--kind ] [--limit ] --outp | `--kind ` | no | `activity`, `trigger`, or `all` (default). **Activities and triggers live in separate TypeCache catalogs**; `all` queries both (one request each), `trigger` is the fast path when you know you want an event. | | `-l, --limit ` | no | Max results (default: 50). | -Each match carries `Kind` (`"activity"` or `"trigger"`). Trigger matches add `EventOperation` (`CREATED`, `BUTTON_CLICKED`, …) and `EventMode` (`polling` / `webhooks`), and their `ActivityType` is `CuratedTrigger` or `GenericTrigger`. Authoring one is a different flow — see [trigger-authoring-guide.md](trigger-authoring-guide.md). +Each match carries `Kind` (`"activity"` / `"trigger"`). Trigger matches add `EventOperation` and `EventMode` (`polling` / `webhooks`); `ActivityType` is `CuratedTrigger` or `GenericTrigger`. See [trigger-authoring-guide.md](trigger-authoring-guide.md). Success output (keys are PascalCased by the output formatter): ```json @@ -228,9 +228,9 @@ Failure modes: ### `uip api-workflow registry stub` -Emit a ready-to-paste activity object for a known `uiPathActivityTypeId`. Combines the TypeCache entry (GUID + `InstanceParameters`) with Integration Service Elements metadata (full path, request/response fields, multipart signal) and picks Http kind (`UiPath.Http`), IntSvc kind (`UiPath.IntSvc`), or — for `CuratedTrigger` / `GenericTrigger` — IntSvcEvent kind (`UiPath.IntSvcEvent`) by `connectorKey` and activity type. `Data.Kind` reports which. +Emit a ready-to-paste activity object for a known `uiPathActivityTypeId`. Combines the TypeCache entry (GUID + `InstanceParameters`) with Integration Service Elements metadata (full path, request/response fields, multipart signal) and picks Http kind (`UiPath.Http`), IntSvc kind (`UiPath.IntSvc`), or — for `CuratedTrigger` / `GenericTrigger` — IntSvcEvent kind (`UiPath.IntSvcEvent`). `Data.Kind` reports which. -For a trigger the enrichment comes from the IS **event** endpoints instead: no verb, no path, no request body. `--inputs` fills `with.eventParameters` and composes the mandatory half of `filterExpression`, and the result adds `Data.EventParameters` + `Data.FilterFields`. See [trigger-authoring-guide.md](trigger-authoring-guide.md). +For a trigger, enrichment comes from the IS **event** endpoints: no verb, path or body. `--inputs` fills the `with` parameter buckets by field location and composes the mandatory half of `filterExpression`; the result adds `Data.EventParameters` + `Data.FilterFields`. See [trigger-authoring-guide.md](trigger-authoring-guide.md). ```bash uip api-workflow registry stub \ @@ -246,7 +246,7 @@ uip api-workflow registry stub \ |--|--|--| | `` | yes | The `uiPathActivityTypeId` GUID from `resolve`. | | `--connection-id ` | IntSvc kind only | Pinged vendor connection UUID. IntSvc kind leaves `` placeholders if omitted. Ignored for Http kind (HTTP). | -| `--object-name ` | Generic activities and GenericTrigger | Target connector object for a Generic activity ("List Records" of *what*). Discover names with `uip is resources list --connection-id `, or `uip is triggers objects ` for a GenericTrigger. Defaults to the object pinned in the definition, when present — a `GenericTrigger` pins none, so it is **required** there. Ignored (with a warning) for Curated activities and CuratedTrigger. | +| `--object-name ` | Generic activities and GenericTrigger | Target connector object ("List Records" of *what*). Discover with `uip is resources list --connection-id `, or `uip is triggers objects ` for a GenericTrigger. Defaults to the object pinned in the definition; a `GenericTrigger` pins none. Ignored (with a warning) for Curated / CuratedTrigger. | | `--instance ` | no | Suffix for slot/export bucket key. Default `1`. `--instance 2` produces `_2` keys. | | `--slot-key ` | no | Override the auto-derived PascalCase slot key. The export bucket key always derives from `objectName + "_"` (both Curated and Generic) and is not affected by this flag. | | `-i, --inputs ` | no | JSON object mapping field names to values. Field names match the IS schema (flat dotted keys — `"message.subject"`, not `{message:{subject:…}}`). Pass bare strings for literals; `${...}` for expression references. | @@ -282,9 +282,9 @@ Success output: Failure modes: - `"Activity '' not found in the Api-compatible TypeCache"` — re-run `resolve` to find a valid GUID. -- `"Activity type '' is not supported"` — the event-shaped flavors that are NOT triggers (`CuratedWaitFor`, `GenericWaitFor`, `GenericPersistence`, …) cannot be stubbed. `Curated`, `Generic`, `CuratedTrigger` and `GenericTrigger` are all supported; triggers stub into `call: "UiPath.IntSvcEvent"` — see [trigger-authoring-guide.md](trigger-authoring-guide.md). +- `"Activity type '' is not supported"` — only `Curated`, `Generic`, `CuratedTrigger`, `GenericTrigger` stub; `CuratedWaitFor`, `GenericWaitFor`, `GenericPersistence`, … do not. - `"Generic activity '' needs a target object"` — Generic activities require `--object-name`. Discover candidates with `uip is resources list --connection-id `. -- `"Trigger '' is a GenericTrigger and needs an object name"` — same for a GenericTrigger. Discover candidates with `uip is triggers objects --connection-id --output json`. +- `"Trigger '' is a GenericTrigger and needs an object name"` — same for a GenericTrigger; discover with `uip is triggers objects --connection-id --output json`. - `"Could not resolve operation '' on object '' …"` — the object doesn't exist or doesn't support this operation (Generic stubs hard-require IS metadata; there is no fallback path/verb). Check the object with `uip is resources describe --connection-id `. - `"Invalid --inputs JSON"` — `--inputs` must be a JSON object (`'{"key":"value"}'`). @@ -336,7 +336,7 @@ Walk a `Workflow.json`, extract IntSvc-kind connector activities **and the `UiPa **When to run.** After every `registry stub --connection-id ` that adds an IntSvc activity to a workflow inside a `Solution/` tree. Always paired with `uip solution resources refresh` (the next step in the typical sequence). -**A workflow with a `UiPath.IntSvcEvent` trigger makes this mandatory in every mode** — the `EventTrigger` entry is the only thing that registers the subscription, and omitting it fails silently (rule 16a). The entry is regenerated on every sync, so re-run after changing the trigger's object, event, filter or connection; otherwise the binding keeps deploying the previous configuration. +**A `UiPath.IntSvcEvent` trigger makes this mandatory in every mode** — the `EventTrigger` entry (plus a `Property` companion for path/query event parameters) is what registers the subscription; omitting it fails silently (rule 16a). Both are regenerated on every sync, so re-run after changing the trigger's object, event, filter or connection. **When to skip:** - **Http-kind-only workflows** — no IntSvc activities to bind. The command will still succeed with `ResourceCount: 0`, but the empty `bindings_v2.json` it writes serves no purpose. @@ -370,13 +370,13 @@ Success output: } ``` -`ResourceCount` is the total entries written (connections + event trigger + resource bindings + preserved). `EventTriggers` counts the `EventTrigger` entries generated — `1` for a trigger workflow, `0` otherwise. A trigger is **not** counted under `IntSvcActivities`: it is a different call kind and yields a different binding. `DuplicatesCollapsed` reports activities that shared a connection — two Outlook activities reading the same mailbox count as 1 binding, with `DuplicatesCollapsed: 1`. `ResourceBindings` counts Solution-resource entries generated from IS metadata (e.g. `process | RPA Workflow` for a Run Job activity); `PreservedResources` counts pre-existing non-connection entries carried over because this run did not regenerate them. +`ResourceCount` is the total entries written (connections + trigger entries + resource bindings + preserved). `EventTriggers` is `1` for a trigger workflow, `0` otherwise; a trigger is not counted under `IntSvcActivities`. `DuplicatesCollapsed` reports activities that shared a connection — two Outlook activities reading the same mailbox count as 1 binding, with `DuplicatesCollapsed: 1`. `ResourceBindings` counts Solution-resource entries generated from IS metadata (e.g. `process | RPA Workflow` for a Run Job activity); `PreservedResources` counts pre-existing non-connection entries carried over because this run did not regenerate them. Failure modes: - `"Workflow file not found: "` — `--workflow` does not exist. Pass an existing path. - `"Workflow file is not valid JSON: "` — the file exists but won't parse. Fix the JSON syntax. -**Idempotency.** Always overwrites the existing `bindings_v2.json`. The output is a pure function of the workflow's IntSvc activities and trigger — re-running with the same workflow produces the same file byte-for-byte (modulo trailing newline). Verified against two StudioWeb-authored solutions (a Slack `CuratedTrigger`/webhooks and an Outlook `GenericTrigger`/polling): regenerating from the workflow alone reproduces StudioWeb's own `bindings_v2.json` exactly. +**Idempotency.** Always overwrites the existing `bindings_v2.json`. The output is a pure function of the workflow's IntSvc activities and trigger — re-running with the same workflow produces the same file byte-for-byte (modulo trailing newline). ## `uip solution resources refresh` diff --git a/skills/uipath-api-workflow/references/connector-activity-discovery.md b/skills/uipath-api-workflow/references/connector-activity-discovery.md index 6a0beb7369..9ca81efab0 100644 --- a/skills/uipath-api-workflow/references/connector-activity-discovery.md +++ b/skills/uipath-api-workflow/references/connector-activity-discovery.md @@ -843,7 +843,7 @@ When the user asks to change a value, add a field, or copy a stubbed activity to ## Limits of this approach -1. **Triggers need `--kind trigger` on `resolve`, and stub into a different shape.** `"CuratedTrigger"` / `"GenericTrigger"` are event subscriptions, not callable tasks: they live in a separate TypeCache catalog that the default activity search never returns, and `stub` emits `call: "UiPath.IntSvcEvent"` plus a mandatory `EventTrigger` binding rather than a callable task. Author them with [trigger-authoring-guide.md](trigger-authoring-guide.md), not this flow. `GenericTrigger` takes `--object-name` like `Generic` does. Other event-shaped flavors (`"CuratedWaitFor"`, `"GenericWaitFor"`, `"GenericPersistence"`, …) are still rejected with `Activity type 'X' is not supported` — escalate those to manual authoring. +1. **Triggers are a different flow.** `"CuratedTrigger"` / `"GenericTrigger"` live in a separate catalog (`resolve --kind trigger`) and stub into `call: "UiPath.IntSvcEvent"` plus an `EventTrigger` binding — see [trigger-authoring-guide.md](trigger-authoring-guide.md). Other event-shaped flavors (`"CuratedWaitFor"`, `"GenericWaitFor"`, `"GenericPersistence"`, …) are rejected with `Activity type 'X' is not supported` — escalate to manual authoring. 2. **Stub doesn't validate `--inputs` against the IS schema.** Field names not in `requestFields` / `parameters` are silently dropped on the way through `pickFields`. Check `Data.ResponseFields` and the IS schema if a value goes missing. diff --git a/skills/uipath-api-workflow/references/trigger-authoring-guide.md b/skills/uipath-api-workflow/references/trigger-authoring-guide.md index 1892549bf8..43a5a3af0b 100644 --- a/skills/uipath-api-workflow/references/trigger-authoring-guide.md +++ b/skills/uipath-api-workflow/references/trigger-authoring-guide.md @@ -1,18 +1,16 @@ # Trigger Authoring -Start an API workflow from a connector event — a Slack button click, a new Outlook calendar entry, a new Salesforce record — rather than from an HTTP call or a schedule. - -A trigger is an **event subscription**, not a callable activity. It compiles to `call: "UiPath.IntSvcEvent"` and is the workflow's first activity. +Start an API workflow from a connector event (Slack button click, new Outlook calendar entry, new Salesforce record). A trigger is an event subscription, not a callable activity: `call: "UiPath.IntSvcEvent"`, first activity after `WorkflowStart`. ## Critical facts -1. **The trigger is the FIRST activity in the root sequence**, immediately after `WorkflowStart`. A workflow has at most one trigger. +1. **The trigger is the FIRST activity after `WorkflowStart`, and there is at most one.** Studio Web inserts it at that slot and offers "Add trigger" only while none exists. -2. **`bindings_v2.json` must carry an `EventTrigger` entry, and `uip api-workflow bindings sync` is the command that writes it.** Run it after every trigger add or edit. That entry is what registers the Orchestrator event trigger on deploy. Without it the workflow validates, packs, publishes and deploys clean — and then never fires. Every gate reports success, so nothing else will catch this. It is the most expensive mistake on this page. +2. **`bindings_v2.json` must carry an `EventTrigger` entry; `uip api-workflow bindings sync` writes it.** Run it after every trigger add or edit. That entry registers the Orchestrator event trigger on deploy. Without it the workflow validates, packs, publishes and deploys clean — and never fires. No gate catches this. -3. **Never hand-author a trigger.** Rule 16 applies unchanged: `registry resolve --kind trigger`, then `registry stub`. The `uiPathActivityTypeId` and `metadata.configuration` are not guessable. +3. **Never hand-author a trigger.** `registry resolve --kind trigger`, then `registry stub` (rule 16). `uiPathActivityTypeId` and `metadata.configuration` are not guessable. -4. **Only connector events are trigger activities here.** A manual run is just invoking the process; a schedule is an Orchestrator trigger on the deployed process (`uip or triggers`). Neither appears in the workflow file. See [operating-published-workflows.md](operating-published-workflows.md). +4. **Only connector events live in the workflow file.** A schedule is an Orchestrator trigger on the deployed process (`uip or triggers`); a manual run is just invoking it. See [operating-published-workflows.md](operating-published-workflows.md). ## Authoring flow @@ -28,97 +26,43 @@ A trigger is an **event subscription**, not a callable activity. It compiles to 8. `uip api-workflow validate --output json` -Steps 2 and 4 follow the same rules as any Integration Service activity: a listing is folder-scoped, and `ping` is not optional. See [connector-activity-discovery.md](connector-activity-discovery.md#step-2--verify-a-vendor-connection-intsvc-kind-only). - -### Step 1 — resolve the trigger +Connection rules (folder-scoped listing, `ping` mandatory) are the same as for any connector activity: [connector-activity-discovery.md](connector-activity-discovery.md#step-2--verify-a-vendor-connection-intsvc-kind-only). -Triggers live in a **separate TypeCache catalog** from activities: an activity-only search never returns one, and the reverse holds too. `--kind` takes `activity`, `trigger`, or `all` (default). +### Resolve -```bash -uip api-workflow registry resolve "button clicked" --kind trigger --output json -``` +Triggers live in a separate TypeCache catalog. `--kind` takes `activity`, `trigger`, or `all` (default). The keyword also matches `EventOperation`, so `resolve "button_clicked" --kind trigger` works. -If `resolve` answers `unknown option '--kind'`, the installed CLI predates trigger support — upgrade it before authoring a trigger. An older `registry stub` cannot emit `UiPath.IntSvcEvent` either, and hand-authoring is not the fallback (fact 3). +If `resolve` answers `unknown option '--kind'`, the installed CLI predates trigger support — upgrade it. Hand-authoring is not the fallback (fact 3). -Every match carries `Kind`. Trigger matches add: - -| Field | Meaning | -|---|---| -| `ActivityType` | `CuratedTrigger` (object fixed by the definition) or `GenericTrigger` (you pick the object) | -| `EventOperation` | The connector event — `CREATED`, `BUTTON_CLICKED`, `UPDATED`, … | -| `EventMode` | `polling` or `webhooks` — see [Exercising a trigger before deploy](#exercising-a-trigger-before-deploy) | - -The keyword also matches `EventOperation`, so `resolve "button_clicked" --kind trigger` works when you know the event name but not the display name. +Trigger matches carry `ActivityType` (`CuratedTrigger` pins its object; `GenericTrigger` needs `--object-name`), `EventOperation` (`CREATED`, `BUTTON_CLICKED`, …) and `EventMode` (`polling` / `webhooks`). -### Step 3 — the object, for `GenericTrigger` only +### Event parameters and filter -A `CuratedTrigger` pins its object. A `GenericTrigger` applies one event to an object you choose, exactly like a Generic activity — and its TypeCache definition carries no `objectName`, so `stub` fails until `--object-name` supplies one (exact message in [cli-reference.md](cli-reference.md#uip-api-workflow-registry-stub)). Pass the chosen `name` from: - -```bash -uip is triggers objects CREATED --connection-id --output json -``` +`uip is triggers describe` reports three groups; only the first is an input: -### Steps 4–5 — event parameters and the filter - -`uip is triggers describe` reports three groups. Only the first is an input: - -| Group | What it is | +| Group | Meaning | |---|---| -| `eventParameters` | Values that **scope the subscription** (which Slack channel, which folder). Supply via `--inputs`; they land in `with.eventParameters`. | -| `filterFields` | Payload fields a **user filter** can test. Not inputs — see [Filter expressions](#filter-expressions). | +| `eventParameters` | Scope the subscription (which channel, which folder). Supply via `--inputs`. The stub routes each by IS location: `path`/`query` → `with.pathParameters` / `with.queryParameters`; the rest → `with.eventParameters` and the mandatory filter. | +| `filterFields` | Payload fields a user filter can test. Not inputs. | | `outputFields` | The event payload downstream activities read. | -```bash -uip api-workflow registry stub --connection-id \ - --inputs '{"channel_id":""}' --output json -``` +`stub` returns `Data.EventParameters` and `Data.FilterFields`. A required parameter not supplied comes back as a warning — heed it; an unscoped subscription fires on everything. -`stub` returns `Data.EventParameters` and `Data.FilterFields` alongside the usual `Data.Activity`. Required event parameters you did not supply come back as a warning — heed it, because an unscoped subscription fires on everything. +## The trigger activity -## The trigger activity shape +Skeleton: [../assets/templates/trigger-workflow-template.json](../assets/templates/trigger-workflow-template.json). Differences from a connector activity, all load-bearing: -```jsonc -{ - "Button_Clicked_1": { - "call": "UiPath.IntSvcEvent", - "with": { - "connector": "uipath-salesforce-slack", - "connectionId": "", - "connectionResourceId": "", - "eventParameters": { "channel_id": "" }, - "objectName": "button", - "eventType": "BUTTON_CLICKED", - "eventMode": "webhooks", - "filterExpression": "(channel_id == '')" - }, - "export": { "as": "{ ...$context, outputs: { ...$context?.outputs, \"button_1\": $output } }" }, - "metadata": { - "activityType": "Connector", - "fullName": "Connector", - "displayName": "Button Clicked", - "uiPathActivityTypeId": "", - "configuration": "{\"essentialConfiguration\":{...}}" - } - } -} -``` - -Differences from a regular connector activity, all load-bearing: - -- `call` is `UiPath.IntSvcEvent`, not `UiPath.IntSvc`. -- No `method`, no `endpoint` — an event has no verb or path. Inside `metadata.configuration`, `httpMethod` and `path` are `null`. -- `instanceParameters.activityType` is `CuratedTrigger` / `GenericTrigger` and carries `eventOperation` + `eventMode`. A `GenericTrigger` omits `instanceParameters.objectName`, keeping the object on the configuration's top level only. -- `eventParameters` values are connector fields: **bare literals**, never `${'...'}`-wrapped — same as `bodyParameters` (rule 16, the inversion of rule 5). -- The slot key keeps the display name's word boundaries — `Button_Clicked_1`, not `ButtonClicked_1` — while the export bucket derives from the object name (`button_1`), so **slot key and export bucket differ**. Read the payload as `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. - -Copy-paste skeleton: [../assets/templates/trigger-workflow-template.json](../assets/templates/trigger-workflow-template.json). +- `call: "UiPath.IntSvcEvent"`; no `method`/`endpoint`; `with.eventType` holds the event operation, `with.eventMode` is `polling` or `webhooks`. +- `instanceParameters.activityType` is `CuratedTrigger` / `GenericTrigger` with `eventOperation` + `eventMode`; `httpMethod` and `path` are `null`. A `GenericTrigger` omits `instanceParameters.objectName`. +- Parameter values are bare literals, never `${'...'}`-wrapped (rule 16). +- Slot key keeps display-name word boundaries (`Button_Clicked_1`); the export bucket derives from the object (`button_1`). Read the payload as `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. ## The EventTrigger binding -`uip api-workflow bindings sync` writes it, regenerating it on every sync so an edit to the object, event, filter or connection reaches it: +`uip api-workflow bindings sync` writes it and regenerates it on every sync, so an edit to object, event, filter or connection reaches it: ```json { @@ -140,50 +84,27 @@ Copy-paste skeleton: [../assets/templates/trigger-workflow-template.json](../ass } ``` -In Solutions mode, follow it with `uip solution resources refresh --solution-folder ` as for any connector activity (rule 16). +Path/query event parameters get a companion `Property` entry (`key` = event operation, `ParentResourceKey: "EventTrigger."`), also regenerated. In Solutions mode follow with `uip solution resources refresh --solution-folder ` (rule 16). -> Not generated yet: the companion `Property` binding StudioWeb emits for triggers whose event fields sit in `path` / `query` locations (`design.exposeAsSubBinding`). Most triggers have none. If deployment does not rebind an event parameter per environment, open the workflow once in StudioWeb and let the designer write that entry. - -## Filter expressions - -`filterExpression` is JMESPath with **two halves joined by `&&`**: - -| Half | Source | Who writes it | -|---|---|---| -| Mandatory | The event parameters you supplied | `registry stub`, automatically | -| User | A condition on payload fields (`filterFields`) | You, by hand | - -Quote literals the JMESPath way, not the JavaScript way: strings in single quotes (`(channel_id == 'C123')`), booleans and numbers as backtick-wrapped JSON literals (``(isAllDay == `true`)``, ``(count == `3`)``). Double quotes are not JMESPath string literals. +## Filter expression -An all-day-only user filter on an Outlook calendar event, combined with its mandatory half: - -``` -(calendar_id == '') && ((isAllDay==`true`)) -``` - -Add the user half by editing `with.filterExpression`; the trigger's registration is regenerated from it (fact 2). Bad quoting does not fail validation — it fails at subscription time, or silently matches nothing. +`filterExpression` is JMESPath: mandatory half (from event parameters, written by `stub`) `&&` user half (a condition on `filterFields`, written by you). Quoting follows the field type: strings single-quoted, booleans and numbers backtick JSON literals — `(channel_id == 'C123') && (isAllDay == \`true\`)`. Double quotes are not JMESPath string literals. Bad quoting passes `validate` and fails at subscription time or matches nothing. After editing `with.filterExpression`, re-run `bindings sync` (fact 2). ## Exercising a trigger before deploy -`uip api-workflow run` behaves differently depending on what you give it: - -| Situation | What happens | +| `uip api-workflow run` | Result | |---|---| -| `--input-arguments ''` with data | The trigger **passes the input straight through** as the event payload. No connector call. This is how to exercise the rest of the workflow offline. | -| No input, `eventMode: polling` | Calls Integration Service and **replays the most recent real event** matching the filter. Needs auth and a healthy connection. No match → `": Trigger activity could not find any matches"`. | -| No input, `eventMode: webhooks` | Cannot be debugged. The run fails by design — a webhook event only arrives from the vendor. | - -For a webhooks trigger, always test with `--input-arguments`, shaping the payload like the event's `outputFields`. - -> The polling path fires against the **live vendor connection** and consumes a real event. Treat it as a side-effecting run under rule 21 — get the user's consent before looping on it. +| `--input-arguments ''` | The trigger passes the input through as the event payload; no connector call. Shape it like `outputFields`. | +| No input, `eventMode: polling` | Reads the latest matching event from the live connection (needs auth, healthy connection). No match → `": Trigger activity could not find any matches"`. Side-effecting under rule 21. | +| No input, `eventMode: webhooks` | Fails by design; the event only arrives from the vendor. Use `--input-arguments`. | ## Anti-patterns -- **Do NOT put the trigger anywhere but first, and do NOT add a second one.** It is the workflow's entry point; an event subscription in the middle of a sequence is meaningless. -- **Do NOT hand-edit `metadata.configuration`** to change the event or object. Re-stub instead — `eventOperation`, `eventMode` and `objectName` appear in several places that must agree. +- **Do NOT place the trigger anywhere but first, or add a second one.** +- **Do NOT hand-edit `metadata.configuration`** to change event or object — re-stub; `eventOperation`, `eventMode`, `objectName` must agree across `with` and the blob. -- **Do NOT treat a clean `validate` / `pack` / `publish` / `deploy` as proof the trigger works.** It only proves the workflow is well formed; see fact 2 for the artifact that makes it fire. +- **Do NOT treat a clean `validate` / `pack` / `publish` / `deploy` as proof the trigger fires.** Only the `EventTrigger` binding does that (fact 2). From 3ef9af2b926a49b457c98c5e3ed63e7cbc73d1f7 Mon Sep 17 00:00:00 2001 From: rares-baesu-uipath Date: Fri, 25 Sep 2026 12:21:24 +0300 Subject: [PATCH 4/5] docs(api-workflow): keep the bindings sync instruction out of the Studio Web flavor Co-Authored-By: Claude Fable 5.1 --- .../uipath-api-workflow/references/trigger-authoring-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/uipath-api-workflow/references/trigger-authoring-guide.md b/skills/uipath-api-workflow/references/trigger-authoring-guide.md index 43a5a3af0b..d0e0a19845 100644 --- a/skills/uipath-api-workflow/references/trigger-authoring-guide.md +++ b/skills/uipath-api-workflow/references/trigger-authoring-guide.md @@ -89,7 +89,7 @@ Path/query event parameters get a companion `Property` entry (`key` = event oper ## Filter expression -`filterExpression` is JMESPath: mandatory half (from event parameters, written by `stub`) `&&` user half (a condition on `filterFields`, written by you). Quoting follows the field type: strings single-quoted, booleans and numbers backtick JSON literals — `(channel_id == 'C123') && (isAllDay == \`true\`)`. Double quotes are not JMESPath string literals. Bad quoting passes `validate` and fails at subscription time or matches nothing. After editing `with.filterExpression`, re-run `bindings sync` (fact 2). +`filterExpression` is JMESPath: mandatory half (from event parameters, written by `stub`) `&&` user half (a condition on `filterFields`, written by you). Quoting follows the field type: strings single-quoted, booleans and numbers backtick JSON literals — `(channel_id == 'C123') && (isAllDay == \`true\`)`. Double quotes are not JMESPath string literals. Bad quoting passes `validate` and fails at subscription time or matches nothing. Editing `with.filterExpression` is a trigger edit (fact 2). ## Exercising a trigger before deploy From bf228c388d764e8a89159dad932178dcf2696386 Mon Sep 17 00:00:00 2001 From: rares-baesu-uipath Date: Mon, 28 Sep 2026 03:01:09 +0300 Subject: [PATCH 5/5] docs(api-workflow): trim the trigger guide to what an agent acts on Drop the EventTrigger JSON block, the activity internals section and the parameter-routing detail; the stub and bindings sync own those. Add the array-field projection rule for filters and describe the Property entry by what the connector registers with. Same flavor markers, shorter text. Co-Authored-By: Claude Fable 5.1 --- .../references/trigger-authoring-guide.md | 10 +-- .../references/trigger-authoring-guide.md | 78 ++++++------------- 2 files changed, 28 insertions(+), 60 deletions(-) diff --git a/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md index 8eb0f64713..c3465424c7 100644 --- a/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md +++ b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md @@ -1,5 +1,5 @@ -2. **Studio Web owns the trigger's deployment registration.** It derives the `EventTrigger` binding from the saved workflow; treat host-generated bindings and solution resources as authoritative (rule 16). Keep the trigger first and its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card** — a plain connector card means wrong event metadata, and the derived registration is equally wrong. Re-stub to correct it. +2. **Studio Web owns the trigger's deployment registration.** It derives the `EventTrigger` binding from the saved workflow; treat host-generated bindings and solution resources as authoritative (rule 16). Keep the trigger first and its `objectName` / `eventType` / `eventMode` / `filterExpression` accurate. After saving, confirm the activity renders as a **trigger card**; a plain connector card means wrong event metadata, so re-stub. @@ -17,11 +17,11 @@ -If `resolve` answers `unknown option '--kind'`, the embedded CLI predates trigger support. Report that host capability gap and ask how the user wants to proceed — hand-authoring is not the fallback (fact 3). +If `resolve` answers `unknown option '--kind'`, the embedded CLI predates trigger support. Report that host capability gap and ask how the user wants to proceed; hand-authoring is not the fallback (rule 3). -Studio Web writes it from the saved workflow, including the `Property` companion for path/query event parameters. Treat host-generated bindings and solution resources as authoritative (rule 16). After saving, confirm the activity renders as a **trigger card**; re-stub if it renders as a plain connector card. +Studio Web derives it from the saved workflow, including the `Property` entry for the event parameters the connector registers with. Treat host-generated bindings and solution resources as authoritative (rule 16). After saving, confirm the activity renders as a **trigger card**; re-stub if it renders as a plain connector card. @@ -30,11 +30,11 @@ Run through the consent-gated, schema-inspected `RunProject` host operation: | Input | Result | |---|---| | Execution input shaped like `outputFields` | The trigger passes it through as the event payload; no connector call. The safe way to exercise the body. | -| No input | The runtime fetches a recent matching event through the live connection: always for `polling`; for `webhooks` only when the connector has a debug-polling configuration, otherwise it fails with publish guidance. Reaches the vendor — side-effecting under rule 21. | +| No input | The runtime fetches a recent matching event through the live connection: always for `polling`; for `webhooks` only when the connector has a debug-polling configuration, otherwise it fails with publish guidance. Reaches the vendor; side-effecting under rule 21. | Studio Web's **Test trigger** panel checks filter matches against recent events without running the workflow. Offline `uip api-workflow validate` stays the autonomous pre-flight. -- **Treat a clean `uip api-workflow validate` as proof the file is well formed, and only that.** The subscription follows from the saved trigger activity (fact 2), so re-read it before calling the work done. +A clean `uip api-workflow validate` proves the file is well formed, and only that. The subscription follows from the saved trigger activity (rule 2), so re-read it before calling the work done. diff --git a/skills/uipath-api-workflow/references/trigger-authoring-guide.md b/skills/uipath-api-workflow/references/trigger-authoring-guide.md index d0e0a19845..c22f8aab22 100644 --- a/skills/uipath-api-workflow/references/trigger-authoring-guide.md +++ b/skills/uipath-api-workflow/references/trigger-authoring-guide.md @@ -1,14 +1,14 @@ # Trigger Authoring -Start an API workflow from a connector event (Slack button click, new Outlook calendar entry, new Salesforce record). A trigger is an event subscription, not a callable activity: `call: "UiPath.IntSvcEvent"`, first activity after `WorkflowStart`. +Start an API workflow from a connector event (a Slack button clicked, a new Outlook calendar entry, a new Salesforce record). The trigger is an event subscription, not a callable activity: `call: "UiPath.IntSvcEvent"`, first after `WorkflowStart`. -## Critical facts +## Rules -1. **The trigger is the FIRST activity after `WorkflowStart`, and there is at most one.** Studio Web inserts it at that slot and offers "Add trigger" only while none exists. +1. **One trigger, first after `WorkflowStart`.** Never place it elsewhere or add a second one. -2. **`bindings_v2.json` must carry an `EventTrigger` entry; `uip api-workflow bindings sync` writes it.** Run it after every trigger add or edit. That entry registers the Orchestrator event trigger on deploy. Without it the workflow validates, packs, publishes and deploys clean — and never fires. No gate catches this. +2. **Run `uip api-workflow bindings sync` after every trigger add or edit.** It writes the `EventTrigger` entry in `bindings_v2.json` that registers the subscription on deploy. Without it the workflow validates, packs, publishes and deploys clean, and never fires. No gate catches this. -3. **Never hand-author a trigger.** `registry resolve --kind trigger`, then `registry stub` (rule 16). `uiPathActivityTypeId` and `metadata.configuration` are not guessable. +3. **Never hand-author a trigger.** `registry resolve --kind trigger`, then `registry stub` (rule 16). Use the stub output verbatim. To change the event or object, re-stub; do not edit `metadata.configuration`. 4. **Only connector events live in the workflow file.** A schedule is an Orchestrator trigger on the deployed process (`uip or triggers`); a manual run is just invoking it. See [operating-published-workflows.md](operating-published-workflows.md). @@ -28,68 +28,40 @@ Start an API workflow from a connector event (Slack button click, new Outlook ca Connection rules (folder-scoped listing, `ping` mandatory) are the same as for any connector activity: [connector-activity-discovery.md](connector-activity-discovery.md#step-2--verify-a-vendor-connection-intsvc-kind-only). -### Resolve - -Triggers live in a separate TypeCache catalog. `--kind` takes `activity`, `trigger`, or `all` (default). The keyword also matches `EventOperation`, so `resolve "button_clicked" --kind trigger` works. +Triggers live in a separate TypeCache catalog; `--kind` takes `activity`, `trigger`, or `all` (default). The keyword also matches `EventOperation`, so `resolve "button_clicked" --kind trigger` works. Each trigger match carries `ActivityType` (`CuratedTrigger` pins its object; `GenericTrigger` needs `--object-name`), `EventOperation` (`CREATED`, `BUTTON_CLICKED`, …) and `EventMode` (`polling` or `webhooks`). -If `resolve` answers `unknown option '--kind'`, the installed CLI predates trigger support — upgrade it. Hand-authoring is not the fallback (fact 3). +If `resolve` answers `unknown option '--kind'`, the installed CLI predates trigger support — upgrade it. Hand-authoring is not the fallback (rule 3). -Trigger matches carry `ActivityType` (`CuratedTrigger` pins its object; `GenericTrigger` needs `--object-name`), `EventOperation` (`CREATED`, `BUTTON_CLICKED`, …) and `EventMode` (`polling` / `webhooks`). - -### Event parameters and filter - -`uip is triggers describe` reports three groups; only the first is an input: +## Event parameters -| Group | Meaning | -|---|---| -| `eventParameters` | Scope the subscription (which channel, which folder). Supply via `--inputs`. The stub routes each by IS location: `path`/`query` → `with.pathParameters` / `with.queryParameters`; the rest → `with.eventParameters` and the mandatory filter. | -| `filterFields` | Payload fields a user filter can test. Not inputs. | -| `outputFields` | The event payload downstream activities read. | +`uip is triggers describe` returns three groups. Only the first is an input. -`stub` returns `Data.EventParameters` and `Data.FilterFields`. A required parameter not supplied comes back as a warning — heed it; an unscoped subscription fires on everything. +- `eventParameters` scope the subscription (which channel, which folder). Pass them with `--inputs`. +- `filterFields` are payload fields a user filter can test. +- `outputFields` are the event payload downstream activities read. -## The trigger activity +`stub` echoes `Data.EventParameters` and `Data.FilterFields`. A required parameter you did not supply comes back as a warning. Heed it: an unscoped subscription fires on everything. -Skeleton: [../assets/templates/trigger-workflow-template.json](../assets/templates/trigger-workflow-template.json). Differences from a connector activity, all load-bearing: +## Reading the payload -- `call: "UiPath.IntSvcEvent"`; no `method`/`endpoint`; `with.eventType` holds the event operation, `with.eventMode` is `polling` or `webhooks`. -- `instanceParameters.activityType` is `CuratedTrigger` / `GenericTrigger` with `eventOperation` + `eventMode`; `httpMethod` and `path` are `null`. A `GenericTrigger` omits `instanceParameters.objectName`. -- Parameter values are bare literals, never `${'...'}`-wrapped (rule 16). -- Slot key keeps display-name word boundaries (`Button_Clicked_1`); the export bucket derives from the object (`button_1`). Read the payload as `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. +Downstream activities read `$context.outputs..content`, using the stub's `Data.ExportBucketKey` verbatim. Skeleton: [../assets/templates/trigger-workflow-template.json](../assets/templates/trigger-workflow-template.json). ## The EventTrigger binding -`uip api-workflow bindings sync` writes it and regenerates it on every sync, so an edit to object, event, filter or connection reaches it: - -```json -{ - "resource": "EventTrigger", - "key": "", - "activityId": "Button_Clicked_1", - "activityDisplayName": "Button Clicked", - "value": { "ConnectionId": { "defaultValue": "", "isExpression": false } }, - "metadata": { - "UseConnectionService": "true", - "Connector": "uipath-salesforce-slack", - "ActivityName": "Button Clicked", - "BindingsVersion": "2.2", - "ObjectName": "button", - "Operation": "BUTTON_CLICKED", - "FilterExpression": "(channel_id == '')", - "SolutionsSupport": "true" - } -} -``` - -Path/query event parameters get a companion `Property` entry (`key` = event operation, `ParentResourceKey: "EventTrigger."`), also regenerated. In Solutions mode follow with `uip solution resources refresh --solution-folder ` (rule 16). +`uip api-workflow bindings sync` derives it from the trigger's `with` clause and regenerates it on every sync. After syncing, check `bindings_v2.json` for one `resource: "EventTrigger"` entry whose `key` is the connection UUID and whose `metadata.ObjectName` / `Operation` / `FilterExpression` match `with.objectName` / `eventType` / `filterExpression`. Event parameters the connector registers with (typically path and query fields) also get a `Property` entry under it. In Solutions mode follow with `uip solution resources refresh --solution-folder ` (rule 16). ## Filter expression -`filterExpression` is JMESPath: mandatory half (from event parameters, written by `stub`) `&&` user half (a condition on `filterFields`, written by you). Quoting follows the field type: strings single-quoted, booleans and numbers backtick JSON literals — `(channel_id == 'C123') && (isAllDay == \`true\`)`. Double quotes are not JMESPath string literals. Bad quoting passes `validate` and fails at subscription time or matches nothing. Editing `with.filterExpression` is a trigger edit (fact 2). +`with.filterExpression` is JMESPath in two halves joined by ` && `: the mandatory half, which `stub` writes from the event parameters, and an optional user half you write against `filterFields`. + +- Quote by field type: strings in single quotes, booleans and numbers as backtick literals — `(channel_id == 'C123') && (isAllDay == \`true\`)`. Double quotes are identifiers in JMESPath, not strings. +- An array field (its name contains `[*]`) is matched with a projection, not `==`: `ParentFolders[?ID=='INBOX']`. `stub` writes a plain comparison for such a field; replace it. +- Bad quoting passes `validate` and fails at subscription time or matches nothing. +- Editing `filterExpression` is a trigger edit (rule 2). ## Exercising a trigger before deploy @@ -101,10 +73,6 @@ Path/query event parameters get a companion `Property` entry (`key` = event oper | No input, `eventMode: webhooks` | Fails by design; the event only arrives from the vendor. Use `--input-arguments`. | -## Anti-patterns - -- **Do NOT place the trigger anywhere but first, or add a second one.** -- **Do NOT hand-edit `metadata.configuration`** to change event or object — re-stub; `eventOperation`, `eventMode`, `objectName` must agree across `with` and the blob. -- **Do NOT treat a clean `validate` / `pack` / `publish` / `deploy` as proof the trigger fires.** Only the `EventTrigger` binding does that (fact 2). +A clean `validate` / `pack` / `publish` / `deploy` is not proof the trigger fires. Only the `EventTrigger` binding does that (rule 2).