@salesforce/afv-skills 1.40.0 → 1.41.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 (136) hide show
  1. package/package.json +1 -1
  2. package/skills/automation-flow-generate/SKILL.md +32 -43
  3. package/skills/experience-portal-create/SKILL.md +497 -0
  4. package/skills/experience-portal-create/assets/report-template.md +30 -0
  5. package/skills/experience-portal-create/references/mcp-invocation.md +288 -0
  6. package/skills/experience-portal-create/references/post-creation-activate-publish.md +165 -0
  7. package/skills/experience-portal-create/references/templates.md +253 -0
  8. package/skills/experience-ui-bundle-features-generate/SKILL.md +5 -1
  9. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -0
  10. package/skills/experience-ui-bundle-frontend-generate/references/page.md +1 -0
  11. package/skills/platform-datamask-run/SKILL.md +345 -0
  12. package/skills/platform-datamask-run/references/api-surface.md +130 -0
  13. package/skills/platform-datamask-run/references/policy-authoring.md +185 -0
  14. package/skills/platform-datamask-run/references/run-and-abort.md +116 -0
  15. package/skills/platform-datamask-run/scripts/poll-job.sh +115 -0
  16. package/skills/platform-dataspace-access-configure/SKILL.md +51 -3
  17. package/skills/platform-dataspace-access-configure/scripts/inspect-dataspace-scopes.sh +56 -0
  18. package/skills/platform-lightning-type-widget-coordinate/references/build-plan-format.md +1 -0
  19. package/skills/platform-sandbox-configure/SKILL.md +17 -2
  20. package/skills/platform-trial-org-create/SKILL.md +175 -0
  21. package/skills/platform-trial-org-create/examples/create_request.json +9 -0
  22. package/skills/platform-trial-org-create/examples/error_response.json +41 -0
  23. package/skills/platform-trial-org-create/examples/success_response.json +27 -0
  24. package/skills/platform-trial-org-create/references/error_codes.md +42 -0
  25. package/skills/platform-trial-org-create/references/signup_request_fields.md +69 -0
  26. package/skills/platform-trial-org-create/scripts/create_signup_request.sh +175 -0
  27. package/skills/platform-trial-org-create/scripts/get_signup_request.sh +155 -0
  28. package/skills/platform-widget-generate/SKILL.md +47 -6
  29. package/skills/platform-widget-generate/examples/conditional.json +3 -3
  30. package/skills/platform-widget-generate/examples/list-with-foreach.json +2 -2
  31. package/skills/platform-widget-generate/examples/single-object.json +2 -2
  32. package/skills/platform-widget-generate/references/widget-bundle-layout.md +1 -1
  33. package/skills/service-agentforce-channel-configure/SKILL.md +271 -0
  34. package/skills/service-agentforce-channel-configure/references/agent-wiring.md +97 -0
  35. package/skills/service-agentforce-channel-configure/references/channel-branch-email.md +145 -0
  36. package/skills/service-agentforce-channel-configure/references/channel-branch-voice.md +69 -0
  37. package/skills/service-agentforce-channel-configure/references/channel-types.md +61 -0
  38. package/skills/service-agentforce-channel-configure/references/live-traffic-gate.md +86 -0
  39. package/skills/service-agentforce-channel-configure/references/queue-resolution.md +135 -0
  40. package/skills/service-agentforce-channel-configure/references/routing-flow.md +384 -0
  41. package/skills/service-catalog-template-deploy/SKILL.md +310 -0
  42. package/skills/service-catalog-template-deploy/references/cli-invocation.md +258 -0
  43. package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +164 -0
  44. package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +94 -0
  45. package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +331 -0
  46. package/skills/service-catalog-template-search/SKILL.md +212 -0
  47. package/skills/service-catalog-template-search/references/cli-invocation.md +128 -0
  48. package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +205 -0
  49. package/skills/service-concierge-portal-generate/SKILL.md +126 -0
  50. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1428 -0
  51. package/skills/service-digital-engagement-channel-configure/SKILL.md +46 -6
  52. package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +2 -1
  53. package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -1
  54. package/skills/service-helpagent-coordinate/README.md +8 -2
  55. package/skills/service-helpagent-coordinate/SKILL.md +126 -130
  56. package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +70 -53
  57. package/skills/service-helpagent-coordinate/references/agent-script.md +571 -457
  58. package/skills/service-helpagent-coordinate/references/channel-voice.md +38 -9
  59. package/skills/service-helpagent-coordinate/references/channel-web-chat.md +173 -49
  60. package/skills/service-helpagent-coordinate/references/output-report-format.md +126 -0
  61. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +153 -0
  62. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +79 -0
  63. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +35 -0
  64. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +271 -0
  65. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/references/cli-invocation.md +265 -0
  66. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-enable-plan.mjs +220 -0
  67. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-final-report.mjs +102 -0
  68. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/record-enable-result.mjs +73 -0
  69. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/SKILL.md +206 -0
  70. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/references/cli-invocation.md +194 -0
  71. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/scripts/classify-readiness.mjs +223 -0
  72. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +45 -7
  73. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +45 -5
  74. package/skills/service-itsm-agentic-setup-configure/SKILL.md +116 -0
  75. package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +64 -0
  76. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +158 -0
  77. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +361 -0
  78. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/error-taxonomy.md +44 -0
  79. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +66 -0
  80. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +66 -0
  81. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/specialized-templates.md +148 -0
  82. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +169 -0
  83. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/build-create-body.mjs +116 -0
  84. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +185 -0
  85. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +168 -0
  86. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/create-scratch-dir.mjs +58 -0
  87. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +197 -0
  88. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +186 -0
  89. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/action-availability.md +51 -0
  90. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +345 -0
  91. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/error-taxonomy.md +44 -0
  92. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +63 -0
  93. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +44 -0
  94. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +149 -0
  95. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/build-create-body.mjs +110 -0
  96. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-action-availability.mjs +201 -0
  97. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-activate-result.mjs +135 -0
  98. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +194 -0
  99. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +158 -0
  100. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/create-scratch-dir.mjs +58 -0
  101. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +191 -0
  102. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +133 -0
  103. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +71 -0
  104. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +308 -0
  105. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +23 -0
  106. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +193 -0
  107. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +57 -0
  108. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +394 -0
  109. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/SKILL.md +266 -0
  110. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/cli-invocation.md +106 -0
  111. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/helper-contracts.md +142 -0
  112. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/permset-topology.md +82 -0
  113. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-action-surface.mjs +137 -0
  114. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-assignment-state.mjs +99 -0
  115. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-permset-availability.mjs +120 -0
  116. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/resolve-target-user.mjs +86 -0
  117. package/skills/service-itsm-agentic-setup-uel-user-create/SKILL.md +284 -0
  118. package/skills/service-itsm-agentic-setup-uel-user-create/references/mcp-invocation.md +302 -0
  119. package/skills/service-itsm-channels-coordinate/SKILL.md +472 -0
  120. package/skills/service-itsm-incident-mgmt-configure/SKILL.md +212 -0
  121. package/skills/service-itsm-incident-mgmt-configure/references/mcp-invocation.md +225 -0
  122. package/skills/service-itsm-incident-priority-configure/SKILL.md +53 -12
  123. package/skills/service-itsm-swarming-configure/SKILL.md +212 -0
  124. package/skills/service-itsm-teams-configure/SKILL.md +395 -0
  125. package/skills/service-itsm-teams-configure/references/azure-credential-population.md +213 -0
  126. package/skills/service-itsm-teams-configure/references/gotchas.md +23 -0
  127. package/skills/service-itsm-teams-coordinate/SKILL.md +175 -0
  128. package/skills/service-itsm-teams-coordinate/examples/output-templates.md +85 -0
  129. package/skills/service-itsm-teams-debug/SKILL.md +144 -0
  130. package/skills/service-itsm-teams-debug/references/configuration-checklists.md +277 -0
  131. package/skills/service-itsm-teams-debug/references/report-generation.md +95 -0
  132. package/skills/service-itsm-teams-employee-agent-configure/SKILL.md +139 -0
  133. package/skills/service-itsm-teams-employee-agent-configure/assets/Teams_AgentForce.EmbeddedServiceConfig-meta.xml +43 -0
  134. package/skills/service-itsm-teams-employee-agent-configure/references/teams-embedded-employee-agent.md +480 -0
  135. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +232 -0
  136. package/skills/service-itsm-teams-itservice-configure/SKILL.md +391 -0
