@salesforce/afv-skills 1.47.0 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/education-cloud-domain-configure/SKILL.md +234 -0
- package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
- package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
- package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
- package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
- package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
- package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
- package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
- package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +4 -3
- package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
- package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
- package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
- package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
- package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
- package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
- package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
- package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
- package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
- package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
- package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
- package/skills/service-omni-agent-users-create/SKILL.md +96 -0
- package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
- package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
- package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
- package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
- package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
- package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
- package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
- package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
- package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
- package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
- package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
- package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
- package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
- package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
- package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
- package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
- package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
- package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
- package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
- package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
- package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
- package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
- package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
- package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
- package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
- package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
- package/skills/service-omni-queue-deploy/SKILL.md +91 -0
- package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
- package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
- package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
- package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
- package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
- package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
- package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
- package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
- package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
- package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
- package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
- package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
- package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
- package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
- package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
- package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
- package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
- package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
- package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
- package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
- package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
- package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
- package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
- package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
- package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
- package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
- package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
- package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
- package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
- package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
- package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
- package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
- package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
- package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
- package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
- package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-omni-service-channel-configure
|
|
3
|
+
description: "Use to ensure exactly one ServiceChannel exists on a Salesforce org for a routable sObject (Case | Incident | MessagingSession | VoiceCall), reusing any standard or custom channel already bound to that sObject and only deploying a canonical template when none exists. Salesforce enforces one ServiceChannel per RelatedEntity, so the skill detects via the Tooling API first. Standard channels ship for Case (Cases), MessagingSession (sfdc_livemessage), VoiceCall (sfdc_phone); Incident has none. Triggers: configure ServiceChannel for Case/Incident/Messaging/Voice routing, add Incident or Voice routing to Omni. Do not use to tune per-sObject capacity (use PresenceUserConfig.Capacity), to reset a mis-configured channel to canonical shape, or for sObjects outside Case/Incident/MessagingSession/VoiceCall."
|
|
4
|
+
allowed-tools: Bash Read Write Edit Glob Grep
|
|
5
|
+
metadata:
|
|
6
|
+
version: "1.0"
|
|
7
|
+
domains: ["Service"]
|
|
8
|
+
minApiVersion: "66.0"
|
|
9
|
+
relatedSkills:
|
|
10
|
+
- "service-omni-base-settings-configure"
|
|
11
|
+
accessCheck:
|
|
12
|
+
- type: license
|
|
13
|
+
value: ServiceCloud
|
|
14
|
+
cliTools:
|
|
15
|
+
- tool: ["jq"]
|
|
16
|
+
semver: ">=1.6"
|
|
17
|
+
- tool: ["sf"]
|
|
18
|
+
semver: ">=2.139.6"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# service-omni-service-channel-configure
|
|
22
|
+
|
|
23
|
+
Ensure exactly one `ServiceChannel` is present for a routable sObject, reusing whatever is already bound (Salesforce standard or a pre-existing custom channel) and deploying the skill's canonical v66 template only when none exists. Salesforce enforces one ServiceChannel per `RelatedEntity`, so the skill detects first and never deploys blindly — deploying a second channel for an sObject that already has one fails with "This Salesforce object is already in use by another Service channel." It handles one sObject per call; callers configuring multiple sObjects invoke it once for each sObject, after `service-omni-base-settings-configure` has enabled Omni-Channel.
|
|
24
|
+
|
|
25
|
+
| sObject | Standard channel (if any) | Canonical asset |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `Case` | `Cases` | `Cases.serviceChannel-meta.xml` |
|
|
28
|
+
| `Incident` | none (Incident Management sObject) | `Incidents.serviceChannel-meta.xml` — deployed on first run |
|
|
29
|
+
| `MessagingSession` | `sfdc_livemessage` | `MessagingSessions.serviceChannel-meta.xml` — deployed only if the standard channel is missing |
|
|
30
|
+
| `VoiceCall` | `sfdc_phone` | `VoiceCalls.serviceChannel-meta.xml` — deployed only if the standard channel is missing |
|
|
31
|
+
|
|
32
|
+
## Inputs
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bash scripts/deploy-and-report.sh <org-alias> [Case|Incident|MessagingSession|VoiceCall]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `org-alias` (required).
|
|
39
|
+
- `sobject_type` (optional, default `Case`) — one of `Case | Incident | MessagingSession | VoiceCall`.
|
|
40
|
+
|
|
41
|
+
## Preconditions and safety
|
|
42
|
+
|
|
43
|
+
- Target org authenticated via `sf` CLI (My Domain URL), Service Cloud license, `sf` CLI ≥ 2.139.6.
|
|
44
|
+
- Omni-Channel base settings enabled (`service-omni-base-settings-configure`) — `ServiceChannel` is only queryable/writable when `enableOmniChannel=true`; a `INVALID_TYPE` on deploy means base settings are off.
|
|
45
|
+
- `Incident` requires the Incident Management feature enabled; `MessagingSession` requires Enhanced Messaging. The skill verifies `EntityDefinition` first and blocks with a click-path when the target sObject is absent.
|
|
46
|
+
|
|
47
|
+
## Run
|
|
48
|
+
|
|
49
|
+
`deploy-and-report.sh` performs the prereq check, discovery, and (only when needed) deploy, then emits the report:
|
|
50
|
+
|
|
51
|
+
1. **Prereq** — confirm the target sObject is queryable via `EntityDefinition`; block with a click-path otherwise.
|
|
52
|
+
2. **Discover** — query the Tooling API for a `ServiceChannel` with `RelatedEntity = <sobject_type>`. `ServiceChannel` is a Tooling entity on v66; the standard Data API returns `INVALID_TYPE`. A hit means reuse.
|
|
53
|
+
3. **Deploy** (only when nothing was found) — `sf project deploy start` of the canonical XML (`Cases` | `Incidents` | `MessagingSessions` | `VoiceCalls`); a `Changed` state is `created`, `Unchanged` is `reused`.
|
|
54
|
+
|
|
55
|
+
## Behavior
|
|
56
|
+
|
|
57
|
+
**Detect-then-reuse-or-deploy.** Because Salesforce rejects a second channel per `RelatedEntity`, deploy-first is unsafe: an operator who renamed the standard `Cases` channel would hit the "already in use" error instead of gracefully reusing. Tooling discovery is fast and returns both the reuse decision and the discovered `DeveloperName` in one call. The trade-off is that the skill cannot tell "our canonical shape matches" from "some custom channel is bound" — an operator with a non-canonical channel gets `reused` and their channel is left untouched (v1 never overwrites operator state).
|
|
58
|
+
|
|
59
|
+
**Capacity model.** The canonical assets use `TAB_BASED` for every variant. `STATUS_BASED` requires companion fields whose full schema is undocumented, so it is out of scope; per-agent capacity tuning belongs to `PresenceUserConfig.Capacity`, not this skill.
|
|
60
|
+
|
|
61
|
+
**Concurrency.** If a deploy fails with "already in use," another process created the channel between discovery and deploy; the skill soft-fails and a re-run reuses it.
|
|
62
|
+
|
|
63
|
+
## Output contract
|
|
64
|
+
|
|
65
|
+
A single JSON object per invocation with `status` ∈ `created` | `reused` | `blocked`, plus `sobject_type`, `channel_developer_name`, `channel_id`, `channel_origin`, `target_capacity_model`, `target_related_entity_type`, `before_state`, `deploy_id`, `manual_actions`, and `blocking_issue`.
|
|
66
|
+
|
|
67
|
+
- `reused` — an existing channel was found (discovery hit, or a deploy returned `Unchanged`).
|
|
68
|
+
- `created` — no channel existed; the canonical XML deployed with `Changed`.
|
|
69
|
+
- `blocked` — the sObject is not on the org, base settings are off (`INVALID_TYPE`), or a race left the channel already in use.
|
|
70
|
+
- `channel_origin` is one of `salesforce_standard` (an `sfdc_*` name or `Cases`), `canonical_asset` (a shipped asset name), `canonical_template` (freshly deployed this run), or `custom` (a pre-existing org-specific channel).
|
|
71
|
+
- `channel_id` is populated whenever a channel exists (`null` on `blocked`); `deploy_id` is populated only on the deploy branch; `blocking_issue` is non-null only when `status: blocked`.
|
|
72
|
+
|
|
73
|
+
## Limitations
|
|
74
|
+
|
|
75
|
+
- One sObject per invocation.
|
|
76
|
+
- Only `Case`, `Incident`, `MessagingSession`, `VoiceCall` are supported — extend by adding an `assets/force-app/main/default/serviceChannels/<Name>.serviceChannel-meta.xml` file and a dispatcher branch.
|
|
77
|
+
- Always reuses an existing channel; it does not reset a mis-configured channel to canonical shape.
|
|
78
|
+
|
|
79
|
+
## References
|
|
80
|
+
|
|
81
|
+
| File | When to read |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `references/api-notes.md` | On unexpected discovery results, deploy failures, or sObject-specific schema questions |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ServiceChannel xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<capacityModel>TAB_BASED</capacityModel>
|
|
4
|
+
<doesMinimizeWidgetOnAccept>false</doesMinimizeWidgetOnAccept>
|
|
5
|
+
<hasAutoAcceptEnabled>false</hasAutoAcceptEnabled>
|
|
6
|
+
<isInterruptible>false</isInterruptible>
|
|
7
|
+
<label>Cases</label>
|
|
8
|
+
<relatedEntityType>Case</relatedEntityType>
|
|
9
|
+
</ServiceChannel>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ServiceChannel xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<capacityModel>TAB_BASED</capacityModel>
|
|
4
|
+
<doesMinimizeWidgetOnAccept>false</doesMinimizeWidgetOnAccept>
|
|
5
|
+
<hasAutoAcceptEnabled>false</hasAutoAcceptEnabled>
|
|
6
|
+
<isInterruptible>false</isInterruptible>
|
|
7
|
+
<label>Incidents</label>
|
|
8
|
+
<relatedEntityType>Incident</relatedEntityType>
|
|
9
|
+
</ServiceChannel>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ServiceChannel xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<capacityModel>TAB_BASED</capacityModel>
|
|
4
|
+
<doesMinimizeWidgetOnAccept>false</doesMinimizeWidgetOnAccept>
|
|
5
|
+
<hasAutoAcceptEnabled>false</hasAutoAcceptEnabled>
|
|
6
|
+
<isInterruptible>true</isInterruptible>
|
|
7
|
+
<label>Messaging Sessions</label>
|
|
8
|
+
<relatedEntityType>MessagingSession</relatedEntityType>
|
|
9
|
+
</ServiceChannel>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ServiceChannel xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<capacityModel>TAB_BASED</capacityModel>
|
|
4
|
+
<doesMinimizeWidgetOnAccept>false</doesMinimizeWidgetOnAccept>
|
|
5
|
+
<hasAutoAcceptEnabled>false</hasAutoAcceptEnabled>
|
|
6
|
+
<isInterruptible>false</isInterruptible>
|
|
7
|
+
<label>Voice Calls</label>
|
|
8
|
+
<relatedEntityType>VoiceCall</relatedEntityType>
|
|
9
|
+
</ServiceChannel>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<types>
|
|
4
|
+
<members>Cases</members>
|
|
5
|
+
<members>Incidents</members>
|
|
6
|
+
<members>MessagingSessions</members>
|
|
7
|
+
<members>VoiceCalls</members>
|
|
8
|
+
<name>ServiceChannel</name>
|
|
9
|
+
</types>
|
|
10
|
+
<version>66.0</version>
|
|
11
|
+
</Package>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ServiceChannel — API notes
|
|
2
|
+
|
|
3
|
+
Load this reference only when the deploy fails or Salesforce reports unexpected state. Under normal operation the skill's deploy-and-report script just runs and emits its JSON; this doc is for troubleshooting and extensions.
|
|
4
|
+
|
|
5
|
+
**Metadata type:** `ServiceChannel` (queryable via the Tooling API for basic fields; Metadata-only for `capacityModel` and other tuning fields). **API version:** v66.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The six fields in the shipped Cases XML
|
|
10
|
+
|
|
11
|
+
| Field | Value | Semantics |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `capacityModel` | `TAB_BASED` | Capacity accounting model. `TAB_BASED` = each open Console tab = 1 unit of an agent's total capacity. Alternative: `STATUS_BASED` (capacity varies by presence status) |
|
|
14
|
+
| `doesMinimizeWidgetOnAccept` | `false` | Whether the Omni widget minimizes when an agent accepts work. `false` = widget stays visible |
|
|
15
|
+
| `hasAutoAcceptEnabled` | `false` | Whether work is auto-accepted (no click). `false` = agent must manually accept |
|
|
16
|
+
| `isInterruptible` | `false` | Whether new work can interrupt an active work item. `false` = agents finish current work first |
|
|
17
|
+
| `label` | `Cases` | UI display name. Must be `Cases` for the standard Case channel |
|
|
18
|
+
| `relatedEntityType` | `Case` | The sObject type this channel routes. Must be `Case` for Case routing |
|
|
19
|
+
|
|
20
|
+
**Fields not in the shipped XML (Salesforce uses defaults):**
|
|
21
|
+
- `secondaryRoutingPriorityField` — set only when secondary priority sorting is needed.
|
|
22
|
+
- `customEventChannel` / `customEventPayloadField` — only for custom-event-driven channels.
|
|
23
|
+
- `dispatcherClass` — only for advanced routing customization.
|
|
24
|
+
|
|
25
|
+
Fork the skill's XML to set any of these.
|
|
26
|
+
|
|
27
|
+
## Capacity is not tunable at the channel level on v66
|
|
28
|
+
|
|
29
|
+
Per-item weighted capacity (`capacityWeight` on older API versions) is no longer a ServiceChannel field on v66; the schema exposes `capacityModel` (`TAB_BASED` | `STATUS_BASED`) instead. For per-agent totals use `PresenceUserConfig.Capacity` at the agent level, or switch `capacityModel` to `STATUS_BASED` for status-dependent behavior. Related renames on v66: `masterLabel` → `label`, `relatedEntity` → `relatedEntityType`; `isCustomerVisible` was removed (customer visibility is now inferred from channel type).
|
|
30
|
+
|
|
31
|
+
**Implication for this skill:** on a fresh v66 CDO the `Cases` channel already has all six fields at their defaults, so this skill's deploy usually returns `state: Unchanged` / `status: reused`. It is most useful for recovering a mis-configured `Cases` channel, migrating from an older API version's config, or documenting the canonical v66 state in a git-controlled asset. It cannot change per-Case weighted capacity, and enabling auto-accept or minimizing behavior requires forking the XML.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Standard channels' DeveloperNames
|
|
36
|
+
|
|
37
|
+
Salesforce ships several standard ServiceChannels on Service Cloud orgs:
|
|
38
|
+
|
|
39
|
+
| DeveloperName | MasterLabel | RelatedEntity | Purpose |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| `Cases` | `Cases` | `Case` | Standard Case routing (**this skill's target**) |
|
|
42
|
+
| `sfdc_liveagent` | `Chat` | `LiveChatTranscript` | Live Agent chat |
|
|
43
|
+
| `sfdc_livemessage` | `Messaging` | `MessagingSession` | Messaging (WhatsApp, SMS, etc.) |
|
|
44
|
+
| `sfdc_phone` | `Phone` | `VoiceCall` | Voice/telephony |
|
|
45
|
+
|
|
46
|
+
The standard Case channel's DeveloperName is `Cases` (plural), not `Case` (singular). To configure a different standard channel, fork this skill's XML and package.xml so the file path and package member both match the target DeveloperName.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ServiceChannel's Metadata field is not Tooling-SOQL queryable
|
|
51
|
+
|
|
52
|
+
`capacityModel` and the other tuning fields cannot be read via Tooling SOQL as a pre-check:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
SELECT Id, DeveloperName, MasterLabel, CapacityWeight FROM ServiceChannel WHERE DeveloperName='Cases'
|
|
56
|
+
-- INVALID_FIELD: No such column 'CapacityWeight' on entity 'ServiceChannel'.
|
|
57
|
+
|
|
58
|
+
SELECT Id, DeveloperName, MasterLabel, Metadata FROM ServiceChannel WHERE DeveloperName='Cases'
|
|
59
|
+
-- INVALID_FIELD: No such column 'Metadata' on entity 'ServiceChannel'.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`GET /services/data/vXX.0/tooling/sobjects/ServiceChannel/<id>` also returns `{Metadata: null}` even when the metadata exists. The workaround is a Metadata API retrieve (`sf project retrieve start --metadata "ServiceChannel:Cases"`) — slow (~90 sec) but reliable. This skill avoids the retrieve entirely by relying on the deploy's own `files[0].state` signal.
|
|
63
|
+
|
|
64
|
+
Behavior is entity-specific: `WorkSkillRouting` exposes a queryable `Metadata` field, `ServiceChannel` does not. Always describe first (or test with a small query) before assuming Tooling SOQL is a viable read path.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Deploy failure modes
|
|
69
|
+
|
|
70
|
+
| Symptom | Root cause | Skill behavior |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `INVALID_TYPE` for ServiceChannel | OmniChannelSettings not enabled (`enableOmniChannel=false`) | Translates to: "OmniChannelSettings is not enabled on this org. Run service-omni-base-settings-configure first." |
|
|
73
|
+
| `FIELD_INTEGRITY_EXCEPTION` for relatedEntity | Attempted to change `relatedEntity` (immutable on the standard Case channel) | Block; do not change relatedEntity via this skill — write a separate custom-channel-create skill |
|
|
74
|
+
| `DUPLICATE_DEVELOPER_NAME` | Tried to create a channel with an existing DeveloperName | Should not happen (this skill updates the existing `Cases`, not creates) — indicates a bug or org corruption |
|
|
75
|
+
| `state: Unchanged` | Payload matched existing state | Success — `status: reused` |
|
|
76
|
+
| `state: Changed, success: true` | Payload modified state | Success — `status: created` |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Alternative approaches (evaluated, not chosen)
|
|
81
|
+
|
|
82
|
+
- **Tooling API PATCH on `Metadata`** — fails at the read step (see above), so a PATCH cannot be validated. The Metadata API is the reliable canonical path.
|
|
83
|
+
- **Retrieve → parse XML → decide → deploy** — rejected because the ~90-second retrieve latency is not justified when the deploy's own `state: Unchanged` signal is authoritative. If before-state visibility becomes important, add an optional `--with-detect` flag.
|
|
84
|
+
- **`sf api request rest` GET → PATCH** — the GET works but returns `Metadata: null` for ServiceChannel, so it is not a viable read path.
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# deploy-and-report.sh - ensure a ServiceChannel exists for the requested sObject, reusing any existing (standard/custom) one and deploying the canonical template only if none exists (Salesforce allows one channel per RelatedEntity). Args, exit codes: SKILL.md.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
if [ $# -lt 1 ]; then
|
|
7
|
+
echo '{"error":"Missing required argument: org-alias","usage":"bash deploy-and-report.sh <org-alias> [Case|Incident|MessagingSession|VoiceCall]"}' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
ORG="$1"
|
|
12
|
+
SOBJECT_TYPE="${2:-Case}"
|
|
13
|
+
|
|
14
|
+
case "$SOBJECT_TYPE" in
|
|
15
|
+
Case)
|
|
16
|
+
CANONICAL_DEVELOPER_NAME="Cases"
|
|
17
|
+
TARGET_CAPACITY_MODEL="TAB_BASED"
|
|
18
|
+
;;
|
|
19
|
+
Incident)
|
|
20
|
+
CANONICAL_DEVELOPER_NAME="Incidents"
|
|
21
|
+
TARGET_CAPACITY_MODEL="TAB_BASED"
|
|
22
|
+
;;
|
|
23
|
+
MessagingSession)
|
|
24
|
+
CANONICAL_DEVELOPER_NAME="MessagingSessions"
|
|
25
|
+
TARGET_CAPACITY_MODEL="TAB_BASED"
|
|
26
|
+
;;
|
|
27
|
+
VoiceCall)
|
|
28
|
+
CANONICAL_DEVELOPER_NAME="VoiceCalls"
|
|
29
|
+
TARGET_CAPACITY_MODEL="TAB_BASED"
|
|
30
|
+
;;
|
|
31
|
+
*)
|
|
32
|
+
echo "{\"error\":\"Unsupported sobject_type: '$SOBJECT_TYPE'. Supported: Case, Incident, MessagingSession, VoiceCall\"}" >&2
|
|
33
|
+
exit 2
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
|
|
37
|
+
if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
|
|
38
|
+
echo "{\"error\":\"Org alias '$ORG' is not authenticated.\"}" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Write guard: can deploy a ServiceChannel, so self-enforce the production-org guard (directly invokable).
|
|
43
|
+
ORG_JSON=$(sf data query --target-org "$ORG" \
|
|
44
|
+
--query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" \
|
|
45
|
+
--json 2>/dev/null) || {
|
|
46
|
+
echo "{\"error\":\"Failed to query Organization for safe_to_write guard: $(printf '%s' "$ORG_JSON" | head -c 300)\"}" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox')
|
|
50
|
+
TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
|
|
51
|
+
ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType')
|
|
52
|
+
|
|
53
|
+
SAFE_TO_WRITE=false
|
|
54
|
+
if [ "$IS_SANDBOX" = "true" ] \
|
|
55
|
+
|| [ "$TRIAL_EXP" != "null" ] \
|
|
56
|
+
|| [ "$ORG_TYPE" = "Developer Edition" ] \
|
|
57
|
+
|| [ "$ORG_TYPE" = "Base Edition" ]; then
|
|
58
|
+
SAFE_TO_WRITE=true
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
if [ "$SAFE_TO_WRITE" != "true" ]; then
|
|
62
|
+
# Structured `blocked` results go to stdout so the coordinator/caller can parse them (stderr would look like empty output).
|
|
63
|
+
echo "{\"skill\":\"service-omni-service-channel-configure\",\"status\":\"blocked\",\"blocking_issue\":\"Refusing to deploy a ServiceChannel to a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org.\",\"safe_to_write\":false}"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# emit read-only plan preview JSON and exit 0 (PLAN_ONLY mode)
|
|
68
|
+
emit_plan() {
|
|
69
|
+
local status="$1"
|
|
70
|
+
local detail="$2"
|
|
71
|
+
jq -n \
|
|
72
|
+
--arg status "$status" \
|
|
73
|
+
--arg detail "$detail" \
|
|
74
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
75
|
+
--arg dn "$CANONICAL_DEVELOPER_NAME" \
|
|
76
|
+
'{
|
|
77
|
+
skill: "service-omni-service-channel-configure",
|
|
78
|
+
status: $status,
|
|
79
|
+
plan_mode: true,
|
|
80
|
+
plan_detail: $detail,
|
|
81
|
+
sobject_type: $sobject,
|
|
82
|
+
channel_developer_name: $dn,
|
|
83
|
+
manual_actions: [],
|
|
84
|
+
blocking_issue: null
|
|
85
|
+
}'
|
|
86
|
+
exit 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
90
|
+
ASSETS_DIR="$(cd "$SCRIPT_DIR/../assets" && pwd)"
|
|
91
|
+
|
|
92
|
+
# Prereq: target sObject exists on the org.
|
|
93
|
+
ENTITY_CHECK=$(sf data query --target-org "$ORG" \
|
|
94
|
+
--query "SELECT QualifiedApiName FROM EntityDefinition WHERE QualifiedApiName = '$SOBJECT_TYPE'" \
|
|
95
|
+
--json 2>/dev/null | jq -r '.result.totalSize // 0')
|
|
96
|
+
|
|
97
|
+
if [ "$ENTITY_CHECK" != "1" ]; then
|
|
98
|
+
jq -n --arg sobject "$SOBJECT_TYPE" \
|
|
99
|
+
'{
|
|
100
|
+
skill: "service-omni-service-channel-configure",
|
|
101
|
+
status: "blocked",
|
|
102
|
+
sobject_type: $sobject,
|
|
103
|
+
channel_developer_name: null,
|
|
104
|
+
channel_origin: null,
|
|
105
|
+
target_capacity_model: null,
|
|
106
|
+
target_related_entity_type: $sobject,
|
|
107
|
+
before_state: "unknown",
|
|
108
|
+
deploy_id: null,
|
|
109
|
+
manual_actions: [{
|
|
110
|
+
id: "prereq-entity-missing",
|
|
111
|
+
title: ("sObject " + $sobject + " not available on this org"),
|
|
112
|
+
click_path: (
|
|
113
|
+
if $sobject == "Incident" then "Enable the Incident Management feature via Setup, then re-run"
|
|
114
|
+
elif $sobject == "MessagingSession" then "Enable Enhanced Messaging via Setup → Messaging, then re-run"
|
|
115
|
+
else "Contact your admin - expected sObject missing"
|
|
116
|
+
end
|
|
117
|
+
)
|
|
118
|
+
}],
|
|
119
|
+
blocking_issue: ("Target sObject " + $sobject + " is not queryable on this org")
|
|
120
|
+
}'
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Step 1: Discover an existing ServiceChannel (Tooling API only). Treat the result as authoritative
|
|
125
|
+
# only when it parses to a numeric totalSize - an inconclusive read must not deploy a duplicate.
|
|
126
|
+
EXISTING_JSON=$(sf data query --use-tooling-api --target-org "$ORG" \
|
|
127
|
+
--query "SELECT Id, DeveloperName, MasterLabel FROM ServiceChannel WHERE RelatedEntity = '$SOBJECT_TYPE' ORDER BY DeveloperName LIMIT 1" \
|
|
128
|
+
--json 2>/dev/null || true)
|
|
129
|
+
|
|
130
|
+
if ! echo "$EXISTING_JSON" | jq -e '.result.totalSize | numbers' >/dev/null 2>&1; then
|
|
131
|
+
jq -n --arg sobject "$SOBJECT_TYPE" --arg raw "$(printf '%s' "$EXISTING_JSON" | head -c 400)" \
|
|
132
|
+
'{
|
|
133
|
+
skill: "service-omni-service-channel-configure",
|
|
134
|
+
status: "blocked",
|
|
135
|
+
sobject_type: $sobject,
|
|
136
|
+
channel_developer_name: null,
|
|
137
|
+
channel_origin: null,
|
|
138
|
+
before_state: "unknown",
|
|
139
|
+
deploy_id: null,
|
|
140
|
+
manual_actions: [],
|
|
141
|
+
blocking_issue: ("Could not determine whether a ServiceChannel already exists for " + $sobject + " - the discovery query was inconclusive. Not deploying on an inconclusive read. Raw: " + $raw)
|
|
142
|
+
}'
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
EXISTING_COUNT=$(echo "$EXISTING_JSON" | jq -r '.result.totalSize // 0')
|
|
147
|
+
|
|
148
|
+
if [ "$EXISTING_COUNT" = "1" ]; then
|
|
149
|
+
# Step 2a: REUSE the existing channel - no deploy.
|
|
150
|
+
EXISTING_DEV_NAME=$(echo "$EXISTING_JSON" | jq -r '.result.records[0].DeveloperName')
|
|
151
|
+
EXISTING_ID=$(echo "$EXISTING_JSON" | jq -r '.result.records[0].Id')
|
|
152
|
+
# Origin taxonomy: salesforce_standard (OOTB), canonical_asset (matches our asset names), custom (else).
|
|
153
|
+
CHANNEL_ORIGIN="custom"
|
|
154
|
+
case "$EXISTING_DEV_NAME" in
|
|
155
|
+
sfdc_*|Cases) CHANNEL_ORIGIN="salesforce_standard" ;;
|
|
156
|
+
Incidents|MessagingSessions) CHANNEL_ORIGIN="canonical_asset" ;;
|
|
157
|
+
esac
|
|
158
|
+
|
|
159
|
+
jq -n \
|
|
160
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
161
|
+
--arg dev_name "$EXISTING_DEV_NAME" \
|
|
162
|
+
--arg existing_id "$EXISTING_ID" \
|
|
163
|
+
--arg origin "$CHANNEL_ORIGIN" \
|
|
164
|
+
--arg cap_model "$TARGET_CAPACITY_MODEL" \
|
|
165
|
+
'{
|
|
166
|
+
skill: "service-omni-service-channel-configure",
|
|
167
|
+
status: "reused",
|
|
168
|
+
sobject_type: $sobject,
|
|
169
|
+
channel_developer_name: $dev_name,
|
|
170
|
+
channel_id: $existing_id,
|
|
171
|
+
channel_origin: $origin,
|
|
172
|
+
target_capacity_model: $cap_model,
|
|
173
|
+
target_related_entity_type: $sobject,
|
|
174
|
+
before_state: "Unchanged",
|
|
175
|
+
deploy_id: null,
|
|
176
|
+
manual_actions: [],
|
|
177
|
+
blocking_issue: null
|
|
178
|
+
}'
|
|
179
|
+
exit 0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# PLAN_ONLY: reaching here means no channel exists for this sObject (the reuse path above already
|
|
183
|
+
# returned). A --run would deploy the canonical template. Report that and stop before the deploy.
|
|
184
|
+
if [ "${PLAN_ONLY:-}" = "1" ]; then
|
|
185
|
+
emit_plan "action_needed" "No ServiceChannel bound to $SOBJECT_TYPE; would deploy canonical '$CANONICAL_DEVELOPER_NAME'."
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Step 2b: No existing channel - deploy our canonical XML.
|
|
189
|
+
if [ ! -f "$ASSETS_DIR/sfdx-project.json" ]; then
|
|
190
|
+
echo "{\"error\":\"Assets DX project not found at $ASSETS_DIR\"}" >&2
|
|
191
|
+
exit 1
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
CHANNEL_XML="$ASSETS_DIR/force-app/main/default/serviceChannels/${CANONICAL_DEVELOPER_NAME}.serviceChannel-meta.xml"
|
|
195
|
+
if [ ! -f "$CHANNEL_XML" ]; then
|
|
196
|
+
echo "{\"error\":\"ServiceChannel asset not found for sobject_type=$SOBJECT_TYPE at $CHANNEL_XML\"}" >&2
|
|
197
|
+
exit 1
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
DEPLOY_JSON=$(cd "$ASSETS_DIR" && sf project deploy start --target-org "$ORG" --metadata "ServiceChannel:${CANONICAL_DEVELOPER_NAME}" --json 2>/dev/null || true)
|
|
201
|
+
|
|
202
|
+
DEPLOY_SUCCESS=$(echo "$DEPLOY_JSON" | jq -r '.result.success // false' 2>/dev/null)
|
|
203
|
+
DEPLOY_ID=$(echo "$DEPLOY_JSON" | jq -r '.result.id // null' 2>/dev/null)
|
|
204
|
+
|
|
205
|
+
translate_deploy_error() {
|
|
206
|
+
local raw="$1"
|
|
207
|
+
case "$raw" in
|
|
208
|
+
*INVALID_TYPE*"ServiceChannel"*)
|
|
209
|
+
echo "OmniChannelSettings is not enabled on this org. Run service-omni-base-settings-configure first."
|
|
210
|
+
;;
|
|
211
|
+
*"already in use by another Service channel"*)
|
|
212
|
+
echo "Race condition: another process created a ServiceChannel for $SOBJECT_TYPE between our detect and deploy. Re-run to reuse it."
|
|
213
|
+
;;
|
|
214
|
+
*)
|
|
215
|
+
echo "$raw"
|
|
216
|
+
;;
|
|
217
|
+
esac
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if [ "$DEPLOY_SUCCESS" != "true" ]; then
|
|
221
|
+
FAILURES=$(echo "$DEPLOY_JSON" | jq -r '.result.details.componentFailures // [] | map(.problem) | join("; ")' 2>/dev/null)
|
|
222
|
+
if [ -z "$FAILURES" ] || [ "$FAILURES" = "null" ]; then
|
|
223
|
+
FAILURES=$(echo "$DEPLOY_JSON" | jq -r '.message // .result.errorMessage // "Unknown deploy error"' 2>/dev/null)
|
|
224
|
+
fi
|
|
225
|
+
FRIENDLY=$(translate_deploy_error "$FAILURES")
|
|
226
|
+
jq -n \
|
|
227
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
228
|
+
--arg dev_name "$CANONICAL_DEVELOPER_NAME" \
|
|
229
|
+
--arg cap_model "$TARGET_CAPACITY_MODEL" \
|
|
230
|
+
--arg deploy_id "$DEPLOY_ID" \
|
|
231
|
+
--arg blocking "$FRIENDLY" \
|
|
232
|
+
--arg raw "$FAILURES" \
|
|
233
|
+
'{
|
|
234
|
+
skill: "service-omni-service-channel-configure",
|
|
235
|
+
status: "blocked",
|
|
236
|
+
sobject_type: $sobject,
|
|
237
|
+
channel_developer_name: $dev_name,
|
|
238
|
+
channel_id: null,
|
|
239
|
+
channel_origin: "canonical_template",
|
|
240
|
+
target_capacity_model: $cap_model,
|
|
241
|
+
target_related_entity_type: $sobject,
|
|
242
|
+
before_state: "unknown",
|
|
243
|
+
deploy_id: (if $deploy_id == "null" or $deploy_id == "" then null else $deploy_id end),
|
|
244
|
+
manual_actions: [],
|
|
245
|
+
blocking_issue: $blocking,
|
|
246
|
+
raw_error: $raw
|
|
247
|
+
}'
|
|
248
|
+
exit 1
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# Require a clean Succeeded (SucceededPartial / anything else is not success).
|
|
252
|
+
DEPLOY_STATUS=$(echo "$DEPLOY_JSON" | jq -r '.result.status // "unknown"' 2>/dev/null)
|
|
253
|
+
if [ "$DEPLOY_STATUS" != "Succeeded" ]; then
|
|
254
|
+
jq -n \
|
|
255
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
256
|
+
--arg dev_name "$CANONICAL_DEVELOPER_NAME" \
|
|
257
|
+
--arg cap_model "$TARGET_CAPACITY_MODEL" \
|
|
258
|
+
--arg deploy_id "$DEPLOY_ID" \
|
|
259
|
+
--arg deploy_status "$DEPLOY_STATUS" \
|
|
260
|
+
'{
|
|
261
|
+
skill: "service-omni-service-channel-configure",
|
|
262
|
+
status: "blocked",
|
|
263
|
+
sobject_type: $sobject,
|
|
264
|
+
channel_developer_name: $dev_name,
|
|
265
|
+
channel_id: null,
|
|
266
|
+
channel_origin: "canonical_template",
|
|
267
|
+
target_capacity_model: $cap_model,
|
|
268
|
+
target_related_entity_type: $sobject,
|
|
269
|
+
before_state: "unknown",
|
|
270
|
+
deploy_id: (if $deploy_id == "null" or $deploy_id == "" then null else $deploy_id end),
|
|
271
|
+
manual_actions: [],
|
|
272
|
+
blocking_issue: ("Deploy did not fully succeed (result.status=" + $deploy_status + "). Refusing to report success on a partial/ambiguous deploy.")
|
|
273
|
+
}'
|
|
274
|
+
exit 1
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Component state must be a recognized idempotency signal (Created/Changed/Unchanged); else block.
|
|
278
|
+
BEFORE_STATE=$(echo "$DEPLOY_JSON" | jq -r '.result.files[0].state // "unknown"' 2>/dev/null)
|
|
279
|
+
case "$BEFORE_STATE" in
|
|
280
|
+
Created|Changed) STATUS="created" ;;
|
|
281
|
+
Unchanged) STATUS="reused" ;;
|
|
282
|
+
*)
|
|
283
|
+
jq -n \
|
|
284
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
285
|
+
--arg dev_name "$CANONICAL_DEVELOPER_NAME" \
|
|
286
|
+
--arg cap_model "$TARGET_CAPACITY_MODEL" \
|
|
287
|
+
--arg deploy_id "$DEPLOY_ID" \
|
|
288
|
+
--arg before_state "$BEFORE_STATE" \
|
|
289
|
+
'{
|
|
290
|
+
skill: "service-omni-service-channel-configure",
|
|
291
|
+
status: "blocked",
|
|
292
|
+
sobject_type: $sobject,
|
|
293
|
+
channel_developer_name: $dev_name,
|
|
294
|
+
channel_id: null,
|
|
295
|
+
channel_origin: "canonical_template",
|
|
296
|
+
target_capacity_model: $cap_model,
|
|
297
|
+
target_related_entity_type: $sobject,
|
|
298
|
+
before_state: $before_state,
|
|
299
|
+
deploy_id: (if $deploy_id == "null" or $deploy_id == "" then null else $deploy_id end),
|
|
300
|
+
manual_actions: [],
|
|
301
|
+
blocking_issue: ("Unrecognized component deploy state \"" + $before_state + "\" - expected Created, Changed, or Unchanged. Not reporting success.")
|
|
302
|
+
}'
|
|
303
|
+
exit 1
|
|
304
|
+
;;
|
|
305
|
+
esac
|
|
306
|
+
|
|
307
|
+
# Post-deploy verify: re-query via Tooling API to confirm the channel exists, capture its Id, and
|
|
308
|
+
# check RelatedEntity matches the target (a deploy state alone isn't proof of correct binding).
|
|
309
|
+
VERIFY_JSON=$(sf data query --use-tooling-api --target-org "$ORG" \
|
|
310
|
+
--query "SELECT Id, DeveloperName, MasterLabel, RelatedEntity FROM ServiceChannel WHERE DeveloperName = '$CANONICAL_DEVELOPER_NAME' LIMIT 1" \
|
|
311
|
+
--json 2>/dev/null || true)
|
|
312
|
+
|
|
313
|
+
if ! echo "$VERIFY_JSON" | jq -e '.result.totalSize | numbers' >/dev/null 2>&1 \
|
|
314
|
+
|| [ "$(echo "$VERIFY_JSON" | jq -r '.result.totalSize')" != "1" ]; then
|
|
315
|
+
jq -n \
|
|
316
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
317
|
+
--arg dev_name "$CANONICAL_DEVELOPER_NAME" \
|
|
318
|
+
--arg deploy_id "$DEPLOY_ID" \
|
|
319
|
+
--arg raw "$(printf '%s' "$VERIFY_JSON" | head -c 300)" \
|
|
320
|
+
'{
|
|
321
|
+
skill: "service-omni-service-channel-configure",
|
|
322
|
+
status: "blocked",
|
|
323
|
+
sobject_type: $sobject,
|
|
324
|
+
channel_developer_name: $dev_name,
|
|
325
|
+
channel_id: null,
|
|
326
|
+
channel_origin: "canonical_template",
|
|
327
|
+
target_related_entity_type: $sobject,
|
|
328
|
+
before_state: "unknown",
|
|
329
|
+
deploy_id: (if $deploy_id == "null" or $deploy_id == "" then null else $deploy_id end),
|
|
330
|
+
manual_actions: [],
|
|
331
|
+
blocking_issue: ("Deploy reported success but post-deploy re-query could not confirm the ServiceChannel exists. Raw: " + $raw)
|
|
332
|
+
}'
|
|
333
|
+
exit 1
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
VERIFIED_ID=$(echo "$VERIFY_JSON" | jq -r '.result.records[0].Id')
|
|
337
|
+
VERIFIED_RELATED=$(echo "$VERIFY_JSON" | jq -r '.result.records[0].RelatedEntity // "null"')
|
|
338
|
+
|
|
339
|
+
if [ "$VERIFIED_RELATED" != "$SOBJECT_TYPE" ]; then
|
|
340
|
+
jq -n \
|
|
341
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
342
|
+
--arg dev_name "$CANONICAL_DEVELOPER_NAME" \
|
|
343
|
+
--arg channel_id "$VERIFIED_ID" \
|
|
344
|
+
--arg related "$VERIFIED_RELATED" \
|
|
345
|
+
'{
|
|
346
|
+
skill: "service-omni-service-channel-configure",
|
|
347
|
+
status: "blocked",
|
|
348
|
+
sobject_type: $sobject,
|
|
349
|
+
channel_developer_name: $dev_name,
|
|
350
|
+
channel_id: $channel_id,
|
|
351
|
+
channel_origin: "canonical_template",
|
|
352
|
+
target_related_entity_type: $sobject,
|
|
353
|
+
before_state: "unknown",
|
|
354
|
+
manual_actions: [],
|
|
355
|
+
blocking_issue: ("ServiceChannel exists but RelatedEntity=\"" + $related + "\" does not match the requested sObject \"" + $sobject + "\".")
|
|
356
|
+
}'
|
|
357
|
+
exit 1
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
jq -n \
|
|
361
|
+
--arg status "$STATUS" \
|
|
362
|
+
--arg sobject "$SOBJECT_TYPE" \
|
|
363
|
+
--arg dev_name "$CANONICAL_DEVELOPER_NAME" \
|
|
364
|
+
--arg channel_id "$VERIFIED_ID" \
|
|
365
|
+
--arg related "$VERIFIED_RELATED" \
|
|
366
|
+
--arg cap_model "$TARGET_CAPACITY_MODEL" \
|
|
367
|
+
--arg before_state "$BEFORE_STATE" \
|
|
368
|
+
--arg deploy_id "$DEPLOY_ID" \
|
|
369
|
+
'{
|
|
370
|
+
skill: "service-omni-service-channel-configure",
|
|
371
|
+
status: $status,
|
|
372
|
+
sobject_type: $sobject,
|
|
373
|
+
channel_developer_name: $dev_name,
|
|
374
|
+
channel_id: $channel_id,
|
|
375
|
+
channel_origin: "canonical_template",
|
|
376
|
+
target_capacity_model: $cap_model,
|
|
377
|
+
target_related_entity_type: $sobject,
|
|
378
|
+
related_entity_verified: $related,
|
|
379
|
+
before_state: $before_state,
|
|
380
|
+
deploy_id: $deploy_id,
|
|
381
|
+
manual_actions: [],
|
|
382
|
+
blocking_issue: null
|
|
383
|
+
}'
|