@salesforce/afv-skills 1.52.0 → 1.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/skills/service-agentforce-contact-center-coordinate/SKILL.md +170 -0
- package/skills/service-agentforce-contact-center-coordinate/assets/escalation-flow.flow-meta.xml +72 -0
- package/skills/service-agentforce-contact-center-coordinate/assets/omni-flow.flow-meta.xml +85 -0
- package/skills/service-agentforce-contact-center-coordinate/assets/report-template.md +52 -0
- package/skills/service-agentforce-contact-center-coordinate/references/agentforce-prerequisite.md +31 -0
- package/skills/service-agentforce-contact-center-coordinate/references/messaging_channel.md +76 -0
- package/skills/service-agentforce-contact-center-coordinate/references/number_management_api.md +78 -0
- package/skills/service-agentforce-contact-center-coordinate/references/omni-flow-routing.md +74 -0
- package/skills/service-agentforce-contact-center-coordinate/references/setup_summary.md +36 -0
- package/skills/service-agentforce-contact-center-coordinate/references/verification_and_errors.md +42 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/check-agentforce-prereq.sh +54 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/create-routing-flows.sh +49 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/create-voice-agent.sh +73 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/create-voice-channel.sh +66 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/fetch-numbers.sh +21 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/lib.sh +46 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/prepare-agent-workdir.sh +21 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/procure-number.sh +24 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-acc-queue.sh +27 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-channel-line.sh +37 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-flow-definition.sh +33 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/verify-number-live.sh +63 -0
- package/skills/experience-content-media-search/SKILL.md +0 -353
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Omni Flow routing (Agentforce agent on voice)
|
|
2
|
+
|
|
3
|
+
Two routing models attach a `PstnVoice` channel to the contact center:
|
|
4
|
+
|
|
5
|
+
| Model | `SessionHandlerId` | `FallbackQueueId` | Use when |
|
|
6
|
+
|-------|--------------------|-------------------|----------|
|
|
7
|
+
| **Omni Queue** (default) | ACC queue Id (`00G…`) | — | Calls land straight in the contact center queue for human reps. |
|
|
8
|
+
| **Omni Flow** | inbound RoutingFlow **FlowDefinition** Id (`300…`) | ACC/voice queue Id (`00G…`) | Calls route to an Agentforce agent first, with the queue as fallback/escalation. |
|
|
9
|
+
|
|
10
|
+
Omni Queue is the skill's original behavior (Step 8). This file covers **Omni Flow**.
|
|
11
|
+
|
|
12
|
+
## The wiring contract
|
|
13
|
+
|
|
14
|
+
An Omni Flow channel points its `SessionHandlerId` at an **inbound RoutingFlow** (not a queue) and names the queue in `FallbackQueueId`. That inbound flow references a **published, active** Agentforce agent. So the flow and agent must exist *before* the channel is created:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
agent (published + Active) -> inbound RoutingFlow (Active, 300-prefix) -> channel
|
|
18
|
+
escalation RoutingFlow (Active) -> agent outbound (optional)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`FlowDefinition` (Id prefix `300`) is the stable handle used in `SessionHandlerId` — not the per-version `Flow` (prefix `301`).
|
|
22
|
+
|
|
23
|
+
## Sub-fork: new vs existing
|
|
24
|
+
|
|
25
|
+
- **Use existing** — the user supplies an already-published agent and an active inbound RoutingFlow. Resolve the flow's `300` Id with `scripts/resolve-flow-definition.sh <alias> <flow-dev-name>`, then create the channel (below). Skip agent/flow creation.
|
|
26
|
+
- **Create new** — build both, in order:
|
|
27
|
+
1. **Preflight** — `scripts/check-agentforce-prereq.sh <alias>` must return a canonical `Einstein Agent User` profile user. It can provision one with `sf org create agent-user` if the org has capacity. Do not use broad Agent/Bot profile matches.
|
|
28
|
+
2. **Work dir** — `scripts/prepare-agent-workdir.sh acc-voice-build`, then run `sf agent generate agent-spec ... --output-file specs/<api-name>.yaml --target-org <alias>` from inside `acc-voice-build`; the spec generator requires SFDX project context.
|
|
29
|
+
3. **Agent** — from the parent/original work dir, run `scripts/create-voice-agent.sh <alias> <api-name> <label> acc-voice-build/specs/<api-name>.yaml acc-voice-build`. The script generates the authoring bundle, replaces the generator's `default_agent_user: "NEW AGENT USER"` placeholder with the spec's `agentUser`, deploys the `AiAuthoringBundle`, validates, publishes, and activates.
|
|
30
|
+
4. **Flows** — `scripts/create-routing-flows.sh <alias> <api-name> <label> <queue-id> <queue-name> acc-voice-build`. Renders and deploys the inbound (`_Voice_Omni_Flow`) and escalation (`_Voice_Escalation`) flows from inside the SFDX work dir and prints the inbound `300` Id.
|
|
31
|
+
|
|
32
|
+
## Create the channel
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
scripts/create-voice-channel.sh <alias> <number> <channelLineId> <flowDefinitionId> <queueId>
|
|
36
|
+
# 5th arg present -> Omni Flow: SessionHandlerId=<flowDefinitionId 300>, FallbackQueueId=<queueId>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## RoutingFlow field contract
|
|
40
|
+
|
|
41
|
+
Both flows are `processType: RoutingFlow`, `status: Active`, on the `sfdc_phone` service channel (`serviceChannelLabel: Phone`). Templates: `assets/omni-flow.flow-meta.xml`, `assets/escalation-flow.flow-meta.xml`.
|
|
42
|
+
|
|
43
|
+
| Field | Inbound (`_Voice_Omni_Flow`) | Escalation (`_Voice_Escalation`) |
|
|
44
|
+
|-------|------------------------------|----------------------------------|
|
|
45
|
+
| `routingType` | `Copilot` (routes to the agent) | `QueueBased` (routes to the queue) |
|
|
46
|
+
| `copilotId` | `<setupReference>` → `BotDefinition` = agent api-name (required) | — |
|
|
47
|
+
| `copilotLabel` | agent `MasterLabel` | — |
|
|
48
|
+
| `queueId` / `queueLabel` | fallback queue (18-char Id + `Name`) | escalation queue |
|
|
49
|
+
|
|
50
|
+
## Deviation — planner type
|
|
51
|
+
|
|
52
|
+
Do **not** switch the agent's `plannerType` to `Atlas__VoiceAgent` (or add `plannerSurfaces`). This org (API ≤ 67) rejects it with an opaque server error (e.g. `-1103525358`), even with a complete published bundle. Keep the `Atlas__ConcurrentMultiAgentOrchestration` planner that `sf agent publish` generates — **inbound voice routes correctly via the `Copilot` RoutingFlow regardless of planner type** (verified against the org's own working voice agent). `create-voice-agent.sh` never touches the planner, so no action is needed; just don't add a planner-swap step.
|
|
53
|
+
|
|
54
|
+
## Fallback to Omni Queue
|
|
55
|
+
|
|
56
|
+
If the Agentforce prerequisite can't be confirmed (`references/agentforce-prerequisite.md`), or agent/flow creation fails, stop and report unless the user explicitly accepts a queue-only fallback. Some runs require an Agentforce agent and Omni Flow channel, so do not silently downgrade the routing model.
|
|
57
|
+
|
|
58
|
+
## Agent creation gotchas
|
|
59
|
+
|
|
60
|
+
| Symptom | Fix |
|
|
61
|
+
|---------|-----|
|
|
62
|
+
| `RequiresProjectError` from `sf agent generate agent-spec` | Prepare and run from `acc-voice-build` with `scripts/prepare-agent-workdir.sh` |
|
|
63
|
+
| `default agent user NEW AGENT USER` during publish | Let `create-voice-agent.sh` patch the generated `.agent` from the spec's `agentUser` before deploy/publish |
|
|
64
|
+
| `Unable to access the Salesforce Agent APIs` / `User doesn't have access to use agent` | Ensure `agentUser` is a canonical `Einstein Agent User` profile user, not a generic Agent/Bot-profile user |
|
|
65
|
+
| `InvalidProjectWorkspaceError` during flow deploy | Reuse the prepared work dir; `create-routing-flows.sh` deploys from inside it |
|
|
66
|
+
|
|
67
|
+
## Verify
|
|
68
|
+
|
|
69
|
+
```sql
|
|
70
|
+
SELECT Id, DeveloperName, MessageType, SessionHandlerId, FallbackQueueId, IsActive
|
|
71
|
+
FROM MessagingChannel WHERE Id = '<channel-id>'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`SessionHandlerId` must start with `300` (the inbound FlowDefinition) and `FallbackQueueId` with `00G` (the queue).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Setup Summary
|
|
2
|
+
|
|
3
|
+
After the voice channel is created and verified, present a summary to the user.
|
|
4
|
+
|
|
5
|
+
## Summary table
|
|
6
|
+
|
|
7
|
+
| Configuration | Value |
|
|
8
|
+
|---------------|-------|
|
|
9
|
+
| Phone Number | `<phone-number>` |
|
|
10
|
+
| Number Type | `<phone-type>` |
|
|
11
|
+
| Country | `<country>` |
|
|
12
|
+
| ChannelLine ID | `<channel-line-id>` |
|
|
13
|
+
| Phone Status | `<code-status>` |
|
|
14
|
+
| Channel ID | `<channel-id>` |
|
|
15
|
+
| Channel Status | Active |
|
|
16
|
+
| Routing model | `<Omni Queue \| Omni Flow>` |
|
|
17
|
+
| Queue | `<acc-queue-name>` |
|
|
18
|
+
| Agentforce agent (Omni Flow) | `<agent-label>` (`<agent-api-name>`) |
|
|
19
|
+
| Inbound flow (Omni Flow) | `<agent-api-name>_Voice_Omni_Flow` |
|
|
20
|
+
| Developer Name | `<dev-name>` |
|
|
21
|
+
|
|
22
|
+
Include the Agentforce agent / inbound flow rows only for the Omni Flow path.
|
|
23
|
+
|
|
24
|
+
## What's ready
|
|
25
|
+
|
|
26
|
+
- Phone number procured and provisioned
|
|
27
|
+
- ChannelLine resolved
|
|
28
|
+
- Voice channel created and activated
|
|
29
|
+
- **Omni Queue:** channel routed to the Agentforce Contact Center queue
|
|
30
|
+
- **Omni Flow:** channel routed to the Agentforce agent (Copilot inbound flow) with the queue as fallback; escalation flow deployed
|
|
31
|
+
|
|
32
|
+
## Suggested next steps
|
|
33
|
+
|
|
34
|
+
- **Omni Queue:** configure agents in the queue.
|
|
35
|
+
- **Omni Flow:** test the agent conversation; optionally tune voice (`modality voice:`) or wire outbound escalation to the agent.
|
|
36
|
+
- Test by calling the procured number.
|
package/skills/service-agentforce-contact-center-coordinate/references/verification_and_errors.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Verification & Error Handling
|
|
2
|
+
|
|
3
|
+
## Live-verification polling strategy
|
|
4
|
+
|
|
5
|
+
A procured number must reach `Live` status on its `CommunicationChannelLine`
|
|
6
|
+
before a working voice channel can route calls.
|
|
7
|
+
|
|
8
|
+
`CommunicationChannelLine` is Tooling-API-only — it is not queryable via `sf data query`
|
|
9
|
+
(that returns `INVALID_TYPE: sObject type 'CommunicationChannelLine' is not supported`).
|
|
10
|
+
Query it via `sf api request rest .../tooling/query` instead, the same way
|
|
11
|
+
`scripts/resolve-channel-line.sh` does. The live-status field is `CodeStatus`
|
|
12
|
+
(not `Status__c`, which does not exist on this object), and it's looked up by `Code`
|
|
13
|
+
(not `Name`).
|
|
14
|
+
|
|
15
|
+
1. Query the line by code:
|
|
16
|
+
`SELECT Id, Code, CodeStatus FROM CommunicationChannelLine WHERE Code = '<number>'`
|
|
17
|
+
2. If the record exists and `CodeStatus = 'Live'` → done.
|
|
18
|
+
3. Otherwise call `POST /numberStateReconcile` (see `number_management_api.md`) to
|
|
19
|
+
force reconciliation.
|
|
20
|
+
4. Poll every **5 seconds**, up to **3 attempts** (≈15 seconds total), re-querying `CodeStatus`.
|
|
21
|
+
5. If still not `Live` after 3 attempts, warn the user and offer to wait/retry or proceed
|
|
22
|
+
anyway (the channel may be created but not immediately operational).
|
|
23
|
+
|
|
24
|
+
`scripts/verify-number-live.sh` implements this loop and prints the final status.
|
|
25
|
+
|
|
26
|
+
## ChannelLine resolution (retry)
|
|
27
|
+
|
|
28
|
+
Immediately after procurement, `CommunicationChannelLine` may not yet exist because
|
|
29
|
+
records propagate asynchronously. `scripts/resolve-channel-line.sh` queries by `Code`
|
|
30
|
+
and retries up to 3 times (3s apart) before reporting failure.
|
|
31
|
+
|
|
32
|
+
## Error categories
|
|
33
|
+
|
|
34
|
+
| Category | Symptom | Handling |
|
|
35
|
+
|----------|---------|----------|
|
|
36
|
+
| Number fetch | `400` | Invalid country/type — re-collect selections |
|
|
37
|
+
| Number fetch | `500` | Retry after a few seconds |
|
|
38
|
+
| Procurement | `409` | Already procured — pick a different number |
|
|
39
|
+
| Sync | `404` | Number missing — re-run procurement |
|
|
40
|
+
| Sync | `500` | Retry after a few seconds |
|
|
41
|
+
| Channel create | duplicate `DeveloperName` | Derive a unique name |
|
|
42
|
+
| Channel create | invalid queue Id | Re-resolve the queue by name (Step 7) |
|
package/skills/service-agentforce-contact-center-coordinate/scripts/check-agentforce-prereq.sh
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Preflight the Agentforce (service agent) prerequisite and resolve the canonical Einstein Agent User.
|
|
3
|
+
# The Omni Flow "create new agent" path needs a user with the Einstein Agent User profile;
|
|
4
|
+
# broader Agent/Bot profile matches can validate a spec but fail at publish time.
|
|
5
|
+
# If no canonical user exists, ask the Salesforce CLI to provision one with the correct
|
|
6
|
+
# profile, permission sets, and PSL assignments.
|
|
7
|
+
# Usage: check-agentforce-prereq.sh <org-alias> [base-username]
|
|
8
|
+
# Output: the Einstein Agent User Username on stdout.
|
|
9
|
+
# Exit 0 if found/provisioned, 2 usage, 3 if none exists and auto-provisioning fails.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
14
|
+
|
|
15
|
+
if [[ "${1:-}" == "--help" || $# -lt 1 ]]; then
|
|
16
|
+
echo "Usage: check-agentforce-prereq.sh <org-alias> [base-username]" >&2
|
|
17
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
ALIAS="$1"
|
|
21
|
+
BASE_USERNAME="${2:-agentforce-service-agent@example.com}"
|
|
22
|
+
|
|
23
|
+
# The profile name is the canonical signal used by `sf org create agent-user`.
|
|
24
|
+
# Do not fall back to generic Agent/Bot profiles; those users can make spec generation
|
|
25
|
+
# appear successful but fail `sf agent publish authoring-bundle`.
|
|
26
|
+
USERNAME=$(sf data query \
|
|
27
|
+
--query "SELECT Username FROM User WHERE IsActive = true AND Profile.Name = 'Einstein Agent User' ORDER BY CreatedDate DESC LIMIT 1" \
|
|
28
|
+
--target-org "$ALIAS" --json 2>/dev/null | jq -r '.result.records[0].Username // empty')
|
|
29
|
+
|
|
30
|
+
if [[ -n "$USERNAME" ]]; then
|
|
31
|
+
echo "$USERNAME"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
CREATE_RESP=$(sf org create agent-user \
|
|
36
|
+
--target-org "$ALIAS" \
|
|
37
|
+
--base-username "$BASE_USERNAME" \
|
|
38
|
+
--first-name "EinsteinServiceAgent" \
|
|
39
|
+
--last-name "User" \
|
|
40
|
+
--json 2>&1) || {
|
|
41
|
+
echo "error: no Einstein Agent User found in '${ALIAS}', and auto-provisioning failed." >&2
|
|
42
|
+
echo "$CREATE_RESP" >&2
|
|
43
|
+
echo "See references/agentforce-prerequisite.md to enable Agentforce Agents or free an Agentforce Service Agent User license, then retry." >&2
|
|
44
|
+
exit 3
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
USERNAME=$(echo "$CREATE_RESP" | jq -r '.result.username // empty')
|
|
48
|
+
if [[ -z "$USERNAME" ]]; then
|
|
49
|
+
echo "error: sf org create agent-user did not return result.username:" >&2
|
|
50
|
+
echo "$CREATE_RESP" >&2
|
|
51
|
+
exit 3
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
echo "$USERNAME"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Create and deploy the two RoutingFlows of a voice Omni Flow, then return the inbound flow's
|
|
3
|
+
# FlowDefinition Id (300-prefix) for the channel's SessionHandlerId.
|
|
4
|
+
# - Inbound <api-name>_Voice_Omni_Flow : routingType=Copilot -> routes to the agent, queue as fallback
|
|
5
|
+
# - Escalation <api-name>_Voice_Escalation : routingType=QueueBased -> agent hands off to the queue
|
|
6
|
+
# Usage: create-routing-flows.sh <org-alias> <agent-api-name> <agent-label> <queue-id> <queue-name> [work-dir]
|
|
7
|
+
# work-dir defaults to ./acc-voice-build (reuse the same dir as create-voice-agent.sh)
|
|
8
|
+
# Output: the inbound FlowDefinition Id (300-prefix) on stdout.
|
|
9
|
+
# Exit 0 on success, 2 usage, 5 on deploy failure, 3 if the inbound flow did not activate.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
14
|
+
|
|
15
|
+
if [[ "${1:-}" == "--help" || $# -lt 5 ]]; then
|
|
16
|
+
echo "Usage: create-routing-flows.sh <org-alias> <agent-api-name> <agent-label> <queue-id> <queue-name> [work-dir]" >&2
|
|
17
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
ALIAS="$1"; API_NAME="$2"; LABEL="$3"; QUEUE_ID="$4"; QUEUE_NAME="$5"; WORK_DIR="${6:-acc-voice-build}"
|
|
21
|
+
|
|
22
|
+
INBOUND="${API_NAME}_Voice_Omni_Flow"
|
|
23
|
+
ESC="${API_NAME}_Voice_Escalation"
|
|
24
|
+
ASSETS_DIR="$(cd "$SCRIPT_DIR/../assets" && pwd)"
|
|
25
|
+
|
|
26
|
+
# Free-text values land in XML stringValue/label nodes — escape them so a queue name or
|
|
27
|
+
# agent label containing &, <, >, etc. can't produce malformed XML. QUEUE_ID and API_NAME
|
|
28
|
+
# are platform-constrained (Id / DeveloperName), so they pass through unescaped.
|
|
29
|
+
LABEL_X="$(xml_escape "$LABEL")"
|
|
30
|
+
QUEUE_NAME_X="$(xml_escape "$QUEUE_NAME")"
|
|
31
|
+
|
|
32
|
+
ensure_sfdx_project "$WORK_DIR"
|
|
33
|
+
WORK_DIR_ABS="$(cd "$WORK_DIR" && pwd)"
|
|
34
|
+
FLOWS_DIR="$WORK_DIR_ABS/force-app/main/default/flows"
|
|
35
|
+
mkdir -p "$FLOWS_DIR"
|
|
36
|
+
|
|
37
|
+
render_template "$ASSETS_DIR/omni-flow.flow-meta.xml" \
|
|
38
|
+
"FLOW_LABEL=${LABEL_X} Voice Omni Flow" "AGENT_API_NAME=${API_NAME}" "AGENT_LABEL=${LABEL_X}" \
|
|
39
|
+
"QUEUE_ID=${QUEUE_ID}" "QUEUE_LABEL=${QUEUE_NAME_X}" > "$FLOWS_DIR/${INBOUND}.flow-meta.xml"
|
|
40
|
+
|
|
41
|
+
render_template "$ASSETS_DIR/escalation-flow.flow-meta.xml" \
|
|
42
|
+
"FLOW_LABEL=${LABEL_X} Voice Escalation" "QUEUE_ID=${QUEUE_ID}" "QUEUE_LABEL=${QUEUE_NAME_X}" \
|
|
43
|
+
> "$FLOWS_DIR/${ESC}.flow-meta.xml"
|
|
44
|
+
|
|
45
|
+
DEP=$(cd "$WORK_DIR_ABS" && sf project deploy start --source-dir "$FLOWS_DIR" --target-org "$ALIAS" --json 2>&1) \
|
|
46
|
+
|| { echo "flow deploy failed:" >&2; echo "$DEP" >&2; exit 5; }
|
|
47
|
+
|
|
48
|
+
# The channel wires to the inbound flow's FlowDefinition Id; it must be active.
|
|
49
|
+
"$SCRIPT_DIR/resolve-flow-definition.sh" "$ALIAS" "$INBOUND"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Create, publish, and activate an Agentforce voice service agent (Omni Flow "new agent" path).
|
|
3
|
+
# Replicates the validated session flow: generate an authoring bundle from a spec, repair
|
|
4
|
+
# generator placeholders, deploy the AiAuthoringBundle, validate, publish, and activate.
|
|
5
|
+
# Runs inside a scaffolded SFDX project so the bundle can publish.
|
|
6
|
+
#
|
|
7
|
+
# DEVIATION (do not remove): do NOT change plannerType to Atlas__VoiceAgent. This org (API <= 67)
|
|
8
|
+
# rejects it with an opaque server error. Keep the publish-generated
|
|
9
|
+
# Atlas__ConcurrentMultiAgentOrchestration planner — inbound voice routes correctly via the
|
|
10
|
+
# Copilot RoutingFlow regardless of planner type. See references/omni-flow-routing.md.
|
|
11
|
+
#
|
|
12
|
+
# Usage: create-voice-agent.sh <org-alias> <agent-api-name> <agent-label> <spec-file> [work-dir]
|
|
13
|
+
# work-dir defaults to ./acc-voice-build (pass the same dir to create-routing-flows.sh)
|
|
14
|
+
# Output: the agent api-name (== BotDefinition DeveloperName) on stdout once Active.
|
|
15
|
+
# Exit 0 on Active, 2 usage, 5 on any generate/validate/publish/activate failure.
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
20
|
+
|
|
21
|
+
if [[ "${1:-}" == "--help" || $# -lt 4 ]]; then
|
|
22
|
+
echo "Usage: create-voice-agent.sh <org-alias> <agent-api-name> <agent-label> <spec-file> [work-dir]" >&2
|
|
23
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
ALIAS="$1"; API_NAME="$2"; LABEL="$3"; SPEC="$4"; WORK_DIR="${5:-acc-voice-build}"
|
|
27
|
+
|
|
28
|
+
[[ -f "$SPEC" ]] || { echo "error: spec file not found: $SPEC" >&2; exit 2; }
|
|
29
|
+
SPEC_ABS="$(cd "$(dirname "$SPEC")" && pwd)/$(basename "$SPEC")"
|
|
30
|
+
AGENT_USER=$(awk -F: '/^agentUser:[[:space:]]*/ { sub(/^[[:space:]]+/, "", $2); gsub(/^"|"$/, "", $2); print $2; exit }' "$SPEC_ABS")
|
|
31
|
+
|
|
32
|
+
ensure_sfdx_project "$WORK_DIR"
|
|
33
|
+
cd "$WORK_DIR"
|
|
34
|
+
|
|
35
|
+
GEN=$(sf agent generate authoring-bundle --spec "$SPEC_ABS" --name "$LABEL" --api-name "$API_NAME" \
|
|
36
|
+
--output-dir force-app/main/default --target-org "$ALIAS" --json 2>&1) \
|
|
37
|
+
|| { echo "authoring-bundle generation failed:" >&2; echo "$GEN" >&2; exit 5; }
|
|
38
|
+
|
|
39
|
+
AGENT_FILE="force-app/main/default/aiAuthoringBundles/${API_NAME}/${API_NAME}.agent"
|
|
40
|
+
if [[ -n "$AGENT_USER" && -f "$AGENT_FILE" ]]; then
|
|
41
|
+
tmp_file="$(mktemp)"
|
|
42
|
+
awk -v user="$AGENT_USER" '
|
|
43
|
+
/^[[:space:]]*default_agent_user:/ {
|
|
44
|
+
match($0, /^[[:space:]]*/)
|
|
45
|
+
print substr($0, RSTART, RLENGTH) "default_agent_user: \"" user "\""
|
|
46
|
+
next
|
|
47
|
+
}
|
|
48
|
+
{ print }
|
|
49
|
+
' "$AGENT_FILE" > "$tmp_file"
|
|
50
|
+
mv "$tmp_file" "$AGENT_FILE"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
DEP=$(sf project deploy start --source-dir "force-app/main/default/aiAuthoringBundles/${API_NAME}" \
|
|
54
|
+
--target-org "$ALIAS" --json 2>&1) \
|
|
55
|
+
|| { echo "authoring-bundle deploy failed:" >&2; echo "$DEP" >&2; exit 5; }
|
|
56
|
+
|
|
57
|
+
VAL=$(sf agent validate authoring-bundle --api-name "$API_NAME" --target-org "$ALIAS" --json 2>&1) \
|
|
58
|
+
|| { echo "agent validate failed:" >&2; echo "$VAL" >&2; exit 5; }
|
|
59
|
+
|
|
60
|
+
PUB=$(sf agent publish authoring-bundle --api-name "$API_NAME" --target-org "$ALIAS" --json 2>&1) \
|
|
61
|
+
|| { echo "agent publish failed:" >&2; echo "$PUB" >&2; exit 5; }
|
|
62
|
+
|
|
63
|
+
# --json + --api-name is non-interactive: with --json and no --version the CLI auto-activates
|
|
64
|
+
# the latest version and does not prompt, so no confirmation needs to be piped in.
|
|
65
|
+
ACT=$(sf agent activate --api-name "$API_NAME" --target-org "$ALIAS" --json 2>&1) \
|
|
66
|
+
|| { echo "agent activate failed:" >&2; echo "$ACT" >&2; exit 5; }
|
|
67
|
+
|
|
68
|
+
STATUS=$(sf data query \
|
|
69
|
+
--query "SELECT Status FROM BotVersion WHERE BotDefinition.DeveloperName = '${API_NAME}' ORDER BY VersionNumber DESC LIMIT 1" \
|
|
70
|
+
--target-org "$ALIAS" --json 2>/dev/null | jq -r '.result.records[0].Status // empty')
|
|
71
|
+
|
|
72
|
+
[[ "$STATUS" == "Active" ]] || { echo "error: agent '${API_NAME}' is not Active (status: ${STATUS:-unknown})" >&2; exit 5; }
|
|
73
|
+
echo "$API_NAME"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Create an active PstnVoice MessagingChannel for a procured number.
|
|
3
|
+
# Usage: create-voice-channel.sh <org-alias> <phone-number> <channel-line-id> <session-handler-id> [fallback-queue-id]
|
|
4
|
+
# Omni Queue (4 args): pass the ACC queue Id as <session-handler-id>.
|
|
5
|
+
# Omni Flow (5 args): pass the inbound FlowDefinition Id (300-prefix) as <session-handler-id>
|
|
6
|
+
# and the fallback queue Id as [fallback-queue-id].
|
|
7
|
+
# Output: JSON create response on stdout. Exit 0 only when the response is
|
|
8
|
+
# "success": true; exit 5 otherwise.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
13
|
+
|
|
14
|
+
if [[ "${1:-}" == "--help" || $# -lt 4 ]]; then
|
|
15
|
+
echo "Usage: create-voice-channel.sh <org-alias> <phone-number> <channel-line-id> <session-handler-id> [fallback-queue-id]" >&2
|
|
16
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
ALIAS="$1"; NUMBER="$2"; CHANNEL_LINE_ID="$3"; SESSION_HANDLER_ID="$4"; FALLBACK_QUEUE_ID="${5:-}"
|
|
20
|
+
|
|
21
|
+
DEV_NAME="Voice_Channel_${NUMBER//+/}"
|
|
22
|
+
ENDPOINT="/services/data/${API_VERSION}/sobjects/MessagingChannel/"
|
|
23
|
+
|
|
24
|
+
post_channel() {
|
|
25
|
+
local err_file out status
|
|
26
|
+
err_file="$(mktemp)"
|
|
27
|
+
set +e
|
|
28
|
+
out=$(sf api request rest "$ENDPOINT" \
|
|
29
|
+
--method POST \
|
|
30
|
+
--body "$1" \
|
|
31
|
+
--target-org "$ALIAS" 2>"$err_file")
|
|
32
|
+
status=$?
|
|
33
|
+
set -e
|
|
34
|
+
|
|
35
|
+
if [[ -n "$out" ]]; then
|
|
36
|
+
printf '%s\n' "$out"
|
|
37
|
+
else
|
|
38
|
+
cat "$err_file"
|
|
39
|
+
fi
|
|
40
|
+
rm -f "$err_file"
|
|
41
|
+
return "$status"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# SessionHandlerId is the queue Id (Omni Queue) or the inbound FlowDefinition Id (Omni Flow).
|
|
45
|
+
# On the Omni Flow path also set FallbackQueueId to the queue the agent overflows/escalates to.
|
|
46
|
+
if [[ -n "$FALLBACK_QUEUE_ID" ]]; then
|
|
47
|
+
BODY=$(jq -n \
|
|
48
|
+
--arg dn "$DEV_NAME" --arg num "$NUMBER" --arg cl "$CHANNEL_LINE_ID" \
|
|
49
|
+
--arg sh "$SESSION_HANDLER_ID" --arg fq "$FALLBACK_QUEUE_ID" \
|
|
50
|
+
'{DeveloperName:$dn, MasterLabel:("Voice Channel " + $num), MessageType:"PstnVoice",
|
|
51
|
+
MessagingPlatformKey:$num, ChannelLineId:$cl, IsActive:true, SessionHandlerId:$sh, FallbackQueueId:$fq}')
|
|
52
|
+
else
|
|
53
|
+
BODY=$(jq -n \
|
|
54
|
+
--arg dn "$DEV_NAME" --arg num "$NUMBER" --arg cl "$CHANNEL_LINE_ID" --arg sh "$SESSION_HANDLER_ID" \
|
|
55
|
+
'{DeveloperName:$dn, MasterLabel:("Voice Channel " + $num), MessageType:"PstnVoice",
|
|
56
|
+
MessagingPlatformKey:$num, ChannelLineId:$cl, IsActive:true, SessionHandlerId:$sh}')
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
RESP=$(post_channel "$BODY") || true
|
|
60
|
+
|
|
61
|
+
echo "$RESP"
|
|
62
|
+
|
|
63
|
+
# The response is emitted above for the caller to parse; set the exit code so a
|
|
64
|
+
# failed create (error payload, non-live number, duplicate name) can't be mistaken
|
|
65
|
+
# for success by a caller that only checks the exit status.
|
|
66
|
+
echo "$RESP" | jq -e '.success == true' >/dev/null 2>&1 || exit 5
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fetch available default phone numbers for a country + phone-number type.
|
|
3
|
+
# Usage: fetch-numbers.sh <org-alias> <country> <phone-type>
|
|
4
|
+
# country: US | CA
|
|
5
|
+
# phone-type: 10DLC | "Toll Free"
|
|
6
|
+
# Output: JSON { "phoneNumbers": [...] } on stdout.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
11
|
+
|
|
12
|
+
if [[ "${1:-}" == "--help" || $# -lt 3 ]]; then
|
|
13
|
+
echo "Usage: fetch-numbers.sh <org-alias> <country> <phone-type>" >&2
|
|
14
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
ALIAS="$1"; COUNTRY="$2"; PHONE_TYPE="$3"
|
|
18
|
+
|
|
19
|
+
sf api request rest \
|
|
20
|
+
"/${NM_BASE}/numbers?countryCode=${COUNTRY}&phoneNumberType=$(urlenc_spaces "$PHONE_TYPE")" \
|
|
21
|
+
--target-org "$ALIAS"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared constants for the Agentforce Contact Center coordination scripts.
|
|
3
|
+
# Not invoked directly — sourced by the other scripts.
|
|
4
|
+
#
|
|
5
|
+
# All API calls go through `sf api request rest`, which uses the CLI's stored
|
|
6
|
+
# session for --target-org. Never extract the access token from `sf org display`.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
API_VERSION="v68.0"
|
|
11
|
+
NM_BASE="services/data/${API_VERSION}/connect/number-management/v1"
|
|
12
|
+
|
|
13
|
+
# URL-encode a value's spaces (sufficient for country/phoneType query params).
|
|
14
|
+
urlenc_spaces() { printf '%s' "${1// /%20}"; }
|
|
15
|
+
|
|
16
|
+
# XML-escape a value for safe interpolation into flow-template stringValue/label nodes.
|
|
17
|
+
# Free-text org data (queue names, agent labels) can contain &, <, >, ", ' — unescaped
|
|
18
|
+
# they produce malformed XML and an opaque `sf project deploy start` parse error.
|
|
19
|
+
# Escape & first so the entities emitted by the later rules aren't double-escaped.
|
|
20
|
+
xml_escape() { printf '%s' "$1" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/g'; }
|
|
21
|
+
|
|
22
|
+
# Scaffold a minimal SFDX project at <dir> if one isn't already there. The Omni Flow path
|
|
23
|
+
# generates an agent authoring bundle and deploys RoutingFlows, both of which need a project
|
|
24
|
+
# context. sourceApiVersion is pinned to API_VERSION for consistency (v68).
|
|
25
|
+
ensure_sfdx_project() {
|
|
26
|
+
local dir="$1"
|
|
27
|
+
mkdir -p "$dir/force-app/main/default"
|
|
28
|
+
if [[ ! -f "$dir/sfdx-project.json" ]]; then
|
|
29
|
+
printf '%s\n' "{\"packageDirectories\":[{\"path\":\"force-app\",\"default\":true}],\"namespace\":\"\",\"sfdcLoginUrl\":\"https://login.salesforce.com\",\"sourceApiVersion\":\"${API_VERSION#v}\"}" \
|
|
30
|
+
> "$dir/sfdx-project.json"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Render a token template to stdout, replacing every {{KEY}} with the paired value.
|
|
35
|
+
# Usage: render_template <template-file> KEY=VALUE [KEY=VALUE ...]
|
|
36
|
+
# Plain string replacement (no regex) so XML/URL values pass through unchanged.
|
|
37
|
+
render_template() {
|
|
38
|
+
local tmpl="$1"; shift
|
|
39
|
+
local content pair key val
|
|
40
|
+
content="$(<"$tmpl")"
|
|
41
|
+
for pair in "$@"; do
|
|
42
|
+
key="${pair%%=*}"; val="${pair#*=}"
|
|
43
|
+
content="${content//\{\{${key}\}\}/${val}}"
|
|
44
|
+
done
|
|
45
|
+
printf '%s\n' "$content"
|
|
46
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Prepare a minimal SFDX project work directory for Agentforce agent generation.
|
|
3
|
+
# `sf agent generate agent-spec` requires Salesforce project context before the
|
|
4
|
+
# authoring-bundle script has a chance to call ensure_sfdx_project.
|
|
5
|
+
# Usage: prepare-agent-workdir.sh [work-dir]
|
|
6
|
+
# Output: absolute work-dir path on stdout.
|
|
7
|
+
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
11
|
+
|
|
12
|
+
if [[ "${1:-}" == "--help" ]]; then
|
|
13
|
+
echo "Usage: prepare-agent-workdir.sh [work-dir]" >&2
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
WORK_DIR="${1:-acc-voice-build}"
|
|
18
|
+
ensure_sfdx_project "$WORK_DIR"
|
|
19
|
+
mkdir -p "$WORK_DIR/specs"
|
|
20
|
+
cd "$WORK_DIR"
|
|
21
|
+
pwd
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Procure a selected phone number.
|
|
3
|
+
# Usage: procure-number.sh <org-alias> <country> <phone-type> <phone-number>
|
|
4
|
+
# Output: JSON response body on stdout. Non-zero exit on API error.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
9
|
+
|
|
10
|
+
if [[ "${1:-}" == "--help" || $# -lt 4 ]]; then
|
|
11
|
+
echo "Usage: procure-number.sh <org-alias> <country> <phone-type> <phone-number>" >&2
|
|
12
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
ALIAS="$1"; COUNTRY="$2"; PHONE_TYPE="$3"; NUMBER="$4"
|
|
16
|
+
|
|
17
|
+
BODY=$(jq -n --arg t "$PHONE_TYPE" --arg c "$COUNTRY" --arg p "$NUMBER" \
|
|
18
|
+
'{phoneNumberType:$t, country:$c, phoneNumber:$p}')
|
|
19
|
+
|
|
20
|
+
sf api request rest \
|
|
21
|
+
"/${NM_BASE}/number" \
|
|
22
|
+
--method POST \
|
|
23
|
+
--body "$BODY" \
|
|
24
|
+
--target-org "$ALIAS"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve the Agentforce Contact Center default queue Id by name (never hardcoded — org-specific).
|
|
3
|
+
# Usage: resolve-acc-queue.sh <org-alias> [queue-name]
|
|
4
|
+
# queue-name defaults to "Default Queue Agentforce Contact Center"
|
|
5
|
+
# Output: the queue Id on stdout. Exit 1 if not found.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
10
|
+
|
|
11
|
+
if [[ "${1:-}" == "--help" || $# -lt 1 ]]; then
|
|
12
|
+
echo "Usage: resolve-acc-queue.sh <org-alias> [queue-name]" >&2
|
|
13
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
ALIAS="$1"
|
|
17
|
+
QUEUE_NAME="${2:-Default Queue Agentforce Contact Center}"
|
|
18
|
+
|
|
19
|
+
ID=$(sf data query \
|
|
20
|
+
--query "SELECT Id FROM Group WHERE Type = 'Queue' AND Name = '${QUEUE_NAME}' LIMIT 1" \
|
|
21
|
+
--target-org "$ALIAS" --json 2>/dev/null | jq -r '.result.records[0].Id // empty')
|
|
22
|
+
|
|
23
|
+
if [[ -z "$ID" ]]; then
|
|
24
|
+
echo "error: queue '${QUEUE_NAME}' not found in org '${ALIAS}'" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
echo "$ID"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve the CommunicationChannelLine for a procured number, retrying while it propagates.
|
|
3
|
+
# Usage: resolve-channel-line.sh <org-alias> <phone-number>
|
|
4
|
+
# Output: JSON { "id": "...", "codeStatus": "..." } on success. Exit 1 if not found
|
|
5
|
+
# after retries; 2 on usage error; 4 if the query API returns an error envelope.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
10
|
+
|
|
11
|
+
if [[ "${1:-}" == "--help" || $# -lt 2 ]]; then
|
|
12
|
+
echo "Usage: resolve-channel-line.sh <org-alias> <phone-number>" >&2
|
|
13
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
ALIAS="$1"; NUMBER="$2"
|
|
17
|
+
ENCODED_PHONE="${NUMBER//+/%2B}"
|
|
18
|
+
QUERY="SELECT+Id,CodeStatus+FROM+CommunicationChannelLine+WHERE+Code='${ENCODED_PHONE}'+LIMIT+1"
|
|
19
|
+
|
|
20
|
+
for attempt in 1 2 3; do
|
|
21
|
+
DATA=$(sf api request rest \
|
|
22
|
+
"/services/data/${API_VERSION}/tooling/query?q=${QUERY}" \
|
|
23
|
+
--target-org "$ALIAS" 2>/dev/null || true)
|
|
24
|
+
# A success query is an object; an API error is a JSON array [{errorCode,...}].
|
|
25
|
+
# Stop on the error shape instead of letting jq crash on `.records` below.
|
|
26
|
+
echo "$DATA" | jq -e 'type == "array"' >/dev/null 2>&1 && { echo "error: query failed for ${NUMBER}: $DATA" >&2; exit 4; }
|
|
27
|
+
ID=$(echo "$DATA" | jq -r '.records[0].Id // empty')
|
|
28
|
+
STATUS=$(echo "$DATA" | jq -r '.records[0].CodeStatus // empty')
|
|
29
|
+
if [[ -n "$ID" ]]; then
|
|
30
|
+
jq -n --arg id "$ID" --arg s "$STATUS" '{id:$id, codeStatus:$s}'
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
[[ $attempt -lt 3 ]] && sleep 3
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
echo "error: CommunicationChannelLine not found for ${NUMBER} after 3 attempts" >&2
|
|
37
|
+
exit 1
|
package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-flow-definition.sh
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve a RoutingFlow's FlowDefinition Id (300-prefix) by DeveloperName via the Tooling API.
|
|
3
|
+
# Used for the channel's SessionHandlerId on the Omni Flow path, and to validate an existing
|
|
4
|
+
# inbound flow when the user reuses one. FlowDefinition (300) is the stable handle — not the
|
|
5
|
+
# per-version Flow (301) Id.
|
|
6
|
+
# Usage: resolve-flow-definition.sh <org-alias> <flow-developer-name>
|
|
7
|
+
# Output: the 300-prefix FlowDefinition Id on stdout.
|
|
8
|
+
# Exit 0 if active, 2 usage, 1 if not found, 3 if found but not activated (ActiveVersionId null).
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/lib.sh"
|
|
13
|
+
|
|
14
|
+
if [[ "${1:-}" == "--help" || $# -lt 2 ]]; then
|
|
15
|
+
echo "Usage: resolve-flow-definition.sh <org-alias> <flow-developer-name>" >&2
|
|
16
|
+
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
ALIAS="$1"; DEVNAME="$2"
|
|
20
|
+
|
|
21
|
+
RESP=$(sf api request rest \
|
|
22
|
+
"/services/data/${API_VERSION}/tooling/query?q=SELECT+Id,DeveloperName,ActiveVersionId+FROM+FlowDefinition+WHERE+DeveloperName='${DEVNAME}'+LIMIT+1" \
|
|
23
|
+
--target-org "$ALIAS" 2>/dev/null || true)
|
|
24
|
+
|
|
25
|
+
# A success query is an object; an API error is a JSON array [{errorCode,...}].
|
|
26
|
+
echo "$RESP" | jq -e 'type == "array"' >/dev/null 2>&1 && { echo "error: FlowDefinition query failed for ${DEVNAME}: $RESP" >&2; exit 1; }
|
|
27
|
+
|
|
28
|
+
ID=$(echo "$RESP" | jq -r '.records[0].Id // empty')
|
|
29
|
+
ACTIVE=$(echo "$RESP" | jq -r '.records[0].ActiveVersionId // empty')
|
|
30
|
+
|
|
31
|
+
[[ -n "$ID" ]] || { echo "error: FlowDefinition '${DEVNAME}' not found in '${ALIAS}'" >&2; exit 1; }
|
|
32
|
+
[[ -n "$ACTIVE" ]] || { echo "error: FlowDefinition '${DEVNAME}' exists but has no active version" >&2; exit 3; }
|
|
33
|
+
echo "$ID"
|