diff --git a/skill-flavors/studioweb/uipath-api-workflow/SKILL.md b/skill-flavors/studioweb/uipath-api-workflow/SKILL.md index e6e198a325..20bf5a19cf 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.** 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. + + + + - **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 new file mode 100644 index 0000000000..c3465424c7 --- /dev/null +++ b/skill-flavors/studioweb/uipath-api-workflow/references/trigger-authoring-guide.md @@ -0,0 +1,40 @@ + +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. + + + +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). + + + +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 host capability gap and ask how the user wants to proceed; hand-authoring is not the fallback (rule 3). + + + +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. + + + +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. | + +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. + + + +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/SKILL.md b/skills/uipath-api-workflow/SKILL.md index dc3aa70b19..01c8ad1f38 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"), 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) @@ -93,8 +94,15 @@ 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. **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. Place the stub in exactly one slot: the root Sequence's `do` array, directly after `WorkflowStart` (`do[0].Sequence_1.do[1]`) — never inside an If / ForEach / TryCatch, never after another activity, never a second trigger. `validate` rejects a misplaced or second trigger. 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` 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 `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. 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 +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 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 | @@ -340,6 +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** — `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/assets/templates/trigger-workflow-template.json b/skills/uipath-api-workflow/assets/templates/trigger-workflow-template.json new file mode 100644 index 0000000000..4d72bd84f7 --- /dev/null +++ b/skills/uipath-api-workflow/assets/templates/trigger-workflow-template.json @@ -0,0 +1,86 @@ +{ + "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": { + "user_id": "", + "channel_id": "" + }, + "objectName": "button", + "eventType": "BUTTON_CLICKED", + "eventMode": "webhooks", + "filterExpression": "(user_id == '') && (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..d29fc86459 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"` / `"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 { @@ -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`). `Data.Kind` reports which. + +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 \ @@ -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 ("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. | @@ -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"` — 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 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 `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. -- **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 + 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 — 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). ## `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 ea0d4c5488..9ca81efab0 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", @@ -558,16 +558,16 @@ 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": "${'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", @@ -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 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 new file mode 100644 index 0000000000..9b64699cfb --- /dev/null +++ b/skills/uipath-api-workflow/references/trigger-authoring-guide.md @@ -0,0 +1,78 @@ +# Trigger Authoring + +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`. + +## Rules + +1. **One trigger, in one slot: the root Sequence's `do` array, directly after `WorkflowStart`** (`do[0].Sequence_1.do[1]`, where Studio Web writes it). Never put it inside an If / ForEach / TryCatch, never after another activity, never add a second one. `validate` rejects a misplaced or second trigger. + +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). 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). + + +## 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` + + +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; `--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 (rule 3). + + +## Event parameters + +`uip is triggers describe` returns three groups. Only the first is an input. + +- `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. + +`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. + +## Reading the payload + +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` 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 + +`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 + + +| `uip api-workflow run` | Result | +|---|---| +| `--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`. | + + + +A clean `validate` / `pack` / `publish` / `deploy` is not proof the trigger fires. Only the `EventTrigger` binding does that (rule 2). + 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", 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..bda43c3e3b --- /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,re,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 re.fullmatch(r\"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}\", 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..0d182c52b6 --- /dev/null +++ b/tests/tasks/uipath-api-workflow/trigger-edit/_setup/seed.py @@ -0,0 +1,183 @@ +"""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" +USER = "U_ALERT_USER_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": {"user_id": USER, "channel_id": OLD_CHANNEL}, + "objectName": "button", + "eventType": "BUTTON_CLICKED", + "eventMode": "webhooks", + "filterExpression": f"(user_id == '{USER}') && (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"(user_id == '{USER}') && (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