@@ -0,0 +1,1428 @@
1
+ # Agentforce Concierge portal — deploy runbook
2
+
3
+ > **When to read this file.** Load it as part of `service-concierge-portal-generate` — read from the skill's entry point after org alias and agent are resolved. Also used by `service-helpagent-coordinate` (Checkpoint 3 → Help Portal branch) via delegation to this skill.
4
+
5
+ Help Portal deploys an **Agentforce Concierge experience** on an LWR Experience Cloud site: an agent-first landing page with a welcome greeting, prompt bar, suggestion chiclets, and full chat surface. This is different from Web Chat, which embeds a chat *widget* on top of an existing site. The Concierge experience IS the portal.
6
+
7
+ The agent script does **not** change here — this is channel/site metadata around the agent.
8
+
9
+ ## Execution model — headless with one operator pause
10
+
11
+ Every step in §A–§S below is executed headlessly via `sf` CLI + Data API + Metadata API + Tooling API. There is exactly **one** step that requires the operator to click a Setup toggle: enabling the **Agentforce Orchestrator** OrgPerm (see §F.4). When the flow reaches that step, pause, present the operator with a direct Setup URL and clear instructions, and wait for them to reply `done` before continuing. Every other 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
+
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
+
15
+ At the end of the run, print the live customer-facing portal URL for the operator to open in an Incognito window.
16
+
17
+ ---
18
+
19
+ ## §0 — Successful headless path (execute top-to-bottom)
20
+
21
+ > **This is the runbook.** Execute in order. Only dive into detail sections (§A–§S) if a step fails or you need branch-specific templates.
22
+ >
23
+ > **Session variables** to capture up front (§A + §C output):
24
+ >
25
+ > ```bash
26
+ > ORG=<sf org alias> # e.g. helpportal
27
+ > SITE_NAME=<Site Name from §A> # e.g. "Skyline Support Center"
28
+ > BUNDLE_NAME=<sanitized bundle name> # ChatterNetworkPicasso name — e.g. Skyline_Support_Center1 (trailing "1")
29
+ > WRAPPER_SITE_NAME=<ChatterNetwork wrapper name> # BUNDLE_NAME with trailing "1" stripped — e.g. Skyline_Support_Center
30
+ > NETWORK_ID=<from POST /connect/communities> # e.g. 0DBgL0000026DJBWA2
31
+ > BOT_ID=<Bot's BotDefinition Id> # from prior agent-authoring flow
32
+ > BOT_DEV_NAME=<Bot DeveloperName> # e.g. Help_Agent
33
+ > ESC_ID=<EmbeddedServiceConfig Id, auth> # created in §F.3.a
34
+ > GUEST_ESC_ID=<EmbeddedServiceConfig Id, guest> # single-ESD model: same as ESC_ID; split model: separate
35
+ > NSSE_ID=<NetworkSelfServiceExtension Id> # created in §F.3.b
36
+ > SEARCH_CUST_ID=<SearchCustomization Id> # created in §O.2
37
+ > SCRT2_URL=<from ESD Install Code Snippet> # e.g. https://…my.salesforce-scrt.com
38
+ > BASE_SITE_URL=<siteURL from ESD snippet, base> # e.g. https://…my.site.com
39
+ > PUBLISHED_PORTAL_URL=$BASE_SITE_URL/$URL_PATH/ # e.g. https://…my.site.com/skylinesupportcenter/ — trailing slash, NO /s suffix (LWR, not Aura)
40
+ > ```
41
+ >
42
+ > ### Stage-by-stage execution order
43
+ >
44
+ > | # | Step | Command / surface | Verify |
45
+ > |---|---|---|---|
46
+ > | 1 | Collect branding + access | §A `AskUserQuestion` × 7 | User confirms values |
47
+ > | 2 | Prerequisite check | §B — `GET /connect/communities` returns 200 | JSON has `communities` array |
48
+ > | 3 | Provision the site | §C — `POST /connect/communities` | Returns `id` (Network Id) |
49
+ > | 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 |
50
+ > | 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 |
51
+ > | 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 |
52
+ > | 8 | **⏸ Enable Agentforce Orchestrator (operator pause)** | §F.4 — preflight SOQL on `AgenticCtxtDecorDefinition`; if not supported, pause and ask operator to flip the toggle at the printed Setup URL; wait for `done` reply; then `POST /connect/self-service/setup/agentOrchestrator {name, developerName, agentId}` | Connect GET returns orchestrator with agentId |
53
+ > | 9 | Deploy the bundle | §G — `sf project deploy start -m DigitalExperienceBundle:site/$BUNDLE_NAME` | Status: Succeeded |
54
+ > | 10 | Publish the site | §I — `POST /connect/communities/$NETWORK_ID/publish`; PATCH `Network.Status=Live` | Site loads at `PUBLISHED_PORTAL_URL` |
55
+ > | 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 |
56
+ > | 12 | CORS — 3 writes | §L.1 — 3× `CorsWhitelistEntry` inserts for SCRT2/base/portal URLs | Query returns 3 rows |
57
+ > | 13 | Trusted URLs — 3 mdapi records | §L.2 — deploy 3 `CspTrustedSite` records with 6 directives each | SOQL returns 3 rows |
58
+ > | 14 | Trusted Domains for Inline Frames | §L.3 — 3× `IframeWhiteListUrl` org-level inserts (`Context=LightningOut`); 2× `SiteIframeWhiteListUrl` on the ChatterNetwork wrapper SiteId | Both queries return rows |
59
+ > | 15 | Experience Builder Security — Clickjack + CSP + LWS | §L.4 — `CustomSite:$WRAPPER_SITE_NAME` mdapi flip `clickjackProtectionLevel=AllowAllFraming`; DEB bundle `mainAppPage/content.json` flip `isLockerServiceEnabled=false, isRelaxedCSPLevel=true`; redeploy + republish | Roundtrip retrieve confirms all 3 |
60
+ > | 16 | AI Experiences toggles | §M — preflight `AiExperienceContextDefinition` describe (skip section on NOT_FOUND). PATCH `Network.OptionsDataCategoryContextPassingEnabled=true, OptionsSlfSrvcPersonalizationEnabled=false`. | Network PATCHes return 204 |
61
+ > | 17 | Bot-routing wiring | §N — 3 writes: `PresenceUserConfigUser` (BotUser → default presence), `GroupMember` (BotUser → fallback queue), `Group.QueueRoutingConfigId` (queue → MessagingSession routing config) | SOQL verify all 3 |
62
+ > | 18 | Search Manager Query Configuration | §O — deploy `searchCustomization` mdapi (channel=`LWRExperienceSiteSearch`, `<selectedObject>` for Case/Knowledge__kav/Product2); §O.5 PATCH `NSSE.SearchCustomizationId=$SEARCH_CUST_ID` | Roundtrip retrieve confirms |
63
+ > | 19a | S.1 — Member profiles *(only for auth paths)* | §S.1 — 4× `POST /sobjects/NetworkMemberGroup {NetworkId, ParentId=<ProfileId>}` for Customer Community × 4 | SOQL count ≥ 5 (incl. SysAdmin) |
64
+ > | 19b | S.2 — Login & Reg + reCAPTCHA | §S.2 — PATCH `NetworkAuthApiSettings` booleans + `RecaptchaScoreThreshold` via Data API | Row PATCHed; SOQL confirms fields |
65
+ > | 19c | S.3 — Head Markup verify | §S.3 — grep bundle `mainAppPage/content.json` for `headMarkup` key (default-populated) | Key present |
66
+ > | 20 | Publish + Activate (final) | §S.5 — `sf community publish`, then `sf data update record Network.Status=Live` | `Status=Live` + `curl -I` returns HTTP/2 301 |
67
+ > | 21 | Smoke verify guest access + print URL | §J + §K — `curl` `/concierge/config` as guest; print `PUBLISHED_PORTAL_URL` and instruct the operator to open it in Incognito | All 3 chatConfig fields populated |
68
+ >
69
+ > ### The 3 preflight skips (do NOT block deploy if these fire)
70
+ >
71
+ > - **`AgenticCtxtDecorDefinition` sObject "not supported" AND the operator confirms they cannot enable the Orchestrator right now** → skip §F.4. Bot works via `SessionHandlerId` alone. Prompt bar responses are ungrounded, but chat still routes.
72
+ > - **`AiExperienceContextDefinition` sObject NOT_FOUND** → skip §M (older-API orgs). No AI Experiences panel exists.
73
+ > - **`SearchCustomization` describe NOT_FOUND** → skip §O (older-API orgs). Chat still works; search corpus falls back to platform default.
74
+ >
75
+ > ### Steps that require a manual UI touch (log and continue — do NOT abort)
76
+ >
77
+ > - **§S.4 Progressive Rendering OFF toggle** — UI-only at v67 with no verified metadata surface. Default is empirically OFF on freshly-provisioned bundles — skip on a fresh deploy. If a downstream visual test shows jank, log the step and continue. Fallback: Experience Builder → Settings → Advanced → Progressive Rendering.
78
+ > - **§P Smart Search Assistance subagent** — asset lives in a Salesforce-hosted registry; no headless path exists yet. Log the step and continue; chat still works without it (Knowledge grounding path via ADL is unaffected).
79
+ >
80
+ > ### Critical gotchas the recipe traps for you
81
+ >
82
+ > 1. **`BUNDLE_NAME` (e.g. `Skyline_Support_Center1`) vs `WRAPPER_SITE_NAME` (`Skyline_Support_Center`)** — the DEB Picasso bundle has the `*1` suffix; the ChatterNetwork wrapper site does NOT. `CustomSite:$BUNDLE_NAME` returns an empty package; only `CustomSite:$WRAPPER_SITE_NAME` retrieves the `.site` XML.
83
+ > 2. **Never inject credential attributes (`conversationPage`, `Org_ID`, `siteURL`, `scrt2URL`) on `conciergePromptBar`/`conciergeChat`/`conciergeChicletGroupContainer`** — v67 DEB pipeline strips them silently. Runtime resolves credentials from ESC + NSSE + Site + Network. Two attributes on `conciergeWelcomeGreeting` are valid: `textAlignment` and `customGreeting`.
84
+ > 3. **Route `urlPrefix` MUST be `conversation`, NOT `concierge`** — `conciergePromptBar` hardcodes submit navigation to `routeType=concierge-conversation` / `urlPrefix=conversation`. Renaming causes "Invalid Page" on prompt-bar submit.
85
+ > 4. **`CspTrustedSite` v67 accepts only 6 directive booleans** — `connect-src`, `font-src`, `frame-src`, `img-src`, `media-src`, `style-src`. Adding others fails deploy with schema error.
86
+ > 5. **`OptionsGuestChatterEnabled` AND `OptionsGuestMemberVisibility` both default to False on freshly-POSTed Networks** — always PATCH BOTH to True in §L.0, not conditionally. Also verify `EmbeddedServiceConfig.AreGuestUsersAllowed=true` on the auth ESC. These three flags are necessary but NOT sufficient — the §F.3.d guest permset must also be assigned to the site's Guest User.
87
+ > 6. **Republish is mandatory after DEB flag changes** — `isRelaxedCSPLevel` / `isLockerServiceEnabled` / `headMarkup` / `authenticationType` do not reach the runtime until `POST /connect/communities/$NETWORK_ID/publish` completes. Deploy alone is insufficient.
88
+ >
89
+ > ### If the recipe fails: which section to dive into
90
+ >
91
+ > - Deploy fails / schema error / bundle-shape confusion → §F.0 (full JSON templates)
92
+ > - Guest `/concierge/config` returns 401 → §L.0 first, then §F.3 wiring
93
+ > - `/conversation/guest` is BLANK in Incognito but `/concierge/config` returns 200 → run BOTH checks in order: (a) §L.0 THREE-flag check, (b) §F.3.d permset check — confirm the site's Guest User has the `<Bundle>_Guest_Concierge` PermissionSetAssignment
94
+ > - Guest `/concierge/config` returns 200 but prompt bar says "Agents are not available" → §N (bot-routing wiring)
95
+ > - Prompt bar submit lands on "Invalid Page" → §F.0.a (Conversation route/view templates)
96
+ > - Chat responds in Incognito but blank in SysAdmin session → expected, not a bug (see anti-patterns table)
97
+
98
+ ---
99
+
100
+ ## Step A — Collect branding + access
101
+
102
+ Ask each question via `AskUserQuestion`, one at a time, in the order below. This mirrors the Salesforce Quick Setup wizard's Branding + Login panels.
103
+
104
+ **Every color field — Primary Color, Text Color, Border Color, Page Background Color — is mandatory. The assistant MUST ask all four, in order, one at a time. Do not skip any. Do not silently apply defaults. Logo is handled separately in §D.1 and is NOT asked in this flow.**
105
+
106
+ **Two-option shape for every color question.** Each `AskUserQuestion` for the four colors MUST present **exactly two options**. The harness renders an *Other* free-text input on every question automatically — that is where custom hex values are typed; it is **not** a third option.
107
+
108
+ - Option 1: *"Use default `<value>`"* — accepts the stock value.
109
+ - Option 2: *"Type a custom value"* — the user provides a hex via the *Other* free-text input.
110
+
111
+ Validate hex responses against `^#[0-9a-fA-F]{6}$`. If invalid, re-ask.
112
+
113
+ 1. **Site Name** — required, free text.
114
+ - Example: `Skyline Support Center`
115
+ - Used for the Connect API `communities` POST body (`name` field).
116
+ - Derive the URL path prefix by sanitizing → **alphanumeric only, no dashes, no spaces** (e.g. `Skyline Support Center` → `skylinesupportcenter`). The Connect API rejects any other characters with `INVALID_INPUT: The URL can only contain alphanumeric characters.`
117
+
118
+ 2. **Primary Color** — mandatory. Default `#066afe`.
119
+
120
+ 3. **Text Color** — mandatory. Default `#181818`.
121
+
122
+ 4. **Border Color** — mandatory. Default `#e5e5e5`.
123
+
124
+ 5. **Page Background Color** — mandatory. Default `#ffffff`.
125
+
126
+ 6. **Visitor access** — mandatory `AskUserQuestion` with two options; drives Step E's `authMode` **and** the `authenticationType` field on the site config in Step F.0.
127
+ - *"Public access — guests and authenticated visitors (Recommended)"* → `authMode = UnAuth`, `authenticationType = AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED`.
128
+ - *"Authenticated visitors only (no guests)"* → `authMode = Auth`, `authenticationType = AUTHENTICATED`. Follow up with the same JWT-configuration `AskUserQuestion` documented in `channel-web-chat.md` Step B.
129
+
130
+ There is no "anonymous only" mode — `AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED` accepts both guests and logged-in users. The legacy `UNAUTHENTICATED` value is not recommended and must not be offered.
131
+
132
+ **Confirm the collected values back to the user before starting deploy.** Present as a single summary block and give them one chance to change any field.
133
+
134
+ ## Step B — Prerequisite check
135
+
136
+ Before provisioning, verify the org has:
137
+
138
+ - **Digital Experiences enabled.** Definitive headless signal: `GET /services/data/v67.0/connect/communities` returns 200 with a JSON body containing a `communities` array. HTTP 404 or a `NOT_FOUND` payload means Digital Experiences is disabled. Enabling the feature is a Setup-UI-only one-time-per-org step on v67 — on any trial/SDO/SDO-Lite org this is pre-enabled; on a clean Developer Edition it isn't. Hard-fail with the setup URL fallback: `<INSTANCE_LIGHTNING_URL>/lightning/setup/CommunitiesSettings/home`.
139
+ - **Experience Cloud** enabled (Communities license active).
140
+ - **Agentforce** enabled — required for the Concierge components to render.
141
+ - **Data Cloud** enabled and permission sets assigned to the running user (the coordinate skill's readiness check at §4.0 handles this; do not re-run here).
142
+ - **Knowledge** enabled with published articles — the Concierge prompt bar and chat surface both need Knowledge to answer.
143
+
144
+ If any prerequisite fails, hard-stop and surface the specific gap.
145
+
146
+ ## Step C — Provision the Experience Cloud site
147
+
148
+ Create a new LWR site via Connect API — **always** use the *Build Your Own (LWR)* template. Do not use the *Help Center* template (it's Aura-based and won't host Concierge components).
149
+
150
+ Construct the `description` from context before the POST: `"Agentforce Concierge portal for <Site Name>, powered by the <BOT_DEV_NAME> agent."` (e.g. `"Agentforce Concierge portal for Claude Skill Testing, powered by the Yoda agent."`).
151
+
152
+ ```bash
153
+ SITE_DESCRIPTION="Agentforce Concierge portal for $SITE_NAME, powered by the $BOT_DEV_NAME agent."
154
+
155
+ sf api request rest "/services/data/v67.0/connect/communities" \
156
+ --method POST \
157
+ --body "{
158
+ \"name\": \"$SITE_NAME\",
159
+ \"urlPathPrefix\": \"$URL_PATH\",
160
+ \"templateName\": \"Build Your Own (LWR)\",
161
+ \"description\": \"$SITE_DESCRIPTION\"
162
+ }" \
163
+ --target-org $ORG
164
+ ```
165
+
166
+ > **`urlPathPrefix` is alphanumeric-only.** No dashes, no underscores, no spaces.
167
+
168
+ Site creation is async. The Connect API returns a `jobId`; poll `BackgroundOperation` until `Status = Complete`:
169
+
170
+ ```bash
171
+ sf data query --target-org $ORG --json --query \
172
+ "SELECT Id, Status, Error FROM BackgroundOperation WHERE Id='<job-id>'"
173
+ ```
174
+
175
+ **Resolve the bundle name.** Salesforce creates two Site rows and one Network — the LWR Site has `1` appended to the sanitized site name with `SiteType = 'ChatterNetworkPicasso'`; the non-`1` Site is the vforcesite wrapper (`ChatterNetwork`) and is not what you edit. Confirm both via SOQL, then keep only the Picasso Id:
176
+
177
+ ```bash
178
+ sf data query --target-org $ORG --json --query \
179
+ "SELECT Id, Name, SiteType, UrlPathPrefix FROM Site WHERE UrlPathPrefix='<sanitized-url-path>'"
180
+ ```
181
+
182
+ **Set the Site description.** The Connect API `description` field populates `Network.Description`, not `Site.Description`. Setup → All Sites shows `Site.Description`, so patch it explicitly on the Picasso Site after the SOQL above resolves `$PICASSO_SITE_ID`:
183
+
184
+ ```bash
185
+ sf data update record -o $ORG --sobject Site --record-id $PICASSO_SITE_ID \
186
+ --values "Description='$SITE_DESCRIPTION'"
187
+ ```
188
+
189
+ **Verify the site is LWR before proceeding.** After `BackgroundOperation` completes, confirm the new bundle appears under `DigitalExperienceBundle` — NOT `ExperienceBundle`. If it only appears under `ExperienceBundle` the wrong template was used (an Aura site was created) and you must delete it via Setup → All Sites and recreate with `templateName: "Build Your Own (LWR)"`:
190
+
191
+ ```bash
192
+ sf org list metadata --metadata-type DigitalExperienceBundle --target-org $ORG --json | python3 -c "
193
+ import sys,json
194
+ names=[r['fullName'] for r in json.load(sys.stdin).get('result',[])]
195
+ target='site/$BUNDLE_NAME'
196
+ print('✅ LWR confirmed' if target in names else '❌ NOT a DEB — wrong template, delete and recreate')
197
+ "
198
+ ```
199
+
200
+ - Newer orgs (Summer '25+): site appears under **`DigitalExperienceBundle`** → §F.0 path.
201
+ - Legacy orgs: site appears under **`ExperienceBundle`** only → §F.1 path (this is expected for pre-Summer '25 orgs, not an error).
202
+
203
+ Store the metadata type and full name in `$BUNDLE_TYPE` and `$BUNDLE_NAME`.
204
+
205
+ ## Step D — Apply branding
206
+
207
+ Best-effort branding. If any of the writes below fails, log the failure, continue the deploy, and surface the gap to the operator at the end — do NOT abort.
208
+
209
+ **Apply colors during the Step F bundle edit — not as a separate deploy.** The four colors from §A live in `sfdc_cms__brandingSet/<themeName>/content.json` inside the retrieved DEB bundle. Edit this file **in the same pass** as the home-page Concierge component injection, before the single Step G deploy. Doing it as a separate deploy wastes a round-trip.
210
+
211
+ **Color → brandingSet field mapping:**
212
+
213
+ | §A field | `content.json` key |
214
+ |---|---|
215
+ | Primary Color | `PrimaryAccentColor` |
216
+ | Text Color | `TextColor` |
217
+ | Border Color | `_NeutralColor` |
218
+ | Page Background Color | `BackgroundColor` |
219
+
220
+ Edit those four keys in `sfdc_cms__brandingSet/<themeName>/content.json`. Leave all other keys unchanged. The theme name is whatever directory exists under `sfdc_cms__brandingSet/` in the retrieved bundle (e.g. `Build_Your_Own_LWR`).
221
+
222
+ **Logo:**
223
+
224
+ Leave `SiteLogo` and `_SiteLogoUrl` as empty strings — the Concierge page renders without a logo. See §D.1 for the optional logo branch if a custom logo is needed.
225
+
226
+ If a branding step fails at runtime: deploy the portal without branding and note "Apply branding via Experience Builder → Branding" in the operator handoff.
227
+
228
+ ## Step E — Bind the agent via MessagingChannel
229
+
230
+ > **⚠️ Bot provisioning prerequisite — the Bot MUST be created via the Setup wizard's "From Template" path, not via a headless Metadata deploy or direct `POST /sobjects/BotDefinition`.** A Bot created outside the wizard has `BotDefinition.AgentTemplate=null` and is silently excluded from the Agentforce Orchestrator's Add Tool → Agent picker. The field is not writeable via any public API — the only recovery is to **delete the Bot and recreate it via Setup → Agentforce Agents → New Agent → From Template → Agentforce Service Agent**. Assume the Bot pre-exists (created by the user via the wizard).
231
+
232
+ This step provisions the MessagingChannel and wires the agent's routing and escalation. **This skill owns none of that work** — it delegates entirely to `service-digital-engagement-channel-configure` (channel infrastructure) and `service-agentforce-channel-configure` (queue, routing, escalation). This ensures queue and escalation questions are always asked by the right skills, not silently resolved here.
233
+
234
+ **Substep 1 — Activate the Bot first (always).**
235
+
236
+ ```bash
237
+ sf agent activate -o $ORG --api-name $BOT_DEV_NAME
238
+ ```
239
+
240
+ Verify `BotVersion.Status = Active` before continuing. The channel PATCH is rejected with "Only active Agentforce Service Agents are supported" if the bot is inactive.
241
+
242
+ **Substep 2 — Resolve or create the MessagingChannel.**
243
+
244
+ Branch on whether `MESSAGING_CHANNEL_DEV_NAME` was passed in:
245
+
246
+ - **Passed in:** verify the channel exists, is Active, and is bound to this agent:
247
+ ```bash
248
+ sf data query --target-org $ORG --json \
249
+ --query "SELECT Id, DeveloperName, IsActive, SessionHandlerId FROM MessagingChannel WHERE DeveloperName='$MESSAGING_CHANNEL_DEV_NAME'"
250
+ ```
251
+ If Active and `SessionHandlerId = $BOT_ID` → capture `MESSAGING_CHANNEL_ID` and skip to Substep 3.
252
+ If not Active or not bound to this agent → treat as not passed in and delegate below.
253
+
254
+ - **Not passed in:** delegate to **`service-digital-engagement-channel-configure`** to create the channel. Pass:
255
+ - Org: `$ORG`
256
+ - Channel type: `EmbeddedMessaging`
257
+ - Agent DeveloperName: `$BOT_DEV_NAME`
258
+ - `authMode`: from Step A #7 (`UnAuth` for access options 1/2; `Auth` for option 3)
259
+
260
+ On completion, capture `MESSAGING_CHANNEL_DEV_NAME` and `MESSAGING_CHANNEL_ID`.
261
+
262
+ **Substep 3 — Wire routing and escalation.**
263
+
264
+ Delegate to **`service-agentforce-channel-configure`** (all three phases). Pass:
265
+ - Org: `$ORG`
266
+ - Agent DeveloperName: `$BOT_DEV_NAME`
267
+ - Channel type: Enhanced Chat (EmbeddedMessaging)
268
+ - MessagingChannel DeveloperName: `$MESSAGING_CHANNEL_DEV_NAME`
269
+
270
+ That skill will:
271
+ - Phase 1: resolve or create the fallback queue
272
+ - Phase 2 Branch A: bind the agent via Data API PATCH — `sessionHandlerAsa` is NOT accepted by the v67 Metadata API, so the channel XML is deployed with only `<sessionHandlerType>AgentforceServiceAgent</sessionHandlerType>` + `<sessionHandlerQueue>`, then bound via `sf api request rest --method PATCH -o $ORG "/services/data/v67.0/sobjects/MessagingChannel/<CHAN_ID>" --body "{\"SessionHandlerId\":\"<BOT_ID>\",\"FallbackQueueId\":\"<QUEUE_ID>\"}"`. Bot must be Active before the PATCH. Verify `SessionHandlerId` + `FallbackQueueId` non-null via SOQL.
273
+ - Phase 3: ask the operator about outbound escalation and wire the `connection customer_web_client:` block if requested
274
+
275
+ ## Step F — Deploy Concierge components into the bundle
276
+
277
+ > **Successful headless path (one recipe, run top-to-bottom):**
278
+ >
279
+ > 1. **Welcome Greeting + Prompt Bar + Chiclet on Home** → three `runtime_service_concierge:*` components inside `sfdc_cms__view/home/content.json`. Set `textAlignment` and `customGreeting` on `conciergeWelcomeGreeting`. **Do NOT populate credential attributes** on `conciergePromptBar`/`conciergeChicletGroupContainer` — v67 DEB strips them. `conciergeChat` goes on the Conversation page only. See §F.0 for the JSON snippets.
280
+ > 2. **Link prompt bar to Query Configuration** → does NOT live on the component. Lives on `NetworkSelfServiceExtension.SearchCustomizationId` (per-Network row). Recipe in §O.5.
281
+ > 3. **Concierge Conversation page** → `sfdc_cms__route/Conversation__c/` + `sfdc_cms__view/conversation/`. **`urlPrefix` must be `conversation`, not `concierge`.** Copy both directories from a working reference bundle (see §F.0.a); do NOT hand-author the stringly-typed JSON.
282
+ > 4. **Credentials on `conciergeChat`** → **no-op at the metadata layer**. Runtime resolves Org_ID / ESD_Developer_Name / siteURL / scrt2URL from `EmbeddedServiceConfig` + `NetworkSelfServiceExtension` + `Site` + `Network` at page-render time.
283
+ > 5. **Concierge Sidebar in Theme Footer** → add `runtime_service_concierge:conciergeNavigationBar` to the `footer` region of `sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json`. See §F.0 for the JSON shape.
284
+ >
285
+ > The whole stage is a single mdapi deploy of the bundle (Step G) + one NSSE PATCH (§O.5).
286
+
287
+ **The retrieve/edit shape depends on `$BUNDLE_TYPE` from Step C.** `sf project retrieve start` must run from a valid SFDX project workspace (`sfdx-project.json` at cwd or above).
288
+
289
+ ### F.0 — DigitalExperienceBundle path *(newer orgs — Summer '25+ CMS-based LWR)*
290
+
291
+ Retrieve:
292
+
293
+ ```bash
294
+ sf project retrieve start --metadata "DigitalExperienceBundle:site/$BUNDLE_NAME" --target-org $ORG
295
+ ```
296
+
297
+ The bundle lands at `force-app/main/default/digitalExperiences/site/$BUNDLE_NAME/` with these sub-directories: `sfdc_cms__appPage/`, `sfdc_cms__view/`, `sfdc_cms__route/`, `sfdc_cms__theme/`, `sfdc_cms__brandingSet/`, `sfdc_cms__site/`, plus a top-level `.digitalExperience-meta.xml`.
298
+
299
+ **Component JSON shape is different from the classic ExperienceBundle:**
300
+
301
+ - Component identifier field is `"definition"`, **not** `"componentName"`.
302
+ - Components live in nested `children` arrays under section → column → region → root, not in a flat `components` array.
303
+ - IDs are full UUIDs (36 chars with dashes). Generate with `python3 -c 'import uuid; print(uuid.uuid4())'` or `uuidgen`.
304
+ - No `renderPriority` or `renditionMap` fields.
305
+
306
+ **Guest access — headless via site-level `authenticationType`.**
307
+
308
+ | Aspect | Value |
309
+ |---|---|
310
+ | Metadata type | `DigitalExperienceBundle` |
311
+ | Bundle member | `sfdc_cms__site/$BUNDLE_NAME/content.json` |
312
+ | Field | `contentBody.authenticationType` (string enum, required) |
313
+ | Deploy | `sf project deploy start --metadata "DigitalExperienceBundle:site/$BUNDLE_NAME"` (Step G) |
314
+ | Activation | Deploy alone doesn't push the flag to the runtime — Step I's publish + `Network.Status=Live` PATCH does. |
315
+
316
+ The retrieved file ships with `"authenticationType" : "AUTHENTICATED"`. Change it to the value driven by Step A #7:
317
+
318
+ ```json
319
+ {
320
+ "type" : "sfdc_cms__site",
321
+ "title" : "<Site Name>",
322
+ "contentBody" : {
323
+ "authenticationType" : "AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED"
324
+ },
325
+ "urlName" : "<sanitized-url-path>"
326
+ }
327
+ ```
328
+
329
+ Valid enum values:
330
+
331
+ | Value | Meaning | When to set |
332
+ |---|---|---|
333
+ | `AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED` | Site accepts both guests and authenticated users. | Step A #7 option 1 (UnAuth) — the recommended default |
334
+ | `AUTHENTICATED` | Login required. Guests bounced to `/login`. | Step A #7 option 2 (Auth-only) |
335
+ | `UNAUTHENTICATED` | Legacy public-only site. Not recommended. | Never |
336
+
337
+ **Home page — edit `sfdc_cms__view/home/content.json`.** The bundle ships with a placeholder `community_builder:htmlEditor` inside the first column of the `content` region. Replace that placeholder's `children` array with these three Concierge components:
338
+
339
+ ```json
340
+ "children" : [ {
341
+ "attributes" : {
342
+ "textAlignment" : "center",
343
+ "customGreeting" : "Welcome! How can I help you today?"
344
+ },
345
+ "definition" : "runtime_service_concierge:conciergeWelcomeGreeting",
346
+ "id" : "<uuid-1>",
347
+ "type" : "component"
348
+ }, {
349
+ "attributes" : { },
350
+ "definition" : "runtime_service_concierge:conciergePromptBar",
351
+ "id" : "<uuid-2>",
352
+ "type" : "component"
353
+ }, {
354
+ "attributes" : { },
355
+ "definition" : "runtime_service_concierge:conciergeChicletGroupContainer",
356
+ "id" : "<uuid-3>",
357
+ "type" : "component"
358
+ } ]
359
+ ```
360
+
361
+ > **⚠️ `conciergeChat` must NOT appear on the Home page.** It belongs only on the Conversation page (`sfdc_cms__view/conversation/content.json`). Adding it to Home renders the full chat UI on every page load, breaking the portal flow.
362
+ >
363
+ > **⚠️ Credential attributes are blocked on v67 orgs.** Do not attempt to inject `conversationPage`, `Org_ID`, `siteURL`, `scrt2URL`, or any credential attribute on `conciergePromptBar`, `conciergeChat`, or `conciergeChicletGroupContainer` — the v67 DEB metadata pipeline strips them silently. Only two attributes on `conciergeWelcomeGreeting` survive: `textAlignment` and `customGreeting`. Font attributes (`fontFamily`, `fontSize`, `fontWeight`) are also stripped silently.
364
+
365
+ **Theme Footer — add `conciergeNavigationBar` to `sfdc_cms__themeLayout/scopedHeaderAndFooter/content.json`.** The Concierge Sidebar (history / navigation) lives in the Theme Footer, not on a page view. Edit the `footer` region to add a `community_layout:section` containing a `footerSection` column region with the component:
366
+
367
+ ```json
368
+ {
369
+ "id": "<footer-region-uuid>",
370
+ "name": "footer",
371
+ "title": "Theme Footer",
372
+ "type": "region",
373
+ "children": [ {
374
+ "attributes": {
375
+ "sectionConfig": "{\"UUID\":\"<section-uuid>\",\"columns\":[{\"UUID\":\"<col-uuid>\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}"
376
+ },
377
+ "children": [ {
378
+ "children": [ {
379
+ "attributes": {},
380
+ "definition": "runtime_service_concierge:conciergeNavigationBar",
381
+ "id": "<component-uuid>",
382
+ "type": "component"
383
+ } ],
384
+ "id": "<col-uuid>",
385
+ "name": "footerSection",
386
+ "title": "Theme Footer",
387
+ "type": "region"
388
+ } ],
389
+ "definition": "community_layout:section",
390
+ "id": "<section-uuid>",
391
+ "type": "component"
392
+ } ]
393
+ }
394
+ ```
395
+
396
+ Replace `<footer-region-uuid>`, `<section-uuid>`, `<col-uuid>`, and `<component-uuid>` with fresh UUIDs (`uuidgen`). Note: `<section-uuid>` and `<col-uuid>` appear twice each — once in `sectionConfig` (the stringified JSON) and once as the `id` field on the corresponding component/region. They must match.
397
+
398
+ ### F.0.a — Conversation route + view (inline templates — no reference bundle needed)
399
+
400
+ The prompt bar's submit navigates to `routeType=concierge-conversation` with `urlPrefix=conversation`. Without a matching route + view in the bundle, LWR returns "Invalid Page" on submit.
401
+
402
+ **Do not copy from a reference bundle** — use the verified templates below directly.
403
+
404
+ **`sfdc_cms__route/Conversation__c/content.json`:**
405
+ ```json
406
+ {
407
+ "type": "sfdc_cms__route",
408
+ "title": "Conversation",
409
+ "contentBody": {
410
+ "activeViewId": "conversation",
411
+ "configurationTags": [],
412
+ "pageAccess": "UseParent",
413
+ "routeType": "concierge-conversation",
414
+ "urlPrefix": "conversation"
415
+ },
416
+ "urlName": "conversation"
417
+ }
418
+ ```
419
+
420
+ **`sfdc_cms__route/Conversation__c/_meta.json`:**
421
+ ```json
422
+ {"apiName":"Conversation__c","type":"sfdc_cms__route","path":"routes"}
423
+ ```
424
+
425
+ **`sfdc_cms__view/conversation/content.json`** — replace `<uuid-chat>`, `<uuid-seo>`, and `<uuid-content-region>` with fresh UUIDs (`uuidgen`). All three must be unique across the entire file. The remaining UUID constants (`sectionConfig`, section `id`, column region `id`, root component `id`) are stable structural anchors and can be reused as-is across deployments:
426
+ ```json
427
+ {
428
+ "type": "sfdc_cms__view",
429
+ "title": "Conversation",
430
+ "contentBody": {
431
+ "component": {
432
+ "children": [
433
+ {
434
+ "children": [
435
+ {
436
+ "attributes": {
437
+ "backgroundImageConfig": "",
438
+ "backgroundImageOverlay": "rgba(0,0,0,0)",
439
+ "componentSpacerSize": "",
440
+ "layoutDirectionDesktop": "row",
441
+ "layoutDirectionMobile": "column",
442
+ "layoutDirectionTablet": "column",
443
+ "maxContentWidth": "",
444
+ "sectionColumnGutterWidth": "",
445
+ "sectionConfig": "{\"UUID\":\"3a36dbf4-ea89-4538-a9a8-9395d75df15f\",\"columns\":[{\"UUID\":\"6005d4c3-89cc-4a96-ab13-20d6104c6ec6\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}",
446
+ "sectionMinHeight": "",
447
+ "sectionVerticalAlign": "flex-start"
448
+ },
449
+ "children": [
450
+ {
451
+ "children": [
452
+ {
453
+ "attributes": {},
454
+ "definition": "runtime_service_concierge:conciergeChat",
455
+ "id": "<uuid-chat>",
456
+ "type": "component"
457
+ }
458
+ ],
459
+ "id": "6005d4c3-89cc-4a96-ab13-20d6104c6ec6",
460
+ "name": "col1",
461
+ "title": "Column 1",
462
+ "type": "region"
463
+ }
464
+ ],
465
+ "definition": "community_layout:section",
466
+ "id": "3a36dbf4-ea89-4538-a9a8-9395d75df15f",
467
+ "type": "component"
468
+ }
469
+ ],
470
+ "id": "<uuid-content-region>",
471
+ "name": "content",
472
+ "title": "Content",
473
+ "type": "region"
474
+ },
475
+ {
476
+ "children": [
477
+ {
478
+ "attributes": {
479
+ "customHeadTags": "",
480
+ "description": "",
481
+ "pageTitle": "Conversation",
482
+ "recordId": "{!recordId}"
483
+ },
484
+ "definition": "community_builder:seoAssistant",
485
+ "id": "<uuid-seo>",
486
+ "type": "component"
487
+ }
488
+ ],
489
+ "id": "7e6fea67-e160-4002-9dba-c0eecd4508c4",
490
+ "name": "sfdcHiddenRegion",
491
+ "title": "sfdcHiddenRegion",
492
+ "type": "region"
493
+ }
494
+ ],
495
+ "definition": "community_layout:sldsFlexibleLayout",
496
+ "id": "c4679f2c-19e8-4d49-a196-440a40533f0c",
497
+ "type": "component"
498
+ },
499
+ "dataProviders": [],
500
+ "themeLayoutType": "Inner",
501
+ "viewType": "concierge-conversation"
502
+ },
503
+ "urlName": "conversation"
504
+ }
505
+ ```
506
+
507
+ **`sfdc_cms__view/conversation/_meta.json`:**
508
+ ```json
509
+ {"apiName":"conversation","type":"sfdc_cms__view","path":"views"}
510
+ ```
511
+
512
+ > **Common sub-errors:** (a) `routeType=home` on the route is wrong — must be `concierge-conversation`; (b) `viewType` must match `routeType` exactly; (c) the `sfdcHiddenRegion` with `community_builder:seoAssistant` is required — omitting it causes a deploy schema error.
513
+
514
+ Verify post-publish: `curl -sSL -w "HTTP=%{http_code}\n" "$PUBLISHED_PORTAL_URL/conversation" -o /dev/null` should chain 301 → 200.
515
+
516
+ ### F.1 — ExperienceBundle path *(legacy orgs, pre-Summer '25)*
517
+
518
+ Retrieve:
519
+
520
+ ```bash
521
+ sf project retrieve start --metadata "ExperienceBundle:$BUNDLE_NAME" --target-org $ORG
522
+ ```
523
+
524
+ Edit `experiences/$BUNDLE_NAME/views/home.json`. Add three components at the beginning of the `content` region's `components` array:
525
+
526
+ ```json
527
+ {
528
+ "componentAttributes": {
529
+ "textAlignment": "center",
530
+ "customGreeting": "Welcome! How can I help you today?"
531
+ },
532
+ "componentName": "runtime_service_concierge:conciergeWelcomeGreeting",
533
+ "id": "<16-char-hex>",
534
+ "renderPriority": "NEUTRAL",
535
+ "renditionMap": {},
536
+ "type": "component"
537
+ },
538
+ {
539
+ "componentAttributes": {},
540
+ "componentName": "runtime_service_concierge:conciergePromptBar",
541
+ "id": "<16-char-hex>",
542
+ "renderPriority": "NEUTRAL",
543
+ "renditionMap": {},
544
+ "type": "component"
545
+ },
546
+ {
547
+ "componentAttributes": {},
548
+ "componentName": "runtime_service_concierge:conciergeChicletGroupContainer",
549
+ "id": "<16-char-hex>",
550
+ "renderPriority": "NEUTRAL",
551
+ "renditionMap": {},
552
+ "type": "component"
553
+ }
554
+ ```
555
+
556
+ Generate a fresh 16-char hex `id` per component (`openssl rand -hex 8`).
557
+
558
+ ### F.2 — Conversation page: full chat surface *(ExperienceBundle path only)*
559
+
560
+ Create two files inside the retrieved bundle.
561
+
562
+ **`routes/conversationPage.json`**:
563
+
564
+ ```json
565
+ {
566
+ "activeViewId": "<view-uuid>",
567
+ "appPageId": "<use-appPageId-from-home.json>",
568
+ "configurationTags": [],
569
+ "devName": "Conversation_Page__c",
570
+ "id": "<16-char-hex>",
571
+ "label": "Conversation Page",
572
+ "pageAccess": "UseParent",
573
+ "routeType": "concierge-conversation",
574
+ "type": "route",
575
+ "urlPrefix": "concierge-chat"
576
+ }
577
+ ```
578
+
579
+ **`views/conversationPage.json`** — hosts `conciergeChat` in the `content` region. Copy the full six-region skeleton (`header`, `content`, `footer`, `sfdcHiddenRegion`, `sidebar`, `sidebarAlt`) from an existing view file in the retrieved bundle, then drop this component into `content`:
580
+
581
+ ```json
582
+ {
583
+ "componentAttributes": {},
584
+ "componentName": "runtime_service_concierge:conciergeChat",
585
+ "id": "<16-char-hex>",
586
+ "renderPriority": "NEUTRAL",
587
+ "renditionMap": {},
588
+ "type": "component"
589
+ }
590
+ ```
591
+
592
+ Also set: `themeLayoutType: "Inner"`, `componentName: "siteforce:sldsThreeCol363Layout"`, `viewType: "concierge-conversation"`.
593
+
594
+ The route's `activeViewId` must match the view's `id`. The `appPageId` on both must match the one from `home.json`.
595
+
596
+ ### F.3 — Wire the Concierge runtime binding *(required)*
597
+
598
+ Four artifacts must exist for the LWR runtime endpoint (`/webruntime/api/services/data/vXX.X/connect/self-service/concierge/config`) to resolve. All four are fully headless.
599
+
600
+ 1. **`EmbeddedServiceConfig` (ESC)** — created via Connect API, same path as Web Chat.
601
+ 2. **`NetworkSelfServiceExtension` (NSSE)** — a runtime record linking the Network to the ESC.
602
+ 3. **PermissionSet + assignment** — the site's guest user must have read access on the objects the endpoint joins through.
603
+ 4. **Republish** — the LWR runtime caches the binding at publish time.
604
+
605
+ **F.3.a — Resolve the EmbeddedServiceConfig.**
606
+
607
+ **Branch: was `ESC_ID` passed in by the calling skill?**
608
+
609
+ - **Yes (passed in):** query the org to confirm the ESC exists:
610
+ ```bash
611
+ sf data query -o $ORG --use-tooling-api \
612
+ -q "SELECT Id, DeveloperName, AreGuestUsersAllowed FROM EmbeddedServiceConfig WHERE Id='$ESC_ID'" --json
613
+ ```
614
+ Capture `ESC_DEV_NAME` and proceed to F.3.b. Verify `AreGuestUsersAllowed=true`; if false, patch it via Tooling API before continuing (see §L.0).
615
+
616
+ - **Not passed in:** delegate to **`service-digital-engagement-deployment-configure`** with these inputs:
617
+
618
+ - **Operation**: `create`
619
+ - **Deployment type**: `Web`
620
+ - **Deployment name / DeveloperName**: `<Bundle>_Concierge` (e.g. `Einstein_Support_Center1_Concierge`) / MasterLabel `<Site Name> Concierge`
621
+ - **Channel**: MessagingChannel DeveloperName from Step E (skill will query the record Id)
622
+ - **hostDomain**: the org's `*.my.site.com` hostname — query it from any existing Site: `SELECT UrlPathPrefix, SiteType FROM Site WHERE SiteType='ChatterNetworkPicasso' LIMIT 1` and derive the base domain from its URL, or read it from `sf org display` instance URL replacing `.salesforce.com` with `.my.site.com` (e.g. `trailsignup-ca7498a0804351.my.site.com`)
623
+
624
+ The skill creates the ESD via `POST /connect/embeddedmessaging/deployment/setup` (V2, Connect API), binds the messaging channel at creation time so `isPublishSuccess: true` is returned, and auto-generates the `ESW_*` scaffolding site and test page in Setup → Embedded Service Deployments.
625
+
626
+ After the skill completes, capture the ESC Id:
627
+
628
+ ```bash
629
+ ESC_ID=$(sf data query -o $ORG --use-tooling-api \
630
+ -q "SELECT Id FROM EmbeddedServiceConfig WHERE DeveloperName='<Bundle>_Concierge'" --json \
631
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
632
+ ```
633
+
634
+ **F.3.b — Insert the NSSE record (standard REST DML).**
635
+
636
+ ```bash
637
+ NETWORK_ID=$(sf data query -o $ORG -q "SELECT Id FROM Network WHERE Name='<Site Name>' LIMIT 1" --json | jq -r '.result.records[0].Id')
638
+ # Guard: abort if NETWORK_ID is unset or null — an empty NetworkId causes a 400 at smoke-test time with no obvious link back to NSSE
639
+ if [ -z "$NETWORK_ID" ] || [ "$NETWORK_ID" = "null" ]; then
640
+ echo "ERROR: NETWORK_ID not resolved. Verify the Network exists and Name matches exactly." >&2; exit 1
641
+ fi
642
+
643
+ sf data create record -o $ORG --sobject NetworkSelfServiceExtension \
644
+ --values "DeveloperName=<Bundle>_Concierge MasterLabel='<Site Name> Concierge' NetworkId=$NETWORK_ID Language=en_US"
645
+ ```
646
+
647
+ **F.3.c — Bind NSSE → ESC via Tooling PATCH.** For UnAuth sites (Step A #7 options 1 & 2), set `GuestEmbeddedServiceCnfgId`. For Auth-only sites (option 3), set `EmbeddedServiceCnfgId`. Setting **both** to the same value fails with `INVALID_INPUT: Select different deployments for guest and authenticated users`.
648
+
649
+ ```bash
650
+ NSSE_ID=$(sf data query -o $ORG -q "SELECT Id FROM NetworkSelfServiceExtension WHERE NetworkId='$NETWORK_ID' LIMIT 1" --json | jq -r '.result.records[0].Id')
651
+ ESC_ID=<from F.3.a>
652
+
653
+ # UnAuth (guest-facing)
654
+ sf api request rest -o "$ORG" --method PATCH \
655
+ "/services/data/v67.0/tooling/sobjects/NetworkSelfServiceExtension/$NSSE_ID" \
656
+ --body "{\"GuestEmbeddedServiceCnfgId\":\"$ESC_ID\"}"
657
+ ```
658
+
659
+ HTTP 204 = success.
660
+
661
+ **F.3.d — 🚨 CRITICAL: Grant the site's guest user read on the runtime-config objects.** Deploy a `PermissionSet` (Guest User License is compatible — do **not** use `Enhanced Chat User`, which requires a license the guest profile can't hold) and assign it to the guest user.
662
+
663
+ > **Missing this step is the single most-common cause of blank `/conversation/guest` when §L.0's three flags are already True.** A PermissionSet that exists but isn't assigned to the Site's `GuestUserId` has zero effect — both `sf project deploy start` AND `sf data create record` must run.
664
+
665
+ Create `force-app/main/default/permissionsets/<Bundle>_Guest_Concierge.permissionset-meta.xml`:
666
+
667
+ ```xml
668
+ <?xml version="1.0" encoding="UTF-8"?>
669
+ <PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
670
+ <hasActivationRequired>false</hasActivationRequired>
671
+ <label><Bundle> Guest Concierge</label>
672
+ <objectPermissions>
673
+ <allowRead>true</allowRead>
674
+ <object>EmbeddedServiceConfig</object>
675
+ </objectPermissions>
676
+ <objectPermissions>
677
+ <allowRead>true</allowRead>
678
+ <object>MessagingChannel</object>
679
+ </objectPermissions>
680
+ <objectPermissions>
681
+ <allowRead>true</allowRead>
682
+ <object>NetworkSelfServiceExtension</object>
683
+ </objectPermissions>
684
+ <objectPermissions>
685
+ <allowRead>true</allowRead>
686
+ <object>BrandingSet</object>
687
+ </objectPermissions>
688
+ </PermissionSet>
689
+ ```
690
+
691
+ Deploy and assign:
692
+
693
+ ```bash
694
+ sf project deploy start -o $ORG --metadata "PermissionSet:<Bundle>_Guest_Concierge"
695
+
696
+ GUEST_USER_ID=$(sf data query -o $ORG -q "SELECT GuestUserId FROM Site WHERE Name='<LWR Site Name>'" --json | jq -r '.result.records[0].GuestUserId')
697
+ PSET_ID=$(sf data query -o $ORG -q "SELECT Id FROM PermissionSet WHERE Name='<Bundle>_Guest_Concierge'" --json | jq -r '.result.records[0].Id')
698
+
699
+ sf data create record -o $ORG --sobject PermissionSetAssignment \
700
+ --values "AssigneeId=$GUEST_USER_ID PermissionSetId=$PSET_ID"
701
+ ```
702
+
703
+ **F.3.e — Republish.** The LWR runtime caches the ESC/NSSE binding at publish time. Re-run Step I's publish + `Network.Status=Live` PATCH after F.3.a-d.
704
+
705
+ **F.3.f — Verify.** Use the LWR runtime proxy (`/webruntime/api/...`), **not** `/services/data/...` — the latter returns `Error querying NetworkSelfServiceExtension FK fields` under guest identity even when everything is wired.
706
+
707
+ ```bash
708
+ curl -sS "$PUBLISHED_PORTAL_URL/webruntime/api/services/data/v67.0/connect/self-service/concierge/config?language=en-US&asGuest=false&htmlEncode=false" \
709
+ | jq '.chatConfig | {deploymentName, siteUrl, scrtUrl}'
710
+ ```
711
+
712
+ 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).
713
+
714
+ ### F.4 — Wire the Agentforce Orchestrator *(one operator pause, then fully headless)*
715
+
716
+ 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). Detect the toggle state with a SOQL write-probe, prompt one manual click if off, then continue fully headless.
717
+
718
+ **F.4.0 — Detection preflight.**
719
+
720
+ 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, which causes a false "not supported" parse. Re-running the identical query without waiting resolves the issue — no org state changes between attempts.
721
+
722
+ ```bash
723
+ TOGGLE_STATE="UNKNOWN"
724
+ for _attempt in 1 2; do
725
+ _raw=$(sf data query -o "$ORG" -q "SELECT Id FROM AgenticCtxtDecorDefinition LIMIT 1" --json 2>&1)
726
+ if echo "$_raw" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if d.get('status')==0 else 1)" 2>/dev/null; then
727
+ TOGGLE_STATE="ON"; break
728
+ fi
729
+ if echo "$_raw" | grep -qi "not supported"; then
730
+ TOGGLE_STATE="OFF"
731
+ # do NOT break on first failure — retry once to rule out transient parse failure
732
+ fi
733
+ done
734
+ ```
735
+
736
+ - ✅ `TOGGLE_STATE=ON` → proceed with §F.4.2.
737
+ - ❌ `TOGGLE_STATE=OFF` (both attempts) → toggle is genuinely OFF, **pause and prompt the operator**.
738
+
739
+ **F.4.1 — ⏸ OPERATOR PAUSE: enable the Orchestrator OrgPerm.**
740
+
741
+ If the SOQL probe returned "not supported", send this message verbatim to the operator, then wait for their reply:
742
+
743
+ > **One manual step needed.** Please open this URL and enable Agentforce Orchestrator:
744
+ >
745
+ > `<INSTANCE_LIGHTNING_URL>/lightning/setup/AgentforceOrchestrator/home`
746
+ >
747
+ > 1. Click **Enable Agentforce Orchestrator** (top of the page).
748
+ > 2. Wait for the confirmation banner.
749
+ > 3. Reply `done` here and I'll continue.
750
+
751
+ When the operator replies `done`, re-run the F.4.0 SOQL probe. If it still returns "not supported", either the toggle wasn't flipped or there's an org-shape gap. Ask them to double-check, then re-probe. If the org genuinely does not have this surface (older-API pod), fall through to the F.4 skip: continue the deploy without the Orchestrator, and log the gap in the operator handoff.
752
+
753
+ **F.4.2 — Bot prerequisite check.**
754
+
755
+ ```bash
756
+ sf data query -o "$ORG" -q "SELECT DeveloperName, AgentTemplate FROM BotDefinition WHERE DeveloperName='<BotDevName>'"
757
+ ```
758
+
759
+ If `AgentTemplate` is `null`, the Bot was created outside the wizard and MUST be deleted and recreated via **Setup → Agentforce Agents → New Agent → From Template → Agentforce Service Agent**. There is no API path to patch `AgentTemplate` on an existing Bot.
760
+
761
+ Grab the Bot Id for the POST payload:
762
+
763
+ ```bash
764
+ BOT_ID=$(sf data query -o "$ORG" -q "SELECT Id FROM BotDefinition WHERE DeveloperName='<BotDevName>'" --json \
765
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
766
+ ```
767
+
768
+ **F.4.3 — Create the Orchestrator + Retriever + Parameters via one Connect POST.**
769
+
770
+ ```bash
771
+ cat > /tmp/orch.json <<EOF
772
+ {
773
+ "name": "Help Portal Concierge Orchestrator",
774
+ "developerName": "Help_Portal_Concierge_Orch",
775
+ "agentId": "$BOT_ID"
776
+ }
777
+ EOF
778
+
779
+ sf api request rest -o "$ORG" --method POST \
780
+ "/services/data/v67.0/connect/self-service/setup/agentOrchestrator" \
781
+ --body @/tmp/orch.json
782
+ # → {"orchestratorId":"1iExx0000000..."}
783
+ ```
784
+
785
+ **Payload field reference:**
786
+
787
+ | Field | Required | Notes |
788
+ |---|---|---|
789
+ | `agentId` | ✅ Yes | The `0Xx`-prefix `BotDefinition.Id`. |
790
+ | `name` | Optional | Human label. Defaults to the agent's MasterLabel. |
791
+ | `developerName` | Optional | API name. Defaults to `name`. |
792
+ | `description` | ❌ **Rejected** on v67 |
793
+ | `agenticContextDecoratorType` | ❌ **Rejected** on v67 (type is set implicitly — Concierge is the default). |
794
+ | `type` / `digitalWorkerType` | ❌ **Rejected** on v67 |
795
+ | `greetingPrompt` | Optional | DeveloperName of a `GenAiPromptTemplate` (Flex type). |
796
+ | `enableRecommendations` | Optional | Boolean. |
797
+
798
+ Response: `{"orchestratorId":"1iE...UAM"}`. Capture this for verification.
799
+
800
+ **F.4.4 — NO NSSE PATCH on v67.** On v67, `NetworkSelfServiceExtension` has no `AgenticCtxtDecorDefinitionId` field. Binding is implicit: an org can only have one Orchestrator with `AgenticContextDecoratorType=Concierge`, and every LWR Concierge site picks it up automatically. Skip this step on v67. On v68+ where the NSSE field exists, PATCH it:
801
+
802
+ ```bash
803
+ # ONLY run this block on v68+ orgs
804
+ NSSE_ID=$(sf data query -o "$ORG" -q "SELECT Id FROM NetworkSelfServiceExtension WHERE NetworkId='$NETWORK_ID' LIMIT 1" --json \
805
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
806
+ sf api request rest -o "$ORG" --method PATCH \
807
+ "/services/data/v68.0/tooling/sobjects/NetworkSelfServiceExtension/$NSSE_ID" \
808
+ --body "{\"AgenticCtxtDecorDefinitionId\":\"$ORCHESTRATOR_ID\"}"
809
+ ```
810
+
811
+ **F.4.5 — Verify.**
812
+
813
+ ```bash
814
+ sf data query -o "$ORG" -q "SELECT Id, DeveloperName, MasterLabel, Status, AgenticContextDecoratorType FROM AgenticCtxtDecorDefinition"
815
+
816
+ sf api request rest -o "$ORG" --method GET \
817
+ "/services/data/v67.0/connect/self-service/setup/agentOrchestrator"
818
+ # orchestrators[].agentIds should include your $BOT_ID
819
+ ```
820
+
821
+ **F.4.6 — Republish the site** *(only required when NSSE was patched — skip on v67)*.
822
+
823
+ ## Step G — Deploy the modified bundle
824
+
825
+ ```bash
826
+ sf project deploy start --metadata "$BUNDLE_TYPE:$BUNDLE_NAME_FOR_DEPLOY" --target-org $ORG --json \
827
+ | jq '{status,success,errors:[.result.details.componentFailures[]?|{fullName,problem}]}'
828
+ ```
829
+
830
+ For DigitalExperienceBundle, `$BUNDLE_NAME_FOR_DEPLOY` is `site/$BUNDLE_NAME`. For ExperienceBundle, it's just `$BUNDLE_NAME`.
831
+
832
+ Watch for UUID collisions — every `id` field in the bundle must be unique. If deploy fails with a UUID conflict, re-retrieve the current bundle, re-apply changes, redeploy.
833
+
834
+ ## Step H — Personalization *(best-effort, non-blocking)*
835
+
836
+ Concierge chiclets (`conciergeChicletGroupContainer`) surface personalized recommended actions and recommended knowledge articles. This requires a Data Graph linked to the Agentforce Orchestrator for the site, plus AES (Agentic Enterprise Search) as the org's search engine.
837
+
838
+ Attempt both linkages headlessly:
839
+
840
+ - **Data Graph linkage:** `sf api request rest` against the Orchestrator configuration endpoint.
841
+ - **AES:** headless via Search Query Manager configuration.
842
+
843
+ If either fails, log the gap and continue — chat still works without personalized chiclets. Note "Configure Data Graph + AES in Setup → Agentforce Orchestrator" in the operator handoff.
844
+
845
+ ## Step I — Publish the site
846
+
847
+ **Resolve `NETWORK_ID` by the LWR site**, not by `UrlPathPrefix`. `Network.UrlPathPrefix` on new sites appends `vforcesite` even though the LWR URL uses the un-suffixed prefix on `.my.site.com`. Filter by `Name` instead:
848
+
849
+ ```bash
850
+ NETWORK_ID=$(sf data query --target-org $ORG --json --query \
851
+ "SELECT Id FROM Network WHERE Name = '<Site Name>' LIMIT 1" \
852
+ | jq -r '.result.records[0].Id')
853
+
854
+ sf api request rest "/services/data/v67.0/connect/communities/$NETWORK_ID/publish" \
855
+ --method POST --body '{}' --target-org $ORG
856
+ ```
857
+
858
+ Publish is async. Poll `BackgroundOperation` until `Status = Complete`.
859
+
860
+ **Post-publish activation.** On many org shapes, `Status` remains `UnderConstruction` even after publish completes. Go fully live headlessly:
861
+
862
+ ```bash
863
+ sf api request rest "/services/data/v67.0/sobjects/Network/$NETWORK_ID" \
864
+ --method PATCH --body '{"Status":"Live"}' --target-org $ORG
865
+ ```
866
+
867
+ HTTP 204 = success. Re-query to confirm `Status = Live`.
868
+
869
+ ## Step J — Verify guest access (post-publish smoke check)
870
+
871
+ After Step I finishes publishing, verify the portal is publicly reachable:
872
+
873
+ ```bash
874
+ curl -sSI "$PUBLISHED_PORTAL_URL/" | head -1
875
+ ```
876
+
877
+ Expected: `HTTP/2 200`. If instead you see `HTTP/2 302 Location: .../login?ec=302&startURL=...`, then:
878
+
879
+ 1. Confirm `sfdc_cms__site/$BUNDLE_NAME/content.json` on disk has `"authenticationType" : "AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED"`.
880
+ 2. Re-deploy the bundle (Step G) and re-publish (Step I).
881
+ 3. Re-probe.
882
+
883
+ For **Auth-only sites** (Step A #7 option 3): `authenticationType = AUTHENTICATED` is correct — a 302 to `/login` is the intended behavior.
884
+
885
+ **Also probe the LWR runtime's Concierge config endpoint** to confirm Step F.3's binding is complete:
886
+
887
+ ```bash
888
+ curl -sS "$PUBLISHED_PORTAL_URL/webruntime/api/services/data/v67.0/connect/self-service/concierge/config?language=en-US&asGuest=false&htmlEncode=false" \
889
+ | jq '.chatConfig | {deploymentName, siteUrl, scrtUrl}'
890
+ ```
891
+
892
+ If any of the three is null, revisit Step F.3.
893
+
894
+ ## Step L — Trust surface: CORS + Trusted URLs + Trusted Domains + Experience Builder security
895
+
896
+ All four sub-steps are headless — no Setup UI clicks required.
897
+
898
+ **Extract the four URL values first**, from the ESD's Install Code Snippet (Setup → Embedded Service Deployments → *your ESD* → Code Snippet):
899
+
900
+ | Value | How to extract | Example |
901
+ |---|---|---|
902
+ | `Org_ID` | 1st positional argument to `embeddedservice_bootstrap.init(...)` | `00Dbm00000rSNZR` |
903
+ | `ESD_Developer_Name` | 2nd positional argument | `Help_Portal_Concierge` |
904
+ | `siteURL` | 3rd positional argument | `https://<myDomainStem>.my.site.com/ESWHelpPortalConcierge…` |
905
+ | `scrt2URL` | Value inside the curly braces (`scrt2URL: '...'`) | `https://<myDomainStem>.my.salesforce-scrt.com` |
906
+
907
+ Compute a **base siteURL** by stripping the path from `siteURL`, and a **published portal URL** — the LWR site's own base.
908
+
909
+ ### L.0 — Network + ESC guest-access flags *(THREE flags, ALL must be True)*
910
+
911
+ > **🚨 CRITICAL — the most common cause of a blank `/conversation/guest` page is this section.** Three independent flags gate guest chat rendering; ALL THREE must be True:
912
+ >
913
+ > 1. `Network.OptionsGuestChatterEnabled = True` — master gate for `/self-service/*` endpoints.
914
+ > 2. `Network.OptionsGuestMemberVisibility = True` — gates the conversation route rendering.
915
+ > 3. `EmbeddedServiceConfig.AreGuestUsersAllowed = True` on **BOTH** the auth ESC AND the guest ESC bound to NSSE.
916
+
917
+ Diagnostic + fix (headless, Data API):
918
+
919
+ ```bash
920
+ # 1. Find the Network id
921
+ sf data query -o $ORG -q "SELECT Id, Name, Status FROM Network WHERE Name LIKE '%<SitePrefix>%'" --json
922
+
923
+ # 2. Check both guest flags
924
+ sf data query -o $ORG -q "SELECT Id, OptionsGuestChatterEnabled, OptionsGuestMemberVisibility FROM Network WHERE Id='$NETWORK_ID'" --json
925
+
926
+ # 3. PATCH both to True
927
+ sf data update record -o $ORG --sobject Network --record-id $NETWORK_ID \
928
+ --values "OptionsGuestChatterEnabled=true OptionsGuestMemberVisibility=true"
929
+
930
+ # 4. Reprobe — should return 200
931
+ curl -sS -w "HTTP=%{http_code}\n" \
932
+ "$PUBLISHED_PORTAL_URL/webruntime/api/services/data/v67.0/connect/self-service/concierge/config?asGuest=true"
933
+
934
+ # 5. Verify every ESC bound via NSSE has AreGuestUsersAllowed=true
935
+ # Filter by DeveloperName so this runs before §M resolves AUTH_ESC_ID by Id.
936
+ sf api request rest -o $ORG "/services/data/v67.0/tooling/query?q=SELECT+Id,DeveloperName,AreGuestUsersAllowed+FROM+EmbeddedServiceConfig+WHERE+DeveloperName+IN+('<AuthESDDevName>','<GuestESDDevName>')"
937
+ # If either row is false: retrieve via mdapi, sed the tag, redeploy, republish site.
938
+ ```
939
+
940
+ ### L.1 — Setup CORS
941
+
942
+ ```bash
943
+ for ORIGIN in "$SCRT2_URL" "$BASE_SITE_URL" "$PUBLISHED_PORTAL_URL"; do
944
+ sf data create record --sobject CorsWhitelistEntry \
945
+ --values "UrlPattern=$ORIGIN" --target-org $ORG || true
946
+ done
947
+ ```
948
+
949
+ Skip an origin if `CorsWhitelistEntry` already lists it. Expect at least one of the three inserts to be a no-op on trial pods.
950
+
951
+ ### L.2 — Trusted URLs (`CspTrustedSite`)
952
+
953
+ Trusted URLs power the org-wide CSP allowlist. Deploy 3 records with the 6 supported directives.
954
+
955
+ **Query first to check what's already covered:**
956
+
957
+ ```bash
958
+ sf data query -o $ORG -q "SELECT DeveloperName, EndpointUrl FROM CspTrustedSite" -r csv
959
+ ```
960
+
961
+ Then deploy the missing patterns via metadata. Example `force-app/main/default/cspTrustedSites/HelpPortal_SCRT2.cspTrustedSite-meta.xml`:
962
+
963
+ ```xml
964
+ <CspTrustedSite xmlns="http://soap.sforce.com/2006/04/metadata">
965
+ <context>All</context>
966
+ <description>Help Portal SCRT2</description>
967
+ <endpointUrl><!-- bare host only, e.g. trailsignup-c29680009d2f9b.my.salesforce-scrt.com — NO https:// prefix --></endpointUrl>
968
+ <isActive>true</isActive>
969
+ <isApplicableToConnectSrc>true</isApplicableToConnectSrc>
970
+ <isApplicableToFontSrc>true</isApplicableToFontSrc>
971
+ <isApplicableToFrameSrc>true</isApplicableToFrameSrc>
972
+ <isApplicableToImgSrc>true</isApplicableToImgSrc>
973
+ <isApplicableToMediaSrc>true</isApplicableToMediaSrc>
974
+ <isApplicableToStyleSrc>true</isApplicableToStyleSrc>
975
+ </CspTrustedSite>
976
+ ```
977
+
978
+ Deploy:
979
+
980
+ ```bash
981
+ sf project deploy start -o $ORG -m CspTrustedSite
982
+ ```
983
+
984
+ **Gotcha:** `isApplicableToScriptSrc` and related booleans (`isApplicableToChildSrc`, `isApplicableToWorkerSrc`, `isApplicableToObjectSrc`, `isApplicableToManifestSrc`, `isApplicableToFrameAncestors`) DO NOT EXIST in the v67 Metadata API schema for `CspTrustedSite`. Only the 6 booleans above are accepted.
985
+
986
+ > **`endpointUrl` must be the bare hostname only** — no `https://` prefix, no path. The API rejects full URLs. Strip the scheme from `$SCRT2_URL` before populating: `SCRT2_HOST=${SCRT2_URL#https://}`. Use `$SCRT2_HOST` in the `<endpointUrl>` tag, not `$SCRT2_URL`.
987
+
988
+ Repeat for base siteURL and published portal URL, changing `DeveloperName` per record.
989
+
990
+ ### L.3 — Trusted Domains for Inline Frames (per-Site)
991
+
992
+ Each `Site` has a private list of domains it will let host it inside an iframe. Two variants:
993
+
994
+ - **`IframeWhiteListUrl` (org-level, no `SiteId`)** — Data API POST with `{Url, Context}`. Context enum: `LightningOut`, `Surveys`, `UIEmbedding`, `VisualforcePages`, `DCH_ADDIN_APP`.
995
+ - **`SiteIframeWhiteListUrl` (per-Site, has `SiteId + Url`)** — only accepts inserts against the ChatterNetwork wrapper Site. The Picasso `SiteType='ChatterNetworkPicasso'` id is rejected with `INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY`.
996
+
997
+ ```bash
998
+ # Path A — org-level trust
999
+ for URL in "$SCRT2_URL" "$BASE_SITE_URL" "$ORG_URL"; do
1000
+ sf api request rest -o $ORG --method POST -H "Content-Type: application/json" \
1001
+ -b "{\"Url\":\"$URL\",\"Context\":\"LightningOut\"}" \
1002
+ "/services/data/v67.0/sobjects/IframeWhiteListUrl"
1003
+ done
1004
+
1005
+ # Path B — per-Site trust (ChatterNetwork wrapper Site only)
1006
+ sf data query -o $ORG -q "SELECT Id, Name, SiteType FROM Site WHERE Name='$SITE_NAME' AND SiteType='ChatterNetwork'"
1007
+ # → capture $WRAPPER_SITE_ID
1008
+
1009
+ for URL in "$BASE_SITE_HOST" "*.${BASE_SITE_HOST}"; do
1010
+ sf api request rest -o $ORG --method POST -H "Content-Type: application/json" \
1011
+ -b "{\"SiteId\":\"$WRAPPER_SITE_ID\",\"Url\":\"$URL\"}" \
1012
+ "/services/data/v67.0/sobjects/SiteIframeWhiteListUrl"
1013
+ done
1014
+ ```
1015
+
1016
+ **Gotchas:**
1017
+ - URL field accepts **bare hosts and wildcards only**. Full URLs with paths return `Enter a valid URL or URI`.
1018
+ - `SiteId` must be the ChatterNetwork wrapper — the Picasso Site is rejected.
1019
+
1020
+ ### L.4 — Experience Builder Security & Privacy
1021
+
1022
+ Three knobs on the site's Security & Privacy panel — all headless-writable.
1023
+
1024
+ **1. Clickjack Protection Level — headless via `CustomSite` mdapi (targeting the ChatterNetwork wrapper site name, NOT the Picasso `*1` name).**
1025
+
1026
+ ```bash
1027
+ WRAPPER_SITE_NAME=<strip the trailing "1" from BUNDLE_NAME>
1028
+ mkdir -p /tmp/cj-deploy/sites
1029
+
1030
+ rm -rf /tmp/cs-retrieve
1031
+ sf project retrieve start -o "$ORG" -m "CustomSite:$WRAPPER_SITE_NAME" --target-metadata-dir /tmp/cs-retrieve
1032
+ unzip -qo /tmp/cs-retrieve/unpackaged.zip -d /tmp/cs-retrieve/e
1033
+
1034
+ sed "s|<clickjackProtectionLevel>[^<]*</clickjackProtectionLevel>|<clickjackProtectionLevel>AllowAllFraming</clickjackProtectionLevel>|" \
1035
+ /tmp/cs-retrieve/e/unpackaged/sites/${WRAPPER_SITE_NAME}.site > /tmp/cj-deploy/sites/${WRAPPER_SITE_NAME}.site
1036
+
1037
+ cat > /tmp/cj-deploy/package.xml <<EOF
1038
+ <?xml version="1.0" encoding="UTF-8"?>
1039
+ <Package xmlns="http://soap.sforce.com/2006/04/metadata">
1040
+ <types><members>${WRAPPER_SITE_NAME}</members><name>CustomSite</name></types>
1041
+ <version>67.0</version>
1042
+ </Package>
1043
+ EOF
1044
+
1045
+ sf project deploy start -o "$ORG" --metadata-dir /tmp/cj-deploy
1046
+ ```
1047
+
1048
+ Picklist values (v67): `AllowAllFraming`, `External`, `SameOriginOnly` (default), `NoFraming`. `AllowAllFraming` is required for Concierge chat (which mounts an SCRT-hosted iframe).
1049
+
1050
+ **2 + 3. CSP Level + Lightning Web Security — headless via DEB bundle `mainAppPage/content.json`.**
1051
+
1052
+ ```bash
1053
+ BUNDLE_FILE="force-app/main/default/digitalExperiences/site/${BUNDLE_NAME}/sfdc_cms__appPage/mainAppPage/content.json"
1054
+ python3 -c "
1055
+ import json
1056
+ d=json.load(open('$BUNDLE_FILE'))
1057
+ d['contentBody']['isLockerServiceEnabled']=False
1058
+ d['contentBody']['isRelaxedCSPLevel']=True
1059
+ json.dump(d, open('$BUNDLE_FILE','w'), indent=2)
1060
+ "
1061
+ sf project deploy start -o "$ORG" -m "DigitalExperienceBundle:site/${BUNDLE_NAME}" --ignore-conflicts
1062
+ sf api request rest -o "$ORG" --method POST \
1063
+ "/services/data/v67.0/connect/communities/${NETWORK_ID}/publish" \
1064
+ -H "Content-Type: application/json" --body "{}"
1065
+ ```
1066
+
1067
+ Verification:
1068
+
1069
+ ```bash
1070
+ # ⚠️ CRITICAL: Use --target-metadata-dir to a TEMP dir — never retrieve back into the live
1071
+ # project directory here. A bare `sf project retrieve start` without --target-metadata-dir
1072
+ # overwrites force-app/main/default/ with whatever the org currently has, clobbering any
1073
+ # locally-written files (conversation view, route, etc.) that haven't been round-tripped yet.
1074
+ rm -rf /tmp/l4-verify
1075
+ sf project retrieve start -o "$ORG" -m "DigitalExperienceBundle:site/${BUNDLE_NAME}" \
1076
+ --target-metadata-dir /tmp/l4-verify
1077
+ unzip -qo /tmp/l4-verify/unpackaged.zip -d /tmp/l4-verify/e 2>/dev/null || true
1078
+ VERIFY_FILE="/tmp/l4-verify/e/unpackaged/digitalExperiences/site/${BUNDLE_NAME}/sfdc_cms__appPage/mainAppPage/content.json"
1079
+ python3 -c "
1080
+ import json
1081
+ cb=json.load(open('$VERIFY_FILE'))['contentBody']
1082
+ print('isLockerServiceEnabled:', cb.get('isLockerServiceEnabled'))
1083
+ print('isRelaxedCSPLevel:', cb.get('isRelaxedCSPLevel'))
1084
+ "
1085
+ # Expected: False, True
1086
+ ```
1087
+
1088
+ **Security note:** these settings weaken the site's script-execution / frame-embedding posture. They are required for Concierge chat (which mounts a cross-origin SCRT iframe with inline scripts). Do not enable them on Experience Cloud sites that don't need Concierge.
1089
+
1090
+ ### L.5 — Verify the trust surface
1091
+
1092
+ ```bash
1093
+ # CORS
1094
+ sf data query -q "SELECT UrlPattern FROM CorsWhitelistEntry WHERE UrlPattern IN ('$SCRT2_URL','$BASE_SITE_URL','$PUBLISHED_PORTAL_URL')" -t
1095
+
1096
+ # Trusted URLs
1097
+ sf data query -q "SELECT DeveloperName, EndpointUrl, IsApplicableToConnectSrc, IsApplicableToFrameSrc FROM CspTrustedSite WHERE EndpointUrl IN ('$SCRT2_URL','$BASE_SITE_URL','$PUBLISHED_PORTAL_URL')" -t
1098
+ ```
1099
+
1100
+ Both must return three rows each.
1101
+
1102
+ ## Step M — AI Experiences ESD assignment
1103
+
1104
+ > **Preflight:** `sf sobject describe --sobject AiExperienceContextDefinition -o $ORG 2>&1 | head -3`
1105
+ >
1106
+ > If this returns `The requested resource does not exist`, skip §M entirely (older-API orgs).
1107
+
1108
+ Two Network toggles are headless-writable:
1109
+
1110
+ | Toggle | Target value | Headless path |
1111
+ |---|---|---|
1112
+ | Share search queries with Agentforce Service Agent | OFF | **No-op — OFF is the org default on v67.** |
1113
+ | Share Data Category Selection with Agentforce Service Agent (Beta) | ON | ✅ `Network.OptionsDataCategoryContextPassingEnabled = true` |
1114
+ | Self Service Personalization | OFF | ✅ `Network.OptionsSlfSrvcPersonalizationEnabled = false` |
1115
+
1116
+ **Headless PATCH:**
1117
+
1118
+ ```bash
1119
+ sf api request rest -o "$ORG" --method PATCH \
1120
+ "/services/data/v67.0/sobjects/Network/$NETWORK_ID" \
1121
+ --body '{"OptionsDataCategoryContextPassingEnabled":true,"OptionsSlfSrvcPersonalizationEnabled":false}'
1122
+ # Expected: HTTP 204 No Content
1123
+ ```
1124
+
1125
+ **Auth-ESD NSSE bind** *(only for portals serving authenticated users)*:
1126
+
1127
+ ```bash
1128
+ AUTH_ESC_ID=$(sf data query -o "$ORG" -q "SELECT Id FROM EmbeddedServiceConfig WHERE DeveloperName='<AuthESDDevName>'" --json \
1129
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
1130
+ sf api request rest -o "$ORG" --method PATCH \
1131
+ "/services/data/v67.0/tooling/sobjects/NetworkSelfServiceExtension/$NSSE_ID" \
1132
+ --body "{\"EmbeddedServiceCnfgId\":\"$AUTH_ESC_ID\"}"
1133
+ ```
1134
+
1135
+ For UnAuth-only Help Portals, guest binding alone is sufficient — skip this PATCH.
1136
+
1137
+ ## Step N — Bot-routing wiring *(runtime layer — required for "Agents are available")*
1138
+
1139
+ `/concierge/config` returning 200 unblocks the guest **bootstrap** (portal loads, prompt bar renders). It does NOT wire the Bot to actually pick up messaging sessions. Without Step N, every guest submission lands in `MessagingSession.Status='Waiting'` and the chat surface shows **"Agents are not available. Try again later."**
1140
+
1141
+ **Three writes** (all Data API, all reversible). Run them AFTER `MessagingChannel.IsActive=true` (Step E) and `BotVersion.Status=Active`:
1142
+
1143
+ ```bash
1144
+ # Prerequisites
1145
+ BOT_ID="0Xx..." # BotDefinition Id from Step E
1146
+ CHAN_ID="0Mj..." # MessagingChannel Id
1147
+ FALLBACK_Q=$(sf data query -o $ORG -q "SELECT FallbackQueueId FROM MessagingChannel WHERE Id='$CHAN_ID'" --json | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['FallbackQueueId'])")
1148
+ BOT_USER=$(sf data query -o $ORG -q "SELECT BotUserId FROM BotDefinition WHERE Id='$BOT_ID'" --json | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['BotUserId'])")
1149
+ PRESENCE_CFG=$(sf data query -o $ORG -q "SELECT Id FROM PresenceUserConfig WHERE DeveloperName='default_presence_config'" --json | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
1150
+ ROUTING_CFG=$(sf data query -o $ORG -q "SELECT Id FROM QueueRoutingConfig WHERE DeveloperName='MessagingSession'" --json | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
1151
+
1152
+ # N.1 — presence enable the BotUser
1153
+ sf data create record -o $ORG --sobject PresenceUserConfigUser \
1154
+ --values "UserId=$BOT_USER PresenceUserConfigId=$PRESENCE_CFG"
1155
+
1156
+ # N.2 — make BotUser a member of the fallback queue
1157
+ sf data create record -o $ORG --sobject GroupMember \
1158
+ --values "GroupId=$FALLBACK_Q UserOrGroupId=$BOT_USER"
1159
+
1160
+ # N.3 — bind the fallback queue to the MessagingSession routing config
1161
+ sf data update record -o $ORG --sobject Group --record-id $FALLBACK_Q \
1162
+ --values "QueueRoutingConfigId=$ROUTING_CFG"
1163
+ ```
1164
+
1165
+ **Verification:**
1166
+
1167
+ ```bash
1168
+ sf data query -o $ORG -q "SELECT COUNT(Id) n FROM PresenceUserConfigUser WHERE UserId='$BOT_USER'"
1169
+ sf data query -o $ORG -q "SELECT COUNT(Id) n FROM GroupMember WHERE GroupId='$FALLBACK_Q' AND UserOrGroupId='$BOT_USER'"
1170
+ sf data query -o $ORG -q "SELECT QueueRoutingConfigId FROM Group WHERE Id='$FALLBACK_Q'"
1171
+ ```
1172
+
1173
+ **Live-runtime step**: Salesforce requires the BotUser to be `Online` in Omni-Presence for routing to consider it available. For Agentforce Service Agent (`Type=ExternalCopilot`), this is typically auto-provisioned when the BotVersion is activated *if* N.1–N.3 exist first. If sessions still stay in `Waiting` after N.1–N.3, verify:
1174
+
1175
+ ```bash
1176
+ sf data query -o $ORG -q "SELECT Id, IsCurrentState, ServicePresenceStatusId FROM UserServicePresence WHERE UserId='$BOT_USER' ORDER BY CreatedDate DESC LIMIT 1"
1177
+ ```
1178
+
1179
+ If zero rows or `IsCurrentState=false`, re-activate the BotVersion:
1180
+
1181
+ ```bash
1182
+ sf agent deactivate -o $ORG --api-name $BOT_DEV_NAME
1183
+ sf agent activate -o $ORG --api-name $BOT_DEV_NAME
1184
+ ```
1185
+
1186
+ Wait a few minutes for the Automated Process pickup path to spin up, then re-test in a browser.
1187
+
1188
+ ## Step O — Search Manager Query Configuration
1189
+
1190
+ Configures a `SearchCustomization` that scopes the portal's object search results (Product / Knowledge / Case).
1191
+
1192
+ **Fully headless via Metadata API using `channel=LWRExperienceSiteSearch`** (NOT `CustomChannel` / `CustomExperience`).
1193
+
1194
+ ### O.1 — Preflight
1195
+
1196
+ ```bash
1197
+ sf api request rest -o "$ORG" --method GET \
1198
+ "/services/data/v67.0/tooling/sobjects/SearchCustomization/describe" \
1199
+ | python3 -c "import sys,json; d=json.load(sys.stdin); print('EXISTS' if d.get('name') else 'NOT_FOUND')"
1200
+ ```
1201
+
1202
+ If `NOT_FOUND` (very old orgs), skip §O.
1203
+
1204
+ ### O.2 — Deploy the config
1205
+
1206
+ `force-app/main/default/searchCustomizations/Help_Portal_LWR_Search.searchCustomization-meta.xml`:
1207
+
1208
+ ```xml
1209
+ <?xml version="1.0" encoding="UTF-8"?>
1210
+ <SearchCustomization xmlns="http://soap.sforce.com/2006/04/metadata">
1211
+ <channel>LWRExperienceSiteSearch</channel>
1212
+ <masterLabel>Help Portal LWR Search</masterLabel>
1213
+ <selectedObject>Product2</selectedObject>
1214
+ <selectedObject>Knowledge__kav</selectedObject>
1215
+ <selectedObject>Case</selectedObject>
1216
+ </SearchCustomization>
1217
+ ```
1218
+
1219
+ ```bash
1220
+ sf project deploy start -o "$ORG" --source-dir force-app
1221
+ ```
1222
+
1223
+ **Enum values on `SearchCustomization.Channel` (v67):** `GlobalSearch`, `CustomChannel`, `KnowledgeComponentSearch`, `LWRExperienceSiteSearch`. `LWRExperienceSiteSearch` is the correct choice for Help Portal.
1224
+
1225
+ ### O.3 — Verify
1226
+
1227
+ ```bash
1228
+ sf data query -o "$ORG" --use-tooling-api \
1229
+ -q "SELECT Id, DeveloperName, MasterLabel, Channel FROM SearchCustomization WHERE DeveloperName='Help_Portal_LWR_Search'"
1230
+ ```
1231
+
1232
+ Round-trip retrieval:
1233
+
1234
+ ```bash
1235
+ sf project retrieve start -o "$ORG" -m SearchCustomization:Help_Portal_LWR_Search
1236
+ cat force-app/main/default/searchCustomizations/Help_Portal_LWR_Search.searchCustomization-meta.xml
1237
+ ```
1238
+
1239
+ Expected: 3 `<selectedObject>` lines in alphabetical order.
1240
+
1241
+ ### O.4 — Bind SearchCustomization to NSSE
1242
+
1243
+ > **Skip this step on orgs with only one LWR site.** With a single `channel=LWRExperienceSiteSearch` SearchCustomization, the platform applies it by implicit channel-match — the explicit PATCH is unnecessary and fails with "Select a custom experience query configuration. Other configurations aren't supported." Only run this PATCH when the org has multiple LWR sites and you want to assign different query configs per site.
1244
+
1245
+ ```bash
1246
+ SEARCH_CONFIG_ID=$(sf api request rest -o "$ORG" \
1247
+ "/services/data/v67.0/tooling/query?q=SELECT+Id+FROM+SearchCustomization+WHERE+DeveloperName='Help_Portal_LWR_Search'" \
1248
+ | python3 -c "import sys,json,re;raw=sys.stdin.read();raw=re.sub(r'^\s*Warning[^\n]*\n','',raw,flags=re.M);print(json.loads(raw)['records'][0]['Id'])")
1249
+
1250
+ sf api request rest -o "$ORG" --method PATCH \
1251
+ "/services/data/v67.0/tooling/sobjects/NetworkSelfServiceExtension/$NSSE_ID" \
1252
+ --body "{\"SearchCustomizationId\":\"$SEARCH_CONFIG_ID\"}"
1253
+ ```
1254
+
1255
+ **Effect scope:** With `SearchCustomizationId=null`, the platform still applies the org's single `channel=LWRExperienceSiteSearch` SearchCustomization by implicit channel-match. The explicit PATCH matters when the org has multiple LWR sites and you want them to point at different query configs.
1256
+
1257
+ ## Step P — Smart Search Assistance subagent *(UI-only — log and continue)*
1258
+
1259
+ Adding a **Smart Search Assistance** subagent to the Agentforce Builder enhances the agent's Knowledge search behavior. The asset lives in a Salesforce-hosted cloud registry with no headless surface at v67. **Log the step in the operator handoff and continue** — chat works without it.
1260
+
1261
+ If the operator wants to add it later:
1262
+
1263
+ 1. Setup → Agentforce Agents → open the agent → Subagents panel → **+** → **Add from Asset Library** → **Smart Search Assistance**.
1264
+ 2. Wire Advanced Settings variable bindings: Search Configuration → `SearchCustomization` variable, Network ID → `NetworkId` variable.
1265
+ 3. Verify the 7 context variables are present: `SmsVerificationKey`, `customerId`, `customerType`, `isVerified`, `VerifiedCustomerId`, `SearchCustomization`, `NetworkId`.
1266
+ 4. Save + activate the agent version.
1267
+
1268
+ ## Step S — Login & Registration + Head Markup + Publish
1269
+
1270
+ ### S.1 — Member profile assignments *(auth paths only)*
1271
+
1272
+ For UnAuth portals (Step A #7 option 3), the Guest User is auto-added via the site-level `authenticationType` flip in §F.0 — no `NetworkMemberGroup` writes needed.
1273
+
1274
+ For AuthOnly / MixedAuth (options 1/2), bind 4 Customer Community profiles via Data API:
1275
+
1276
+ ```bash
1277
+ sf data query -o "$ORG" -q "SELECT Id, Name FROM Profile WHERE Name IN ('Customer Community User','Customer Community Login User','Customer Community Plus User','Customer Community Plus Login User')" -r csv
1278
+
1279
+ for PROFILE_ID in <the 4 Ids above>; do
1280
+ sf api request rest -o "$ORG" --method POST \
1281
+ "/services/data/v67.0/sobjects/NetworkMemberGroup" \
1282
+ --body "{\"NetworkId\":\"$NETWORK_ID\",\"ParentId\":\"$PROFILE_ID\"}"
1283
+ done
1284
+ ```
1285
+
1286
+ The polymorphic field is `ParentId` (keyPrefix `00e` = Profile, `0PS` = PermissionSet). Verify:
1287
+
1288
+ ```bash
1289
+ sf data query -o "$ORG" -q "SELECT Id, NetworkId, ParentId FROM NetworkMemberGroup WHERE NetworkId='$NETWORK_ID'" -r csv
1290
+ # Expect 5 rows after INSERT (SysAdmin + 4 Customer Community variants)
1291
+ ```
1292
+
1293
+ ### S.2 — Login & Registration
1294
+
1295
+ Three of four toggles map to `NetworkAuthApiSettings` fields headlessly. The fourth ("Require reCAPTCHA for username-password login") has no matching v67 field — Setup UI fallback only.
1296
+
1297
+ | UI toggle | Field | Recommended value |
1298
+ |---|---|---|
1299
+ | Uncheck "Allow self-registration via Headless Registration API" | `IsHeadlessUserRegistrationAllowed` | `false` |
1300
+ | Uncheck "Allow password reset via Headless Forgot Password API" | `IsForgotPwdAllowed` | `false` |
1301
+ | Check "Require reCAPTCHA for username-password login" | No field found — Setup UI fallback | — |
1302
+ | Set "Score Threshold" to 0 | `RecaptchaScoreThreshold` | `0.0` |
1303
+
1304
+ No `NetworkAuthApiSettings` row exists by default. Zero-row SOQL means INSERT (not PATCH):
1305
+
1306
+ ```bash
1307
+ sf api request rest -o "$ORG" --method POST \
1308
+ "/services/data/v67.0/sobjects/NetworkAuthApiSettings" \
1309
+ --body "{
1310
+ \"NetworkId\":\"$NETWORK_ID\",
1311
+ \"IsHeadlessUserRegistrationAllowed\":false,
1312
+ \"IsForgotPwdAllowed\":false,
1313
+ \"RecaptchaScoreThreshold\":0.0
1314
+ }"
1315
+ ```
1316
+
1317
+ If a row already exists, PATCH the existing Id:
1318
+
1319
+ ```bash
1320
+ NAAS_ID=$(sf data query -o "$ORG" -q "SELECT Id FROM NetworkAuthApiSettings WHERE NetworkId='$NETWORK_ID'" --json \
1321
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['result']['records'][0]['Id'])")
1322
+ sf api request rest -o "$ORG" --method PATCH \
1323
+ "/services/data/v67.0/sobjects/NetworkAuthApiSettings/$NAAS_ID" \
1324
+ --body '{"IsHeadlessUserRegistrationAllowed":false,"IsForgotPwdAllowed":false,"RecaptchaScoreThreshold":0.0}'
1325
+ ```
1326
+
1327
+ **Server-side reCAPTCHA dependency:** the server refuses `IsHeadlessUserRegistrationAllowed=true` and `IsForgotPwdAllowed=true` unless auth OR reCAPTCHA is enabled. `IsRecaptcha*` boolean writes are silently rejected when `RecaptchaSecretKey` is empty. For UnAuth-only portals, leave these fields at `false` — cosmetic only.
1328
+
1329
+ ### S.3 — Head Markup
1330
+
1331
+ The `headMarkup` string in the DEB's `sfdc_cms__appPage/mainAppPage/content.json` is pre-populated with SLDS + DXP stylesheet imports — no additional write needed. Preflight verify:
1332
+
1333
+ ```bash
1334
+ mkdir -p /tmp/hm-verify && cat > /tmp/hm-verify/package.xml <<EOF
1335
+ <?xml version="1.0" encoding="UTF-8"?>
1336
+ <Package xmlns="http://soap.sforce.com/2006/04/metadata">
1337
+ <types><members>site/${BUNDLE_NAME}</members><name>DigitalExperienceBundle</name></types>
1338
+ <version>67.0</version>
1339
+ </Package>
1340
+ EOF
1341
+ sf project retrieve start -o "$ORG" --manifest /tmp/hm-verify/package.xml --target-metadata-dir /tmp/hm-verify-out --wait 5
1342
+ unzip -qo /tmp/hm-verify-out/unpackaged.zip -d /tmp/hm-verify-ex
1343
+ grep -l '"headMarkup"' /tmp/hm-verify-ex/**/mainAppPage/content.json && echo 'Head Markup present ✓'
1344
+ ```
1345
+
1346
+ ### S.4 — Progressive Rendering *(UI-only — skip, log if visual jank)*
1347
+
1348
+ No verified headless surface. Default is OFF on freshly-provisioned DEB sites — skip on a fresh deploy. If a downstream visual test shows layout jank, log the step for the operator (Experience Builder → Settings → Advanced → Progressive Rendering).
1349
+
1350
+ ### S.5 — Publish + Activate
1351
+
1352
+ ```bash
1353
+ # 1. Publish
1354
+ sf community publish -o "$ORG" --name "$SITE_NAME"
1355
+
1356
+ # 2. Activate
1357
+ sf data update record -o "$ORG" --sobject Network --record-id "$NETWORK_ID" --values "Status=Live"
1358
+
1359
+ # 3. Verify runtime reachability
1360
+ sf data query -o "$ORG" -q "SELECT Status FROM Network WHERE Id='$NETWORK_ID'" -r csv
1361
+ curl -sI "$PUBLISHED_PORTAL_URL"
1362
+ ```
1363
+
1364
+ **Ordering matters:** Publish before Activate. Every subsequent config change (F.3.a-d ESC binding, §L.4 bundle flags, §S.2 NAAS INSERT) requires a re-publish; Activate is one-shot.
1365
+
1366
+ ## Step K — Open the portal for the user
1367
+
1368
+ At the end of the run, print the customer-facing portal URL and instruct the operator to open it in an Incognito window (a Salesforce SysAdmin session shows a blank chat surface — that's expected, not a bug).
1369
+
1370
+ ```text
1371
+ ✅ Help Portal deployed.
1372
+
1373
+ Open this URL in a fresh Incognito / Private window to test as a guest visitor:
1374
+
1375
+ $PUBLISHED_PORTAL_URL
1376
+
1377
+ You should see the welcome greeting, prompt bar, suggestion chiclets, and chat surface on the home page. Type a question and submit — the agent should respond within a few seconds.
1378
+
1379
+ Follow-up manual steps (if any were logged during the deploy):
1380
+ - <populate from the manual-step log>
1381
+ ```
1382
+
1383
+ Also open it locally for convenience:
1384
+
1385
+ ```bash
1386
+ sf org open --target-org $ORG --path "/<url-path>/"
1387
+ ```
1388
+
1389
+ > **Do NOT append `/s` to the portal URL.** LWR Experience Cloud sites (DigitalExperienceBundle) use path-based routing — the home page is at `/<urlPathPrefix>/` (trailing slash). The `/s` suffix is an Aura/Classic Experience Cloud convention and routes to a 404 on LWR sites.
1390
+
1391
+ ## Anti-patterns table
1392
+
1393
+ | Symptom | Cause | Fix |
1394
+ |---|---|---|
1395
+ | Site creation returns `INVALID_INPUT: The URL can only contain alphanumeric characters` | `urlPathPrefix` contained dashes / underscores / spaces | Sanitize to alphanumeric-only before POST |
1396
+ | Site creation 400 with wrong template | Template name typo | Must be exactly `Build Your Own (LWR)` — not `Help Center`, not `Customer Service` |
1397
+ | `sf project retrieve start` fails with `InvalidProjectWorkspaceError` | Ran from a directory without `sfdx-project.json` | `cd` into the SFDX project root before retrieve/deploy |
1398
+ | Retrieve returns `success: true` with `file count: 0` | Retrieved with wrong metadata type | Run both `sf org list metadata -m DigitalExperienceBundle` and `... -m ExperienceBundle`; use whichever type the site is registered under |
1399
+ | Deploy fails: *"Provide a URL with only valid characters and no spaces"* on custom route | `urlPrefix` on a custom `sfdc_cms__route` contained a dash | Use camelCase or lowercase alphanumeric only |
1400
+ | Deploy fails: *"The route ID X is invalid. To create or update a custom route, suffix the route ID with __c"* | New route folder didn't end in `__c` | Rename directory and `apiName` in `_meta.json` to `<Name>__c` |
1401
+ | Prompt bar submits but next page shows "Invalid Page" | Missing `sfdc_cms__route/Conversation__c/` + `sfdc_cms__view/conversation/` in the deployed DigitalExperienceBundle | Copy both directories from a working reference bundle (§F.0.a), redeploy, republish |
1402
+ | Prompt bar submits and navigates, but the destination renders as a **blank page** when opened by a Salesforce SysAdmin | SysAdmin session has no Guest ESD binding — `conciergeChat` runtime binds to the site's Guest ESD | Open the portal in a private/Incognito window and re-test |
1403
+ | `/conversation/guest` blank in Incognito, Home renders, `/concierge/config?asGuest=true` returns 200 | (1) Guest-visibility flags: `OptionsGuestChatterEnabled` + `OptionsGuestMemberVisibility` + `EmbeddedServiceConfig.AreGuestUsersAllowed` on all bound ESCs. (2) Guest permset assignment: the site's `GuestUserId` needs the `<Bundle>_Guest_Concierge` permset assigned via `PermissionSetAssignment` | Run §L.0 diagnostic block (all 5 steps) + §F.3.d permset check + republish |
1404
+ | Concierge components edited but don't render — no deploy error | Edited using `componentName` (ExperienceBundle field) on a DigitalExperienceBundle org, which needs `definition` instead | Use the JSON shape from §F.0 — `definition:`, UUID `id`, no `renderPriority` / `renditionMap` |
1405
+ | Bundle deploy fails on UUID | Duplicate `id` across components | Regenerate any UUID that collides; re-retrieve the bundle first if unsure of current state |
1406
+ | Concierge components don't render | Wrong region — placed in a custom or hidden region | Must go in the existing `content` region, inside the first column of the layout section |
1407
+ | `conciergePromptBar` renders but chat doesn't respond | MessagingChannel not bound to agent, or channel is inactive | Verify `SessionHandlerId` = BotDefinition Id via SOQL (`SELECT SessionHandlerId, FallbackQueueId, IsActive FROM MessagingChannel WHERE Id='$CHAN_ID'`) — bound via Data API PATCH, not `sessionHandlerAsa` in XML (rejected at v67); PATCH `IsActive=true` if deploy left it inactive |
1408
+ | `/services/data/.../connect/self-service/concierge/config` returns **HTTP 400 — `Error querying NetworkSelfServiceExtension FK fields`** | Misleading error — this is what the raw Connect endpoint returns under guest identity regardless of wiring | Ignore the raw endpoint and probe `/webruntime/api/services/data/vXX.X/connect/self-service/concierge/config?asGuest=false` instead |
1409
+ | `/webruntime/api/.../concierge/config` returns `deploymentName: null` even after F.3 records exist | Site wasn't republished after F.3.a-d | Run Step I publish + `Network.Status=Live` PATCH again |
1410
+ | ESC deploy fails: *"This field requires the site type ChatterNetworkPicasso"* | `<site>` in `EmbeddedServiceConfig` metadata pointed at the classic companion site instead of the LWR site | Query `Site.Name` and pick the row matching `$BUNDLE_NAME` |
1411
+ | PermissionSet assignment fails: *"user's user license doesn't support it"* | Attempted to assign a licensed permset to a Guest User License user | Build a license-free permset with just the object-read grants — see §F.3.d template |
1412
+ | Tooling PATCH on NSSE fails with `INVALID_INPUT: Select different deployments for guest and authenticated users` | Attempted to set `EmbeddedServiceCnfgId` and `GuestEmbeddedServiceCnfgId` to the same ESC | Set only one — UnAuth sites use `GuestEmbeddedServiceCnfgId`; Auth-only sites use `EmbeddedServiceCnfgId` |
1413
+ | `Network.Status` still `UnderConstruction` after publish job returned `Complete` | Publish alone doesn't activate on this org shape | PATCH `Network/{Id}` with `{"Status":"Live"}` (HTTP 204) |
1414
+ | Portal loads but redirects visitor to `/login` even though `authMode=UnAuth` | `authenticationType` in `sfdc_cms__site/{Bundle}/content.json` is still the retrieved default `AUTHENTICATED` | Change to `AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED`, redeploy, re-publish |
1415
+ | `Network` query by `UrlPathPrefix` returns nothing | Network's UrlPathPrefix has a `vforcesite` suffix while the LWR site uses the un-suffixed value | Query by `Name` instead |
1416
+ | Widget-style embed attempted | Confused Help Portal with Web Chat | Help Portal is Concierge components on the site itself, not a widget. Read `channel-web-chat.md` for widget flow |
1417
+ | Aura site used as target | Concierge components are LWR-only | Verify `SiteType = 'ChatterNetworkPicasso'` before deploy |
1418
+ | 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 |
1419
+ | 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 |
1420
+ | §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 |
1421
+ | Prompt bar responses are ungrounded / navigate to `/error` on newer-API orgs | Agentforce Orchestrator not wired | See §F.4 — enable the OrgPerm (operator pause) then `POST /connect/self-service/setup/agentOrchestrator` |
1422
+ | 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 |
1423
+
1424
+ ---
1425
+
1426
+ ## Handoff to Checkpoint 3.5 and Checkpoint 4
1427
+
1428
+ After Help Portal provisioning, the flow returns to the spec: Checkpoint 3.5 (silent pre-flight — verify MessagingChannel is Active, ADL is grounded, and the Concierge components round-tripped in the deployed bundle) then Checkpoint 4 (go-live: activate channel, wire escalation flow, offer to test). Those gates live in `assets/help-agent-spec.md` §4.4–§4.5.