@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.
Files changed (24) hide show
  1. package/package.json +3 -3
  2. package/skills/service-agentforce-contact-center-coordinate/SKILL.md +170 -0
  3. package/skills/service-agentforce-contact-center-coordinate/assets/escalation-flow.flow-meta.xml +72 -0
  4. package/skills/service-agentforce-contact-center-coordinate/assets/omni-flow.flow-meta.xml +85 -0
  5. package/skills/service-agentforce-contact-center-coordinate/assets/report-template.md +52 -0
  6. package/skills/service-agentforce-contact-center-coordinate/references/agentforce-prerequisite.md +31 -0
  7. package/skills/service-agentforce-contact-center-coordinate/references/messaging_channel.md +76 -0
  8. package/skills/service-agentforce-contact-center-coordinate/references/number_management_api.md +78 -0
  9. package/skills/service-agentforce-contact-center-coordinate/references/omni-flow-routing.md +74 -0
  10. package/skills/service-agentforce-contact-center-coordinate/references/setup_summary.md +36 -0
  11. package/skills/service-agentforce-contact-center-coordinate/references/verification_and_errors.md +42 -0
  12. package/skills/service-agentforce-contact-center-coordinate/scripts/check-agentforce-prereq.sh +54 -0
  13. package/skills/service-agentforce-contact-center-coordinate/scripts/create-routing-flows.sh +49 -0
  14. package/skills/service-agentforce-contact-center-coordinate/scripts/create-voice-agent.sh +73 -0
  15. package/skills/service-agentforce-contact-center-coordinate/scripts/create-voice-channel.sh +66 -0
  16. package/skills/service-agentforce-contact-center-coordinate/scripts/fetch-numbers.sh +21 -0
  17. package/skills/service-agentforce-contact-center-coordinate/scripts/lib.sh +46 -0
  18. package/skills/service-agentforce-contact-center-coordinate/scripts/prepare-agent-workdir.sh +21 -0
  19. package/skills/service-agentforce-contact-center-coordinate/scripts/procure-number.sh +24 -0
  20. package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-acc-queue.sh +27 -0
  21. package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-channel-line.sh +37 -0
  22. package/skills/service-agentforce-contact-center-coordinate/scripts/resolve-flow-definition.sh +33 -0
  23. package/skills/service-agentforce-contact-center-coordinate/scripts/verify-number-live.sh +63 -0
  24. 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.
@@ -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) |
@@ -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/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/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
@@ -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"