@salesforce/afv-skills 1.65.1 → 1.67.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 +2 -4
- package/skills/agentforce-offline-experimentation-run/SKILL.md +29 -43
- package/skills/agentforce-offline-experimentation-run/scripts/sfap_replay.py +3 -245
- package/skills/commerce-react-storefront-create/SKILL.md +350 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +2 -2
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +28 -16
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +18 -1
- package/skills/dx-devops-work-item-manage/SKILL.md +34 -17
- package/skills/dx-devops-work-item-manage/examples/common-workflows.md +49 -0
- package/skills/dx-devops-work-item-manage/references/commit-api.md +116 -0
- package/skills/education-cloud-domain-configure/SKILL.md +21 -23
- package/skills/education-cloud-domain-configure/references/dependent-features.md +9 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +15 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +8 -0
- package/skills/education-cloud-domain-configure/references/setup-widgets.md +123 -0
- package/skills/experience-lwc-widget-assess/SKILL.md +79 -0
- package/skills/experience-lwc-widget-migrate/SKILL.md +50 -0
- package/skills/experience-search-coordinate/SKILL.md +2 -2
- package/skills/experience-search-coordinate/references/media-route.md +30 -4
- package/skills/experience-ui-bundle-features-generate/SKILL.md +1 -1
- package/skills/experience-ui-bundle-mfa-configure/SKILL.md +1 -1
- package/skills/platform-metadata-deploy/README.md +1 -1
- package/skills/platform-metadata-deploy/SKILL.md +114 -15
- package/skills/platform-metadata-deploy/references/agent-deployment-guide.md +3 -4
- package/skills/platform-metadata-deploy/references/orchestration.md +2 -3
- package/skills/platform-role-hierarchy-apply/SKILL.md +177 -0
- package/skills/platform-value-set-generate/SKILL.md +19 -19
- package/skills/platform-widget-generate/SKILL.md +59 -68
- package/skills/platform-widget-generate/examples/formulas.json +19 -2
- package/skills/platform-widget-generate/examples/localized-labels.json +57 -0
- package/skills/platform-widget-generate/references/widget-actions.md +29 -0
- package/skills/platform-widget-generate/references/widget-bundle-validation.md +15 -0
- package/skills/platform-widget-generate/references/widget-formulas.md +24 -7
- package/skills/platform-widget-generate/references/widget-gotchas.md +10 -0
- package/skills/platform-widget-generate/references/widget-labels.md +78 -0
- package/skills/platform-widget-generate/references/widget-styling.md +12 -0
- package/skills/sales-call-scoring-configure/SKILL.md +17 -10
- package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +55 -33
- package/skills/service-agentforce-contact-center-coordinate/SKILL.md +1 -1
- package/skills/service-agentforce-contact-center-coordinate/references/number_management_api.md +11 -1
- package/skills/service-agentforce-contact-center-coordinate/scripts/fetch-numbers.sh +12 -6
- package/skills/service-agentforce-contact-center-coordinate/scripts/lib.sh +16 -0
- package/skills/service-agentforce-contact-center-coordinate/scripts/procure-number.sh +2 -1
- package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +24 -11
- package/skills/service-digital-engagement-channel-configure/SKILL.md +29 -16
- package/skills/service-digital-engagement-channel-configure/references/channel_settings.md +13 -2
- package/skills/service-digital-engagement-channel-configure/references/onboarding_contract.md +143 -0
- package/skills/service-digital-engagement-deployment-configure/SKILL.md +65 -79
- package/skills/service-digital-engagement-deployment-configure/assets/esd_web_update_template.xml +83 -8
- package/skills/service-digital-engagement-deployment-configure/examples/esd_web_prechat_fields.xml +74 -0
- package/skills/service-digital-engagement-deployment-configure/references/connect_api_creation.md +26 -7
- package/skills/service-digital-engagement-deployment-configure/references/deployment_settings.md +13 -78
- package/skills/service-digital-engagement-deployment-configure/references/onboarding_contract.md +145 -0
- package/skills/service-digital-engagement-deployment-configure/references/prechat_forms.md +245 -0
- package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +166 -112
- package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +61 -37
- package/skills/service-itsm-agentic-setup-incident-management/references/audit-tracking.md +42 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/broadcast-communications.md +23 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/email-to-incident.md +23 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-dashboards.md +25 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +30 -145
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +32 -137
- package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +32 -155
- package/skills/service-itsm-agentic-setup-incident-management/references/manage-incidents-with-ai.md +45 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/priority-matrix.md +32 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +30 -140
- package/skills/service-itsm-agentic-setup-incident-management/references/sla-milestones.md +110 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +34 -25
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +1 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +90 -86
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +63 -38
|
@@ -17,6 +17,7 @@ Every formula expression is wrapped in `{!...}`, similar to a plain binding:
|
|
|
17
17
|
- Functions and operators are case-sensitive UPPERCASE.
|
|
18
18
|
- A formula can nest functions and mix functions with operators: `{!TEXT(ROUND($attrs.property, 2))}`, `{!LEFT($attrs.property, 5) & "..."}`.
|
|
19
19
|
- A formula-bearing attribute is still bound to schema properties the same way as a plain binding — every `$attrs.X` / `$item.X` referenced inside a formula must resolve to a real schema property or loop variable, per the `bindings-resolve` self-validation check.
|
|
20
|
+
- A Custom Label reference `$Label.c.<LabelName>` can be used as a text value inside a formula. See `references/widget-labels.md`.
|
|
20
21
|
|
|
21
22
|
---
|
|
22
23
|
|
|
@@ -34,7 +35,7 @@ Only the operators/functions listed here are confirmed supported — do not inve
|
|
|
34
35
|
| `/` | Divide | `$attrs.operand1 / $attrs.operand2` |
|
|
35
36
|
| `^` | Exponent | `$attrs.operand1 ^ $attrs.operand2` |
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
Unary minus (`-$attrs.fieldName`) is also supported. Precedence, lowest to highest: `||`, `&&`, equality, relational, `+` `-` `&`, `^`, `*` `/`, unary. Note that `*` and `/` bind tighter than `^`, so `2 * 3 ^ 2` is `(2 * 3) ^ 2`; parenthesize to be explicit.
|
|
38
39
|
|
|
39
40
|
### 2. Comparison operators
|
|
40
41
|
|
|
@@ -47,36 +48,40 @@ Return `true`/`false`; commonly used inside `IF()` or `meta.if`.
|
|
|
47
48
|
| `>` / `<` | Greater / less than | `$attrs.property > 1000` |
|
|
48
49
|
| `>=` / `<=` | Greater-or-equal / less-or-equal | `$attrs.property1 >= $attrs.property2` |
|
|
49
50
|
|
|
51
|
+
`==` and `!=` are accepted as aliases for `=` and `<>`. Both operands must have the same type; text is never compared with a number.
|
|
52
|
+
|
|
50
53
|
### 3. Logical functions
|
|
51
54
|
|
|
52
55
|
| Function | Description | Example |
|
|
53
56
|
|---|---|------------------------------------------------------------------------------------------------------|
|
|
54
|
-
| `IF(cond, then, else)` | Conditional value; nests for multi-branch logic | `IF($attrs.property > value, 'result1', 'result2')` |
|
|
57
|
+
| `IF(cond, then, else)` | Conditional value; nests for multi-branch logic. `cond` must be a boolean (`IF($attrs.count > 0, …)`, not `IF($attrs.count, …)`) and both branches must have the same type | `IF($attrs.property > value, 'result1', 'result2')` |
|
|
55
58
|
| `CASE(expr, val1, res1, val2, res2, ..., default)` | Multi-way match, cleaner than nested `IF` for 3+ branches | `CASE($attrs.property, 'value1', 'result1', 'value2', 'result2', 'value3', 'result3', 'defaultResult')` |
|
|
56
59
|
| `AND(...)` | All conditions true | `AND($attrs.property1, $attrs.property2 > value)` |
|
|
57
60
|
| `OR(...)` | Any condition true | `OR($attrs.property1, $attrs.property2 > value)` |
|
|
58
61
|
| `NOT(val)` | Negate a boolean | `NOT($attrs.property)` |
|
|
59
|
-
|
|
|
62
|
+
| `&&` / `\|\|` / `!` (operators) | Aliases for `AND` / `OR` / `NOT` | `$attrs.property1 && !$attrs.property2` |
|
|
63
|
+
| `ISNULL(val)` | True if the value is null. Always false for text — use `ISBLANK` for strings | `IF(ISNULL($attrs.property), 'null', 'has-value')` |
|
|
60
64
|
| `ISBLANK(val)` | True if null or empty string | `IF(ISBLANK($attrs.property), 'blank', 'has-value')` |
|
|
61
65
|
|
|
62
66
|
### 4. Text functions
|
|
63
67
|
|
|
64
68
|
| Function | Description | Example |
|
|
65
69
|
|---|---|-----------------------------------------------------|
|
|
66
|
-
| `UPPER(str)` / `LOWER(str)` | Case conversion | `UPPER($attrs.property)` |
|
|
70
|
+
| `UPPER(str[, locale])` / `LOWER(str[, locale])` | Case conversion, optionally for a locale such as `'tr'` | `UPPER($attrs.property)` |
|
|
67
71
|
| `LEFT(str, n)` / `RIGHT(str, n)` | First/last `n` characters | `LEFT($attrs.property, 3)` |
|
|
68
72
|
| `MID(str, start, len)` | Substring, **1-based** start index (first character is position 1, not 0) | `MID($attrs.property, 1, 3)` |
|
|
73
|
+
| `SUBSTR(str, start[, len])` | Substring with a **1-based** start; a negative `start` counts from the end; without `len`, runs to the end | `SUBSTR($attrs.property, -4)` |
|
|
69
74
|
| `LEN(val)` | Length of a string | `LEN($attrs.property)` |
|
|
70
75
|
| `TRIM(str)` | Strip leading/trailing whitespace | `TRIM($attrs.property)` |
|
|
71
76
|
| `REVERSE(str)` | Reverse a string | `REVERSE($attrs.property)` |
|
|
72
77
|
| `SUBSTITUTE(str, old, new)` | Replace all occurrences | `SUBSTITUTE($attrs.property, 'string1', 'string2')` |
|
|
73
|
-
| `FIND(search, str)` | 1-based index of `search` in `str`, or
|
|
78
|
+
| `FIND(search, str[, start])` | 1-based index of `search` in `str`, or `0` if absent; optional 1-based `start` position | `FIND('string', $attrs.property)` |
|
|
74
79
|
| `LPAD(str, len, pad)` / `RPAD(str, len, pad)` | Pad to a fixed width | `LPAD($attrs.property, 12, '.')` |
|
|
75
80
|
| `TEXT(val)` | Coerce a non-string value to text (commonly paired with `ROUND`) | `TEXT(ROUND($attrs.property, 2))` |
|
|
76
81
|
| `VALUE(str)` | Parse text to a number (inverse of `TEXT`) | `VALUE($attrs.property)` |
|
|
77
82
|
| `CONTAINS(str, sub)` | String contains substring — boolean, pair with `IF` | `CONTAINS($attrs.property, 'string')` |
|
|
78
83
|
| `BEGINS(str, prefix)` | String starts with — boolean, pair with `IF` | `BEGINS($attrs.property, 'string')` |
|
|
79
|
-
| `&` (operator) | Concatenate strings | `$attrs.property1 & ' — ' & $attrs.property2` |
|
|
84
|
+
| `&` (operator) | Concatenate strings. Both sides must be text, so wrap numbers and dates in `TEXT()` — `'Total: ' & TEXT($attrs.total)` | `$attrs.property1 & ' — ' & $attrs.property2` |
|
|
80
85
|
|
|
81
86
|
### 5. Math functions
|
|
82
87
|
|
|
@@ -99,7 +104,7 @@ Return `true`/`false`; commonly used inside `IF()` or `meta.if`.
|
|
|
99
104
|
| `DAY(date)` / `MONTH(date)` / `YEAR(date)` | Extract date components | `DAY(DATEVALUE($attrs.fieldName))` |
|
|
100
105
|
| `WEEKDAY(date)` | Day of week, `1`=Sunday … `7`=Saturday | `WEEKDAY(DATEVALUE($attrs.fieldName))` |
|
|
101
106
|
| `ADDMONTHS(date, n)` | Add `n` whole months to a date | `ADDMONTHS(DATEVALUE($attrs.fieldName), 3)` |
|
|
102
|
-
| `TODAY()` / `NOW()` / `TIMENOW()` | Current
|
|
107
|
+
| `TODAY()` / `NOW()` / `TIMENOW()` | Current date / datetime / time in UTC — no arguments | `TODAY()` |
|
|
103
108
|
| `DATE(year, month, day)` | Construct a date literal | `DATE(2026, 12, 31)` |
|
|
104
109
|
| `HOUR(dt)` / `MINUTE(dt)` / `SECOND(dt)` / `MILLISECOND(dt)` | Extract time components from a datetime | `HOUR(NOW())` |
|
|
105
110
|
|
|
@@ -121,3 +126,15 @@ A raw date/datetime **string** attribute (e.g. an ISO string from `$attrs`) must
|
|
|
121
126
|
| `ISNUMBER(val)` | True if the value parses as a number | `ISNUMBER($attrs.fieldName)` |
|
|
122
127
|
| `REGEX(str, pattern)` | True if `str` matches the regex `pattern` (standard regex string) | `REGEX($attrs.fieldName, '^[A-Z]{2} [0-9]+$')` |
|
|
123
128
|
|
|
129
|
+
### 9. Locale formatting functions
|
|
130
|
+
|
|
131
|
+
Format values for the viewer's locale. A null or blank input returns null. The optional last argument is a decimal count for the number functions and `'short'` (default), `'medium'` or `'long'` for the date and time functions.
|
|
132
|
+
|
|
133
|
+
| Function | Description | Example |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `FORMATNUMBER(num[, decimals])` | Locale-formatted number | `FORMATNUMBER($attrs.fieldName, 2)` |
|
|
136
|
+
| `FORMATPERCENT(num[, decimals])` | Locale-formatted percent (`0.25` → `25%`) | `FORMATPERCENT($attrs.fieldName, 1)` |
|
|
137
|
+
| `FORMATCURRENCY(num)` | Currency code followed by the amount, e.g. `USD 1,234.50` (no currency symbol) | `FORMATCURRENCY($attrs.fieldName)` |
|
|
138
|
+
| `FORMATDATE(date[, style])` | Locale date | `FORMATDATE($attrs.fieldName, 'medium')` |
|
|
139
|
+
| `FORMATDATETIME(dt[, style])` | Locale date and time | `FORMATDATETIME($attrs.fieldName, 'long')` |
|
|
140
|
+
| `FORMATTIME(time[, style])` | Locale time | `FORMATTIME($attrs.fieldName)` |
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Widget Generation Gotchas
|
|
2
|
+
|
|
3
|
+
| Issue | Resolution |
|
|
4
|
+
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
5
|
+
| `getUiComponentSchemas` returns a partial-failure entry | Pick a different block from `discoverUiComponents`; do not silently continue without a schema |
|
|
6
|
+
| Body references `{!$attrs.foo}` but `foo` is not under `schema.json` `properties.attributes.properties` | Add `foo` to `schema.json` `properties.attributes.properties` OR remove the body reference |
|
|
7
|
+
| Output written outside `<pkgDir>/uiWidgets/<widgetName>/` | `<pkgDir>` = `<packageDirectories[].path>/main/default` (see `references/widget-bundle-layout.md`). Dropping the `main/default/` segment is the common cause of widgets landing at `force-app/uiWidgets/...` instead of `force-app/main/default/uiWidgets/...` |
|
|
8
|
+
| `if` bound directly to a raw string or number relies on truthiness and is unreliable | Bind to a `lightning__booleanType` property, or use a formula comparison that evaluates to boolean |
|
|
9
|
+
| `tile/button` renders but does nothing when clicked | No `actions.click` entry was set — an action-less button renders disabled by design. Add one (see SKILL.md *Actions*) |
|
|
10
|
+
| Using `action/sendMessage` for pure navigation, or `action/openLink` when the agent should respond | `action/openLink` is synchronous and does not consume a turn; `action/sendMessage` is asynchronous and earns a fresh turn. Pick the one matching the intended UX |
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Widget Localization with Custom Labels
|
|
2
|
+
|
|
3
|
+
A widget shows translated text by referencing a Salesforce Custom Label instead of a hardcoded string.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Syntax
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
{!$Label.c.<LabelName>}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `c` is the org's default namespace. Always write it.
|
|
14
|
+
- The label name is static. Never build it from data — `{!$Label.c.$attrs.key}` can't be validated when the widget is saved.
|
|
15
|
+
- A `$Label` reference is not a `$attrs` binding and has no entry in `schema.json`.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What to localize
|
|
20
|
+
|
|
21
|
+
Replace every static, user-visible string in an allowed attribute, unless the user limited the request to specific strings.
|
|
22
|
+
|
|
23
|
+
**Allowed**
|
|
24
|
+
|
|
25
|
+
- Display text: `text`, `label`, `placeholder`, `title`, and other free-form string attributes (per `getUiComponentSchemas`)
|
|
26
|
+
- Text results inside a formula, e.g. the branches of an `IF` or `CASE`
|
|
27
|
+
|
|
28
|
+
**Not allowed**
|
|
29
|
+
|
|
30
|
+
- `meta` directives (`if`, `forEach`, `forItem`)
|
|
31
|
+
- Enum-valued attributes (`variant`, `size`, `color`, `gap`, `align`, `justify`) and icon names
|
|
32
|
+
- Anything inside `actions` (`url`, `target`, `sendMessage` `content`)
|
|
33
|
+
- Data bound with `$attrs` / `$item` — payload values are not translated
|
|
34
|
+
- `schema.json` and the `-meta.xml`
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Label names
|
|
39
|
+
|
|
40
|
+
Reuse existing labels first. If the user names labels, or the project's CustomLabels file (`labels/CustomLabels.labels-meta.xml`) already has a label whose value matches the text, reference that label's `fullName` verbatim. Otherwise propose `<WidgetName>_<Role>`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Combining labels with data
|
|
45
|
+
|
|
46
|
+
- Join a label and data with `&` (see `references/widget-formulas.md`):
|
|
47
|
+
`"text": "{!$Label.c.<LabelName> & ' ' & TEXT($attrs.<attrName>)}"`
|
|
48
|
+
- Choose between labels with `IF` / `CASE`:
|
|
49
|
+
`"label": "{!IF($attrs.<booleanAttr>, $Label.c.<LabelWhenTrue>, $Label.c.<LabelWhenFalse>)}"`
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Creating labels
|
|
54
|
+
|
|
55
|
+
This skill gives no guidance on authoring label metadata, and the widget is complete without it. Create labels only when the user asks you to. When you do, add them to the project's existing CustomLabels file instead of overwriting it, and never redefine a label that already exists. The user is responsible for checking the labels and any translations in the org and for deploying them with or before the widget.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Label report
|
|
60
|
+
|
|
61
|
+
End the response with this report:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
### Custom Labels used by this widget
|
|
65
|
+
|
|
66
|
+
These labels must exist in the target org before you deploy — deploying a widget that references a missing label fails.
|
|
67
|
+
|
|
68
|
+
| Label name | Value (default language) | Status | Used in |
|
|
69
|
+
|---|---|---|---|
|
|
70
|
+
| OrderStatus_Heading | Order status | Existing | `tile/text` `text` (widget heading) |
|
|
71
|
+
| OrderStatus_TrackButton | Track order | To create | `tile/button` `label` |
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- List each distinct label once, in the order it first appears in the widget.
|
|
75
|
+
- **Label name** is the `fullName`, without the `c.` prefix.
|
|
76
|
+
- **Value** is the literal text the label replaced.
|
|
77
|
+
- **Status** is `Existing` (already in the project or named by the user), `Created` (you wrote it), or `To create` (the user must create it).
|
|
78
|
+
- **Used in** names the block definition and attribute.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Widget Styling Best Practices
|
|
2
|
+
|
|
3
|
+
Widgets express *intent*, not pixels. Each surface provides a default look and feel; brand/theme overrides apply automatically.
|
|
4
|
+
|
|
5
|
+
- **Style semantically.** Use `variant`, `size`, and other enum-typed attributes (`primary`, `destructive`, `success`, `warning`). Do not pin literal colors or pixel values.
|
|
6
|
+
- **One primary action per visible group.** At most one `tile/button` with `variant: primary`. Use `secondary` or `destructive` for additional actions (see the `tile/button` schema for the full variant enum).
|
|
7
|
+
- **Every `tile/button` needs a `click` action.** See SKILL.md *Actions* — an action-less button renders disabled.
|
|
8
|
+
- **One `h1` per widget.** Use `h2`/`h3` for sub-section headings, `body` for prose, `caption` for helper text.
|
|
9
|
+
- **Use semantic state variants on state-bearing blocks** (`tile/badge`, `tile/callout`).
|
|
10
|
+
- **Omit attributes that equal the schema default.** Check each property's `default` in the `getUiComponentSchemas` metadata (step 4). If the value you would write matches it — e.g. `gap`, `size`, or `action/openLink` `target: "_blank"` — leave the attribute out. Set an attribute only to change it.
|
|
11
|
+
- **Don't pin `width`** unless a content constraint requires it.
|
|
12
|
+
- **Use the Lucide icon set.** Pass the Lucide name (`"check"`, `"alert-circle"`); other icon libraries are not supported.
|
|
@@ -146,12 +146,19 @@ Classify the org into one of 4 states:
|
|
|
146
146
|
### Phase 3 — Prerequisite enablement (State 2 only)
|
|
147
147
|
|
|
148
148
|
- **License gate (checked first):** query `OrganizationSettingsDetail`
|
|
149
|
-
(Tooling API) for the `ECICallScoringAI` OrgPermission.
|
|
150
|
-
`
|
|
151
|
-
requires Agentforce for Sales license." Non-zero exit.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
**
|
|
149
|
+
(Tooling API) for the `ECICallScoringAI` OrgPermission. Only an **explicit
|
|
150
|
+
`false`** refuses — exit with: "AI call coaching is not available in this
|
|
151
|
+
org and org requires Agentforce for Sales license." (Non-zero exit.) If the
|
|
152
|
+
permission **can't be read** — the query is rejected with `INVALID_TYPE`
|
|
153
|
+
(some org editions, for example Developer Edition) or returns no row — treat
|
|
154
|
+
it as **unknown and proceed**; do NOT fail closed on an unreadable signal.
|
|
155
|
+
The Einstein GenAI `TYPE_UNAVAILABLE` pre-check catches most unlicensed
|
|
156
|
+
orgs; the enable `updateMetadata` write is the final check. This mirrors the Phase 0
|
|
157
|
+
`ConversationPilot` gate, which defers on any non-conclusive read.
|
|
158
|
+
This runs even when `enableCallCoaching`/`enableECICallScoring`
|
|
159
|
+
already read favorably, since those settings don't by themselves confirm the
|
|
160
|
+
org is licensed. **Must run before the ECI-off check below** — a genuinely
|
|
161
|
+
unlicensed org
|
|
155
162
|
and a licensed-but-toggled-off org both read `enableCallCoaching=false`,
|
|
156
163
|
so checking ECI first would misdiagnose a licensing gap as a self-serve
|
|
157
164
|
Setup toggle (the regression the `NoLicense` eval dataset guards against).
|
|
@@ -217,7 +224,7 @@ Re-read `enableECICallScoring`. Require `true`. Print SOAP response on failure.
|
|
|
217
224
|
is server-side read-only and `updateMetadata` returns
|
|
218
225
|
`INSUFFICIENT_ACCESS_OR_READONLY`. `enable-call-scoring.sh` maps this to a
|
|
219
226
|
transient "still finishing turning on — wait a few minutes and re-run"
|
|
220
|
-
message rather than a terminal failure
|
|
227
|
+
message rather than a terminal failure.
|
|
221
228
|
- **Every SOAP call is bounded by a 30s timeout** (`CURL_TIMEOUT` in
|
|
222
229
|
`enable-call-scoring.sh`, matching the only existing timeout precedent in
|
|
223
230
|
this repo — the Pipeline skill's `setup-all.sh`) and classified via
|
|
@@ -243,14 +250,14 @@ Re-read `enableECICallScoring`. Require `true`. Print SOAP response on failure.
|
|
|
243
250
|
- **SOAP helpers**: `scripts/shared/soap.sh` (copied from the Pipeline skill —
|
|
244
251
|
`redact_token`, `parse_soap_response`)
|
|
245
252
|
- **Server-side gate** (declarative access check):
|
|
246
|
-
`
|
|
253
|
+
`ConversationalIntelligence.accessChecks.xml`
|
|
247
254
|
— `orgHasCallScoring` = ECI arm OR River Rush arm; `orgHasECICallScoringWithAI`
|
|
248
255
|
adds Einstein GenAI + `ECICallScoringAI` license perm on top.
|
|
249
256
|
- **Pref declaration**:
|
|
250
|
-
`
|
|
257
|
+
`ConversationalIntelligence.settings.xml`
|
|
251
258
|
— `<orgPreference name="ECICallScoringEnabled" mdApiName="enableECICallScoring" ...>`
|
|
252
259
|
- **Java feature registration** (would be the entry point if a
|
|
253
|
-
SetupDiscoveryService REST/MCP wrapper existed): `
|
|
260
|
+
SetupDiscoveryService REST/MCP wrapper existed): `CoachingCompetenciesFeature.java`
|
|
254
261
|
|
|
255
262
|
## Competency CRUD workflow
|
|
256
263
|
|
|
@@ -61,14 +61,14 @@ resolve_org_auth_with_token "$ORG_ALIAS" || exit 1
|
|
|
61
61
|
# orgs) reject the OrganizationSettingsDetail query with INVALID_TYPE. That's
|
|
62
62
|
# not a licensing signal — it's a metadata-availability signal. Treat any
|
|
63
63
|
# non-conclusive result as "unknown, defer to SOAP" and only refuse when the
|
|
64
|
-
# query explicitly comes back "false"
|
|
64
|
+
# query explicitly comes back "false".
|
|
65
65
|
CONVERSATIONPILOT_ENABLED=""
|
|
66
66
|
if CONVERSATIONPILOT_RAW=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \
|
|
67
67
|
--query "SELECT SettingValue FROM OrganizationSettingsDetail WHERE SettingName='ConversationPilot' AND DataType='OrgPermissionSettings'" \
|
|
68
68
|
--json 2>/dev/null); then
|
|
69
69
|
CONVERSATIONPILOT_JSON=$(echo "$CONVERSATIONPILOT_RAW" | awk '/^{/,EOF')
|
|
70
70
|
if echo "$CONVERSATIONPILOT_JSON" | jq -e '.status == 0' >/dev/null 2>&1; then
|
|
71
|
-
CONVERSATIONPILOT_ENABLED=$(echo "$CONVERSATIONPILOT_JSON" | jq -r '.result.records[0].SettingValue
|
|
71
|
+
CONVERSATIONPILOT_ENABLED=$(echo "$CONVERSATIONPILOT_JSON" | jq -r '.result.records[0].SettingValue | select(. != null)')
|
|
72
72
|
fi
|
|
73
73
|
fi
|
|
74
74
|
|
|
@@ -215,7 +215,6 @@ fi
|
|
|
215
215
|
# doing a write — Einstein GenAI Platform is a hard prerequisite for AI
|
|
216
216
|
# Call Scoring, so an org that can't even see that settings type won't be
|
|
217
217
|
# able to enable Call Coaching regardless of what the ECI toggle says.
|
|
218
|
-
# W-24026047.
|
|
219
218
|
#
|
|
220
219
|
# Not perfect: an org could have the GenAI platform present but still lack
|
|
221
220
|
# the specific Call Scoring license. State 2c's ECICallScoringAI
|
|
@@ -245,19 +244,33 @@ if ECICALLSCORINGAI_RAW=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-
|
|
|
245
244
|
--json 2>/dev/null); then
|
|
246
245
|
ECICALLSCORINGAI_JSON=$(echo "$ECICALLSCORINGAI_RAW" | awk '/^{/,EOF')
|
|
247
246
|
if echo "$ECICALLSCORINGAI_JSON" | jq -e '.status == 0' >/dev/null 2>&1; then
|
|
248
|
-
|
|
247
|
+
# A missing row means "couldn't read it", NOT "license is off" — default
|
|
248
|
+
# to empty (unknown), never to "false". OrganizationSettingsDetail is not
|
|
249
|
+
# queryable (INVALID_TYPE) on some org editions (for example Developer
|
|
250
|
+
# Edition) and can return zero rows even when queryable; both are "can't
|
|
251
|
+
# tell", and collapsing them to "false" fails closed. Use select(), not
|
|
252
|
+
# `// empty`: SettingValue is a boolean and `//` drops an explicit false.
|
|
253
|
+
ECICALLSCORINGAI_ENABLED=$(echo "$ECICALLSCORINGAI_JSON" | jq -r '.result.records[0].SettingValue | select(. != null)')
|
|
249
254
|
fi
|
|
250
255
|
fi
|
|
251
256
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
# Only an EXPLICIT negative refuses. An unknown result — the query was
|
|
258
|
+
# rejected (INVALID_TYPE on this org shape) or returned no row — must NOT
|
|
259
|
+
# hard-fail: doing so blocks legitimately-licensed orgs where
|
|
260
|
+
# OrganizationSettingsDetail simply isn't queryable. This mirrors the Phase 0
|
|
261
|
+
# ConversationPilot gate, which defers to SOAP on any non-conclusive read. The
|
|
262
|
+
# 2a-pre GenAI TYPE_UNAVAILABLE gate above catches most unlicensed orgs, and
|
|
263
|
+
# the updateMetadata write below is the final check — a truly unlicensed org
|
|
264
|
+
# faults on the write. Closes the residual gap the earlier
|
|
265
|
+
# GenAI-proxy fix left behind.
|
|
266
|
+
if [[ "$ECICALLSCORINGAI_ENABLED" == "false" ]]; then
|
|
267
|
+
echo "AI call coaching is not available in this org and org requires Agentforce for Sales license."
|
|
255
268
|
exit 1
|
|
256
269
|
fi
|
|
257
270
|
|
|
258
|
-
if [[ "$ECICALLSCORINGAI_ENABLED"
|
|
259
|
-
echo "
|
|
260
|
-
|
|
271
|
+
if [[ -z "$ECICALLSCORINGAI_ENABLED" ]]; then
|
|
272
|
+
echo "Couldn't confirm the AI Call Scoring license on this org — continuing;"
|
|
273
|
+
echo " if the license is missing, the enable step will report it."
|
|
261
274
|
fi
|
|
262
275
|
|
|
263
276
|
# 2b. ECI must be enabled — this skill is ECI-only.
|
|
@@ -363,7 +376,7 @@ EOF
|
|
|
363
376
|
# read-only and updateMetadata returns INSUFFICIENT_ACCESS_OR_READONLY.
|
|
364
377
|
# This is a transient state that self-resolves in a few minutes, not a
|
|
365
378
|
# permanent failure — surface a wait-and-retry message instead of the
|
|
366
|
-
# generic error.
|
|
379
|
+
# generic error.
|
|
367
380
|
case "$CS_RESULT" in
|
|
368
381
|
WRITE_ERROR:INSUFFICIENT_ACCESS_OR_READONLY*)
|
|
369
382
|
echo " Einstein Conversation Insights is still finishing turning on."
|
|
@@ -388,31 +401,40 @@ CCR_LOOKUP=$(sf api request rest \
|
|
|
388
401
|
"services/data/v68.0/tooling/query/?q=SELECT+Id,SettingValue+FROM+OrganizationSettingsDetail+WHERE+SettingName='CallCoachingReady'+AND+DataType='OrgPermissionSettings'" \
|
|
389
402
|
--target-org "$ORG_ALIAS" 2>/dev/null) || true
|
|
390
403
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
404
|
+
# On some org editions (for example Developer Edition), OrganizationSettingsDetail
|
|
405
|
+
# isn't exposed at all (INVALID_TYPE) — the same unqueryable shape the Phase 3
|
|
406
|
+
# license gate tolerates. The authoritative enable (enableECICallScoring, flipped
|
|
407
|
+
# above and re-verified below) has already succeeded, so a CallCoachingReady
|
|
408
|
+
# lookup we physically cannot run must not fail the enable closed.
|
|
409
|
+
if echo "$CCR_LOOKUP" | grep -q '"errorCode"[[:space:]]*:[[:space:]]*"INVALID_TYPE"'; then
|
|
410
|
+
echo "CallCoachingReady OrgPermission isn't queryable on this org — skipping (Call Coaching is already enabled)."
|
|
411
|
+
else
|
|
412
|
+
CCR_COUNT=$(echo "$CCR_LOOKUP" | jq -r '.totalSize // 0' 2>/dev/null || echo 0)
|
|
413
|
+
if [[ "$CCR_COUNT" == "0" ]]; then
|
|
414
|
+
echo "Could not locate the CallCoachingReady OrgPermission record on this org."
|
|
415
|
+
exit 1
|
|
416
|
+
fi
|
|
396
417
|
|
|
397
|
-
CCR_ID=$(echo "$CCR_LOOKUP" | jq -r '.records[0].Id')
|
|
398
|
-
CCR_CURRENT=$(echo "$CCR_LOOKUP" | jq -r '.records[0].SettingValue')
|
|
418
|
+
CCR_ID=$(echo "$CCR_LOOKUP" | jq -r '.records[0].Id')
|
|
419
|
+
CCR_CURRENT=$(echo "$CCR_LOOKUP" | jq -r '.records[0].SettingValue')
|
|
399
420
|
|
|
400
|
-
if [[ "$CCR_CURRENT" == "true" ]]; then
|
|
401
|
-
|
|
402
|
-
else
|
|
403
|
-
echo "Enabling CallCoachingReady..."
|
|
404
|
-
if CCR_PATCH_RESP=$(sf api request rest \
|
|
405
|
-
"services/data/v68.0/tooling/sobjects/OrganizationSettingsDetail/${CCR_ID}" \
|
|
406
|
-
--target-org "$ORG_ALIAS" \
|
|
407
|
-
--method PATCH \
|
|
408
|
-
--header 'Content-Type: application/json' \
|
|
409
|
-
--body '{"SettingValue": true}' \
|
|
410
|
-
2>/dev/null); then
|
|
411
|
-
echo " CallCoachingReady enabled"
|
|
421
|
+
if [[ "$CCR_CURRENT" == "true" ]]; then
|
|
422
|
+
echo "CallCoachingReady is already enabled — skipping."
|
|
412
423
|
else
|
|
413
|
-
echo "
|
|
414
|
-
|
|
415
|
-
|
|
424
|
+
echo "Enabling CallCoachingReady..."
|
|
425
|
+
if CCR_PATCH_RESP=$(sf api request rest \
|
|
426
|
+
"services/data/v68.0/tooling/sobjects/OrganizationSettingsDetail/${CCR_ID}" \
|
|
427
|
+
--target-org "$ORG_ALIAS" \
|
|
428
|
+
--method PATCH \
|
|
429
|
+
--header 'Content-Type: application/json' \
|
|
430
|
+
--body '{"SettingValue": true}' \
|
|
431
|
+
2>/dev/null); then
|
|
432
|
+
echo " CallCoachingReady enabled"
|
|
433
|
+
else
|
|
434
|
+
echo " Failed to enable CallCoachingReady"
|
|
435
|
+
echo " $CCR_PATCH_RESP"
|
|
436
|
+
exit 1
|
|
437
|
+
fi
|
|
416
438
|
fi
|
|
417
439
|
fi
|
|
418
440
|
|
|
@@ -55,7 +55,7 @@ All steps are sequential. If a step fails, stop and report the error rather than
|
|
|
55
55
|
|
|
56
56
|
2. **Gather configuration** — use `AskUserQuestion` to collect **Country** (`US`/`CA`) and **Phone Number Type** (`10DLC`/`Toll Free`). Do not mark any option as recommended or default — present the choices neutrally. Wait for both before continuing.
|
|
57
57
|
|
|
58
|
-
3. **Fetch available numbers** — read `references/number_management_api.md` for the endpoint contract,
|
|
58
|
+
3. **Fetch available numbers** — read `references/number_management_api.md` for the endpoint contract. Before fetching, use `AskUserQuestion` to offer an **optional** way to narrow results: ask whether the user wants numbers for a specific **area code** (when the type is `10DLC`) or **prefix** (when the type is `Toll Free`), or to list all available numbers. Present this neutrally with no recommended/default option; leaving it blank (or choosing to list all) preserves the unfiltered fetch. Then run `scripts/fetch-numbers.sh <alias> <country> <phoneType> [searchParam]`, passing the area code / prefix as the optional fourth argument only when the user supplied one. Present the returned numbers as a numbered list and ask the user to select one — do not mark any number as recommended or default. If a narrowed fetch returns no numbers, tell the user and offer to retry with a different area code / prefix or to list all available numbers.
|
|
59
59
|
|
|
60
60
|
4. **Procure the selected number** — run `scripts/procure-number.sh <alias> <country> <phoneType> <number>`. On a non-2xx response, map the status via `references/number_management_api.md` and stop with a clear message.
|
|
61
61
|
|
package/skills/service-agentforce-contact-center-coordinate/references/number_management_api.md
CHANGED
|
@@ -21,11 +21,18 @@ Query parameters:
|
|
|
21
21
|
|-------|--------|-------|
|
|
22
22
|
| `countryCode` | `US`, `CA` | |
|
|
23
23
|
| `phoneNumberType` | `10DLC`, `Toll Free` | URL-encode the space in `Toll Free` as `%20` |
|
|
24
|
+
| `searchParam` | area code (`10DLC`) or prefix (`Toll Free`) | **Optional.** Narrows results to numbers matching the area code / prefix. Omit to list all available numbers for the type. Passed through as-is (the API returns `400 BAD_REQUEST` on an invalid value). |
|
|
24
25
|
|
|
25
26
|
```bash
|
|
27
|
+
# All available Toll Free numbers for US
|
|
26
28
|
sf api request rest \
|
|
27
29
|
"/services/data/v68.0/connect/number-management/v1/numbers?countryCode=US&phoneNumberType=Toll%20Free" \
|
|
28
30
|
--target-org <alias>
|
|
31
|
+
|
|
32
|
+
# Optional: narrow 10DLC numbers to a specific area code via searchParam
|
|
33
|
+
sf api request rest \
|
|
34
|
+
"/services/data/v68.0/connect/number-management/v1/numbers?countryCode=US&phoneNumberType=10DLC&searchParam=415" \
|
|
35
|
+
--target-org <alias>
|
|
29
36
|
```
|
|
30
37
|
|
|
31
38
|
Response:
|
|
@@ -42,10 +49,13 @@ Response:
|
|
|
42
49
|
sf api request rest \
|
|
43
50
|
"/services/data/v68.0/connect/number-management/v1/number" \
|
|
44
51
|
--method POST \
|
|
45
|
-
--body '{"phoneNumberType":"Toll Free","country":"US","phoneNumber":"+14155551234"}' \
|
|
52
|
+
--body '{"phoneNumberType":"Toll Free","country":"US","phoneNumber":"+14155551234","contactCenterType":"Agentforce Contact Center"}' \
|
|
46
53
|
--target-org <alias>
|
|
47
54
|
```
|
|
48
55
|
|
|
56
|
+
`contactCenterType` is a fixed internal value (`Agentforce Contact Center`) — always sent,
|
|
57
|
+
never gathered from the user.
|
|
58
|
+
|
|
49
59
|
Success (`201`):
|
|
50
60
|
|
|
51
61
|
```json
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Fetch available default phone numbers for a country + phone-number type.
|
|
3
|
-
# Usage: fetch-numbers.sh <org-alias> <country> <phone-type>
|
|
4
|
-
# country:
|
|
5
|
-
# phone-type:
|
|
3
|
+
# Usage: fetch-numbers.sh <org-alias> <country> <phone-type> [search-param]
|
|
4
|
+
# country: US | CA
|
|
5
|
+
# phone-type: 10DLC | "Toll Free"
|
|
6
|
+
# search-param: optional — area code (10DLC) or prefix (Toll Free) to narrow
|
|
7
|
+
# results. Appended as the API's `searchParam` query param. Omit
|
|
8
|
+
# (or pass empty) to list all available numbers for the type.
|
|
6
9
|
# Output: JSON { "phoneNumbers": [...] } on stdout.
|
|
7
10
|
|
|
8
11
|
set -euo pipefail
|
|
@@ -10,12 +13,15 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
10
13
|
source "$SCRIPT_DIR/lib.sh"
|
|
11
14
|
|
|
12
15
|
if [[ "${1:-}" == "--help" || $# -lt 3 ]]; then
|
|
13
|
-
echo "Usage: fetch-numbers.sh <org-alias> <country> <phone-type>" >&2
|
|
16
|
+
echo "Usage: fetch-numbers.sh <org-alias> <country> <phone-type> [search-param]" >&2
|
|
14
17
|
[[ "${1:-}" == "--help" ]] && exit 0 || exit 2
|
|
15
18
|
fi
|
|
16
19
|
|
|
17
|
-
ALIAS="$1"; COUNTRY="$2"; PHONE_TYPE="$3"
|
|
20
|
+
ALIAS="$1"; COUNTRY="$2"; PHONE_TYPE="$3"; SEARCH_PARAM="${4:-}"
|
|
21
|
+
|
|
22
|
+
QUERY="countryCode=${COUNTRY}&phoneNumberType=$(urlenc_spaces "$PHONE_TYPE")"
|
|
23
|
+
[[ -n "$SEARCH_PARAM" ]] && QUERY="${QUERY}&searchParam=$(urlenc "$SEARCH_PARAM")"
|
|
18
24
|
|
|
19
25
|
sf api request rest \
|
|
20
|
-
"/${NM_BASE}/numbers
|
|
26
|
+
"/${NM_BASE}/numbers?${QUERY}" \
|
|
21
27
|
--target-org "$ALIAS"
|
|
@@ -13,6 +13,22 @@ NM_BASE="services/data/${API_VERSION}/connect/number-management/v1"
|
|
|
13
13
|
# URL-encode a value's spaces (sufficient for country/phoneType query params).
|
|
14
14
|
urlenc_spaces() { printf '%s' "${1// /%20}"; }
|
|
15
15
|
|
|
16
|
+
# Percent-encode a full value for safe use as a URL query-parameter value: only
|
|
17
|
+
# the RFC 3986 unreserved set stays literal, everything else becomes %XX. Needed
|
|
18
|
+
# for free-text values (e.g. a user-supplied area code / prefix) so reserved
|
|
19
|
+
# characters like & or = can't inject extra query parameters into the request.
|
|
20
|
+
urlenc() {
|
|
21
|
+
local s="$1" i c out=""
|
|
22
|
+
for (( i=0; i<${#s}; i++ )); do
|
|
23
|
+
c="${s:i:1}"
|
|
24
|
+
case "$c" in
|
|
25
|
+
[a-zA-Z0-9.~_-]) out+="$c" ;;
|
|
26
|
+
*) printf -v c '%%%02X' "'$c"; out+="$c" ;;
|
|
27
|
+
esac
|
|
28
|
+
done
|
|
29
|
+
printf '%s' "$out"
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
# XML-escape a value for safe interpolation into flow-template stringValue/label nodes.
|
|
17
33
|
# Free-text org data (queue names, agent labels) can contain &, <, >, ", ' — unescaped
|
|
18
34
|
# they produce malformed XML and an opaque `sf project deploy start` parse error.
|
|
@@ -14,8 +14,9 @@ fi
|
|
|
14
14
|
|
|
15
15
|
ALIAS="$1"; COUNTRY="$2"; PHONE_TYPE="$3"; NUMBER="$4"
|
|
16
16
|
|
|
17
|
+
# contactCenterType is a fixed internal value, not a user-supplied input.
|
|
17
18
|
BODY=$(jq -n --arg t "$PHONE_TYPE" --arg c "$COUNTRY" --arg p "$NUMBER" \
|
|
18
|
-
'{phoneNumberType:$t, country:$c, phoneNumber:$p}')
|
|
19
|
+
'{phoneNumberType:$t, country:$c, phoneNumber:$p, contactCenterType:"Agentforce Contact Center"}')
|
|
19
20
|
|
|
20
21
|
sf api request rest \
|
|
21
22
|
"/${NM_BASE}/number" \
|
|
@@ -6,9 +6,9 @@ Help Portal deploys an **Agentforce Concierge experience** on an LWR Experience
|
|
|
6
6
|
|
|
7
7
|
The agent script does **not** change here — this is channel/site metadata around the agent.
|
|
8
8
|
|
|
9
|
-
## Execution model —
|
|
9
|
+
## Execution model — Headless first, UI fallback
|
|
10
10
|
|
|
11
|
-
Every step in §A–§S below is executed headlessly via `sf` CLI + Data API + Metadata API + Tooling API.
|
|
11
|
+
Every step in §A–§S below is executed headlessly via `sf` CLI + Data API + Metadata API + Tooling API. The Agentforce Orchestrator OrgPerm (§F.4) is enabled via a Connect API POST — no operator UI pause is required. Every Setup-URL fallback in this document is a *last resort* — attempt the headless path first, and only fall back to UI if the headless path returns an unrecoverable error.
|
|
12
12
|
|
|
13
13
|
If a non-critical step fails (branding polish, suggestion themes, progressive-rendering toggle), log the failure, continue, and surface the gap to the operator at the end. Do NOT abort the deploy for cosmetic gaps.
|
|
14
14
|
|
|
@@ -50,7 +50,7 @@ At the end of the run, print the live customer-facing portal URL for the operato
|
|
|
50
50
|
> | 4 | Channel + routing + escalation | §E — `sf agent activate` first; if `MESSAGING_CHANNEL_DEV_NAME` not passed in, delegate to `service-digital-engagement-channel-configure` to create the channel (deploy XML with `<sessionHandlerType>AgentforceServiceAgent</sessionHandlerType>` + `<sessionHandlerQueue>` only — `sessionHandlerAsa` is not accepted by the v67 Metadata API); then delegate to `service-agentforce-channel-configure` (Phase 1: queue, Phase 2: Data API PATCH of `SessionHandlerId` + `FallbackQueueId` on the MessagingChannel, Phase 3: escalation question) | SOQL confirms `SessionHandlerId=$BOT_ID`, `FallbackQueueId` set; `BotVersion.Status=Active`; escalation answered by operator |
|
|
51
51
|
> | 6 | Retrieve + edit + deploy bundle | §F.0 — retrieve `DigitalExperienceBundle:site/$BUNDLE_NAME`; **edit `sfdc_cms__brandingSet/*/content.json` with the four colors from §A (§D)**; replace home placeholder with 4 Concierge components (empty `attributes: {}`); add `Conversation__c` route + `conversation` view (§F.0.a); flip `authenticationType`; deploy | Retrieve confirms components + route; branding JSON contains user-specified hex values |
|
|
52
52
|
> | 7 | Wire Concierge runtime binding | §F.3.a–d — create `EmbeddedServiceConfig` via Connect API `POST /connect/embeddedmessaging/deployment/setup` (same as Web Chat §C.4, omit `hostDomain`; auto-generates `ESW_*` site + test page), insert NSSE, **deploy AND assign** guest permset to Site's `GuestUserId` | `/webruntime/api/…/concierge/config` returns `deploymentName + siteUrl + scrtUrl` **AND** `SELECT PermissionSet.Name FROM PermissionSetAssignment WHERE AssigneeId=<GuestUserId>` returns the `<Bundle>_Guest_Concierge` row |
|
|
53
|
-
> | 8 |
|
|
53
|
+
> | 8 | Enable Agentforce Orchestrator + create orchestrator binding | §F.4 — `POST /connect/self-service/setup/agentforceOrchestratorEnablement?includeAgentforceOrchestratorPerms=false` (headless, no operator pause); verify SOQL probe; then `POST /connect/self-service/setup/agentOrchestrator {name, developerName, agentId}` | Connect GET returns orchestrator with agentId |
|
|
54
54
|
> | 9 | Deploy the bundle | §G — `sf project deploy start -m DigitalExperienceBundle:site/$BUNDLE_NAME` | Status: Succeeded |
|
|
55
55
|
> | 10 | Publish the site | §I — `POST /connect/communities/$NETWORK_ID/publish`; PATCH `Network.Status=Live` | Site loads at `PUBLISHED_PORTAL_URL` |
|
|
56
56
|
> | 11 | Network guest flags | §L.0 — PATCH `Network.OptionsGuestChatterEnabled=true AND OptionsGuestMemberVisibility=true` (single Data API call). ALSO verify auth ESC `AreGuestUsersAllowed=true` — flip via mdapi + republish if false. | `/concierge/config?asGuest=true` returns 200; SOQL confirms both Network flags + ESC flag true |
|
|
@@ -712,13 +712,13 @@ curl -sS "$PUBLISHED_PORTAL_URL/webruntime/api/services/data/v67.0/connect/self-
|
|
|
712
712
|
|
|
713
713
|
All three fields must be non-null. If `deploymentName` is null, the bound ESC references a different site or MessagingChannel — verify the `<site>` in the ESC metadata is the LWR site (F.3.a).
|
|
714
714
|
|
|
715
|
-
### F.4 — Wire the Agentforce Orchestrator *(
|
|
715
|
+
### F.4 — Wire the Agentforce Orchestrator *(Headless first, UI fallback)*
|
|
716
716
|
|
|
717
|
-
The Agentforce Orchestrator provides the personalized greeting, Action/Object recommendations, and Concierge proactive routing. The "Enable Agentforce Orchestrator" OrgPerm gates the agent-binding surfaces (Connect POST, Data API sObject, SOQL).
|
|
717
|
+
The Agentforce Orchestrator provides the personalized greeting, Action/Object recommendations, and Concierge proactive routing. The "Enable Agentforce Orchestrator" OrgPerm gates the agent-binding surfaces (Connect POST, Data API sObject, SOQL). Enable it headlessly via `POST /connect/self-service/setup/agentforceOrchestratorEnablement`, verify with the SOQL probe, then create the orchestrator binding.
|
|
718
718
|
|
|
719
719
|
**F.4.0 — Detection preflight.**
|
|
720
720
|
|
|
721
|
-
Run the probe up to **2 times** before concluding the toggle is off. The `sf` CLI occasionally mixes warning output into stdout on the first call
|
|
721
|
+
Run the SOQL probe up to **2 times** before concluding the toggle is off. The `sf` CLI occasionally mixes warning output into stdout on the first call causing a false "not supported" parse. Re-running the identical query without waiting resolves the issue — no org state changes between attempts.
|
|
722
722
|
|
|
723
723
|
```bash
|
|
724
724
|
TOGGLE_STATE="UNKNOWN"
|
|
@@ -735,11 +735,24 @@ done
|
|
|
735
735
|
```
|
|
736
736
|
|
|
737
737
|
- ✅ `TOGGLE_STATE=ON` → proceed with §F.4.2.
|
|
738
|
-
- ❌ `TOGGLE_STATE=OFF` (both attempts) → toggle is genuinely OFF
|
|
738
|
+
- ❌ `TOGGLE_STATE=OFF` (both attempts) → toggle is genuinely OFF → proceed to §F.4.1 (headless enable).
|
|
739
739
|
|
|
740
|
-
**F.4.1 —
|
|
740
|
+
**F.4.1 — Headless enable via Connect API (fully headless — no operator pause).**
|
|
741
741
|
|
|
742
|
-
|
|
742
|
+
Enable Agentforce Orchestrator headlessly:
|
|
743
|
+
|
|
744
|
+
> **`min_api_version: 68.0`** — this endpoint is only available on API v68.0+. On v67 orgs the POST returns `NOT_FOUND`; fall through to the UI path below.
|
|
745
|
+
|
|
746
|
+
```bash
|
|
747
|
+
sf api request rest -o "$ORG" --method POST \
|
|
748
|
+
"/services/data/v68.0/connect/self-service/setup/agentforceOrchestratorEnablement?includeAgentforceOrchestratorPerms=false" \
|
|
749
|
+
--body '{}'
|
|
750
|
+
# → {"isSuccess": true}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
After the POST, re-run the §F.4.0 SOQL probe to confirm `TOGGLE_STATE=ON`. If the probe still returns "not supported" after a successful POST (can happen on a slow pod), wait 10 seconds and re-probe once more. If the probe is still off after that re-probe, fall back to the UI step below — do not continue to §F.4.2.
|
|
754
|
+
|
|
755
|
+
If the org **genuinely does not have this surface** (the POST itself returns `NOT_FOUND` or `UNSUPPORTED_OPERATION`), fall through to the legacy UI path as a last resort:
|
|
743
756
|
|
|
744
757
|
> **One manual step needed.** Please open this URL and enable Agentforce Orchestrator:
|
|
745
758
|
>
|
|
@@ -749,7 +762,7 @@ If the SOQL probe returned "not supported", send this message verbatim to the op
|
|
|
749
762
|
> 2. Wait for the confirmation banner.
|
|
750
763
|
> 3. Reply `done` here and I'll continue.
|
|
751
764
|
|
|
752
|
-
|
|
765
|
+
If the org cannot enable this at all, skip §F.4 entirely and log the gap in the operator handoff.
|
|
753
766
|
|
|
754
767
|
**F.4.2 — Bot prerequisite check.**
|
|
755
768
|
|
|
@@ -1419,7 +1432,7 @@ sf org open --target-org $ORG --path "/<url-path>/"
|
|
|
1419
1432
|
| Guest curl to `/webruntime/api/.../connect/self-service/*` returns `HTTP 401 UNAUTHORIZED` | `Network.OptionsGuestChatterEnabled = False` on target | Run §L.0 diagnostic; PATCH to True via Data API — no republish required |
|
|
1420
1433
|
| Portal loads, `/concierge/config` returns 200, prompt bar renders but says **"Agents are not available. Try again later."** | Bot-routing wiring missing. Typical gaps: `PresenceUserConfigUser`, `GroupMember`, `Group.QueueRoutingConfigId` | Run §N — three Data-API writes |
|
|
1421
1434
|
| §N writes all succeed but chat *still* shows "Agents are not available" immediately | Automated-Process pickup path needs to spin up after routing wiring changes | Re-activate the BotVersion via CLI (`sf agent deactivate` + `sf agent activate`). Wait a few minutes. Do NOT rely on `UserServicePresence` COUNT as the readiness signal |
|
|
1422
|
-
| Prompt bar responses are ungrounded / navigate to `/error` on newer-API orgs | Agentforce Orchestrator not wired | See §F.4 —
|
|
1435
|
+
| Prompt bar responses are ungrounded / navigate to `/error` on newer-API orgs | Agentforce Orchestrator not wired | See §F.4 — `POST /connect/self-service/setup/agentforceOrchestratorEnablement?includeAgentforceOrchestratorPerms=false`, verify SOQL probe, then `POST /connect/self-service/setup/agentOrchestrator` |
|
|
1423
1436
|
| Agentforce Orchestrator's "Add Tool → Agent" picker is empty | `BotDefinition.AgentTemplate` is `null` — Bot was created via headless Metadata deploy, not the Setup wizard | Delete the Bot and recreate via Setup → Agentforce Agents → New Agent → From Template → Agentforce Service Agent |
|
|
1424
1437
|
|
|
1425
1438
|
---
|