@salesforce/afv-skills 1.47.0 → 1.49.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 (160) hide show
  1. package/package.json +1 -1
  2. package/skills/education-cloud-domain-configure/SKILL.md +234 -0
  3. package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
  4. package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
  5. package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
  6. package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
  7. package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
  8. package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
  9. package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
  10. package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
  11. package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
  12. package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
  13. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
  14. package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
  15. package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
  16. package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
  17. package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
  18. package/skills/education-cloud-multi-campus-configure/SKILL.md +2 -0
  19. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +4 -3
  20. package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
  21. package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
  22. package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
  23. package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
  24. package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
  25. package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
  26. package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
  27. package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
  28. package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
  29. package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
  30. package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
  31. package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
  32. package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
  33. package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
  34. package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
  35. package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
  36. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
  37. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
  38. package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
  39. package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
  40. package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
  41. package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
  42. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
  43. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
  44. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
  45. package/skills/service-omni-agent-users-create/SKILL.md +96 -0
  46. package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
  47. package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
  48. package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
  49. package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
  50. package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
  51. package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
  52. package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
  53. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
  54. package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
  55. package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
  56. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
  57. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
  58. package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
  59. package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
  60. package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
  61. package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
  62. package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
  63. package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
  64. package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
  65. package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
  66. package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
  67. package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
  68. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
  69. package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
  70. package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
  71. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
  72. package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
  73. package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
  74. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
  75. package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
  76. package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
  77. package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
  78. package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
  79. package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
  80. package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
  81. package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
  82. package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
  83. package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
  84. package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
  85. package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
  86. package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
  87. package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
  88. package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
  89. package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
  90. package/skills/service-omni-queue-deploy/SKILL.md +91 -0
  91. package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
  92. package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
  93. package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
  94. package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
  95. package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
  96. package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
  97. package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
  98. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
  99. package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
  100. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
  101. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
  102. package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
  103. package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
  104. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
  105. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
  106. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
  107. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
  108. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
  109. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
  110. package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
  111. package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
  112. package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
  113. package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
  114. package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
  115. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
  116. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
  117. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
  118. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
  119. package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
  120. package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
  121. package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
  122. package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
  123. package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
  124. package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
  125. package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
  126. package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
  127. package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
  128. package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
  129. package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
  130. package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
  131. package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
  132. package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
  133. package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
  134. package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
  135. package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
  136. package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
  137. package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
  138. package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
  139. package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
  140. package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
  141. package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
  142. package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
  143. package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
  144. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
  145. package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
  146. package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
  147. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
  148. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
  149. package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
  150. package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
  151. package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
  152. package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
  153. package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
  154. package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
  155. package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
  156. package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
  157. package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
  158. package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
  159. package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
  160. package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ ORG="${1:-}"
6
+ MODE="run"
7
+ CONFIRMED="false"
8
+
9
+ if [ "$#" -gt 0 ]; then
10
+ shift
11
+ fi
12
+ while [ "$#" -gt 0 ]; do
13
+ case "$1" in
14
+ --plan) MODE="plan" ;;
15
+ --confirm-org-wide-visibility) CONFIRMED="true" ;;
16
+ *)
17
+ echo '{"status":"blocked","blocking_issue":"Usage: configure-and-report.sh <org-alias> [--plan] [--confirm-org-wide-visibility]"}'
18
+ exit 1
19
+ ;;
20
+ esac
21
+ shift
22
+ done
23
+
24
+ if [ -z "$ORG" ]; then
25
+ echo '{"status":"blocked","blocking_issue":"An authenticated org alias is required."}'
26
+ exit 1
27
+ fi
28
+
29
+ WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/agentwork-sharing.XXXXXX")
30
+ trap 'rm -rf "$WORK_DIR"' EXIT
31
+
32
+ emit_result() {
33
+ local status="$1" previous_model="$2" current_model="$3" external_model="$4"
34
+ local changed="$5" dry_run="$6" deploy_id="$7" blocking_issue="$8"
35
+ jq -n \
36
+ --arg status "$status" \
37
+ --arg previous "$previous_model" \
38
+ --arg current "$current_model" \
39
+ --arg external "$external_model" \
40
+ --arg changed "$changed" \
41
+ --arg dry_run "$dry_run" \
42
+ --arg deploy_id "$deploy_id" \
43
+ --arg blocking_issue "$blocking_issue" \
44
+ '{
45
+ skill: "service-omni-agent-work-sharing-configure",
46
+ status: $status,
47
+ previous_sharing_model: (if $previous == "" then null else $previous end),
48
+ sharing_model: (if $current == "" then null else $current end),
49
+ external_sharing_model: (if $external == "" then null else $external end),
50
+ changed: ($changed == "true"),
51
+ dry_run: ($dry_run == "true"),
52
+ deploy_id: (if $deploy_id == "" then null else $deploy_id end),
53
+ blocking_issue: (if $blocking_issue == "" then null else $blocking_issue end)
54
+ }'
55
+ }
56
+
57
+ xml_value() {
58
+ local tag="$1" file="$2"
59
+ tr -d '\n\r' < "$file" | sed -n "s:.*<$tag>\([^<]*\)</$tag>.*:\1:p"
60
+ }
61
+
62
+ retrieve_agent_work() {
63
+ local destination="$1" result object_file
64
+ mkdir -p "$destination"
65
+ if ! result=$(sf project retrieve start \
66
+ --metadata "CustomObject:AgentWork" \
67
+ --target-org "$ORG" \
68
+ --target-metadata-dir "$destination" \
69
+ --unzip \
70
+ --json 2>&1); then
71
+ return 1
72
+ fi
73
+ if [ "$(jq -r '.status // 1' <<<"$result" 2>/dev/null)" != "0" ]; then
74
+ return 1
75
+ fi
76
+ object_file=$(find "$destination" -type f -name 'AgentWork.object' -print -quit)
77
+ if [ -z "$object_file" ]; then
78
+ return 1
79
+ fi
80
+ printf '%s' "$object_file"
81
+ }
82
+
83
+ dry_run="false"
84
+ if [ "$MODE" = "plan" ]; then
85
+ dry_run="true"
86
+ fi
87
+
88
+ org_result=$(sf data query \
89
+ --query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" \
90
+ --target-org "$ORG" \
91
+ --json 2>&1) || {
92
+ emit_result "blocked" "" "" "" false "$dry_run" "" \
93
+ "Unable to classify the target org; refusing an org-wide sharing change."
94
+ exit 1
95
+ }
96
+
97
+ is_sandbox=$(jq -r '.result.records[0].IsSandbox // false' <<<"$org_result" 2>/dev/null)
98
+ trial_expiration=$(jq -r '.result.records[0].TrialExpirationDate // empty' <<<"$org_result" 2>/dev/null)
99
+ organization_type=$(jq -r '.result.records[0].OrganizationType // empty' <<<"$org_result" 2>/dev/null)
100
+ if [ "$MODE" = "run" ] \
101
+ && [ "$is_sandbox" != "true" ] \
102
+ && [ -z "$trial_expiration" ] \
103
+ && [ "$organization_type" != "Developer Edition" ] \
104
+ && [ "$organization_type" != "Base Edition" ]; then
105
+ emit_result "blocked" "" "" "" false false "" \
106
+ "Production customer org detected; org-wide AgentWork sharing changes are refused."
107
+ exit 1
108
+ fi
109
+
110
+ if ! object_file=$(retrieve_agent_work "$WORK_DIR/before"); then
111
+ emit_result "blocked" "" "" "" false "$dry_run" "" \
112
+ "Unable to retrieve CustomObject:AgentWork through the Metadata API."
113
+ exit 1
114
+ fi
115
+
116
+ previous_model=$(xml_value sharingModel "$object_file")
117
+ external_model=$(xml_value externalSharingModel "$object_file")
118
+ if [[ ! "$previous_model" =~ ^(Private|Read|ReadWrite)$ ]] || [[ ! "$external_model" =~ ^(Private|Read|ReadWrite)$ ]]; then
119
+ emit_result "blocked" "$previous_model" "$previous_model" "$external_model" false "$dry_run" "" \
120
+ "AgentWork sharing metadata was incomplete or used an unsupported sharing model."
121
+ exit 1
122
+ fi
123
+
124
+ if [ "$previous_model" = "Read" ] || [ "$previous_model" = "ReadWrite" ]; then
125
+ emit_result "reused" "$previous_model" "$previous_model" "$external_model" false "$dry_run" "" ""
126
+ exit 0
127
+ fi
128
+
129
+ if [ "$MODE" = "plan" ]; then
130
+ emit_result "action_needed" "$previous_model" "Read" "$external_model" false true "" ""
131
+ exit 2
132
+ fi
133
+
134
+ if [ "$CONFIRMED" != "true" ]; then
135
+ emit_result "blocked" "$previous_model" "$previous_model" "$external_model" false false "" \
136
+ "Changing AgentWork from Private to Read expands org-wide internal visibility. Re-run with --confirm-org-wide-visibility after reviewing the blast radius."
137
+ exit 1
138
+ fi
139
+
140
+ source_root="$WORK_DIR/source"
141
+ mkdir -p "$source_root/force-app/main/default/objects/AgentWork"
142
+ cat > "$source_root/sfdx-project.json" <<'JSON'
143
+ {"packageDirectories":[{"path":"force-app","default":true}],"namespace":"","sourceApiVersion":"66.0"}
144
+ JSON
145
+ cat > "$source_root/force-app/main/default/objects/AgentWork/AgentWork.object-meta.xml" <<XML
146
+ <?xml version="1.0" encoding="UTF-8"?>
147
+ <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
148
+ <externalSharingModel>${external_model}</externalSharingModel>
149
+ <sharingModel>Read</sharingModel>
150
+ </CustomObject>
151
+ XML
152
+
153
+ if ! deploy_result=$(cd "$source_root" && sf project deploy start \
154
+ --source-dir force-app/main/default/objects/AgentWork/AgentWork.object-meta.xml \
155
+ --target-org "$ORG" \
156
+ --wait 20 \
157
+ --json 2>&1); then
158
+ emit_result "blocked" "$previous_model" "$previous_model" "$external_model" false false "" \
159
+ "The AgentWork sharing metadata deploy failed."
160
+ exit 1
161
+ fi
162
+
163
+ deploy_status=$(jq -r '.result.status // empty' <<<"$deploy_result" 2>/dev/null)
164
+ deploy_success=$(jq -r '.result.success // false' <<<"$deploy_result" 2>/dev/null)
165
+ deploy_id=$(jq -r '.result.id // empty' <<<"$deploy_result" 2>/dev/null)
166
+ if [ "$deploy_status" != "Succeeded" ] && [ "$deploy_success" != "true" ]; then
167
+ emit_result "blocked" "$previous_model" "$previous_model" "$external_model" false false "$deploy_id" \
168
+ "The AgentWork sharing metadata deploy did not succeed."
169
+ exit 1
170
+ fi
171
+
172
+ if ! readback_file=$(retrieve_agent_work "$WORK_DIR/readback"); then
173
+ emit_result "blocked" "$previous_model" "" "$external_model" true false "$deploy_id" \
174
+ "The AgentWork sharing read-back could not be retrieved after deployment."
175
+ exit 1
176
+ fi
177
+
178
+ readback_model=$(xml_value sharingModel "$readback_file")
179
+ readback_external=$(xml_value externalSharingModel "$readback_file")
180
+ if [ "$readback_model" != "Read" ] || [ "$readback_external" != "$external_model" ]; then
181
+ emit_result "blocked" "$previous_model" "$readback_model" "$readback_external" true false "$deploy_id" \
182
+ "The AgentWork sharing read-back did not match the requested internal model or preserved external model."
183
+ exit 1
184
+ fi
185
+
186
+ emit_result "configured" "$previous_model" "$readback_model" "$readback_external" true false "$deploy_id" ""
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import tempfile
7
+ from pathlib import Path
8
+
9
+
10
+ SKILLS_ROOT = Path(__file__).resolve().parents[3]
11
+ SCRIPT = "service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh"
12
+
13
+ _FAKE_SF = r'''#!/usr/bin/env bash
14
+ sub="${1:-} ${2:-}"; args="$*"
15
+ state_dir="${FAKE_STATE_DIR:?}"
16
+ trace="$state_dir/trace"
17
+ deployed="$state_dir/deployed"
18
+ printf '%s\n' "$args" >> "$trace"
19
+
20
+ arg_after() {
21
+ local wanted="$1" previous=""
22
+ shift
23
+ for value in "$@"; do
24
+ if [ "$previous" = "$wanted" ]; then printf '%s' "$value"; return; fi
25
+ previous="$value"
26
+ done
27
+ }
28
+
29
+ case "$sub" in
30
+ "data query")
31
+ if printf '%s' "$args" | grep -q "FROM Organization"; then
32
+ if [ "${FAKE_PRODUCTION:-0}" = "1" ]; then
33
+ echo '{"status":0,"result":{"records":[{"Id":"00Dxx0000000000EAA","IsSandbox":false,"TrialExpirationDate":null,"OrganizationType":"Enterprise Edition"}]}}'
34
+ elif [ "${FAKE_DEVELOPER_EDITION:-0}" = "1" ]; then
35
+ echo '{"status":0,"result":{"records":[{"Id":"00Dxx0000000000EAA","IsSandbox":false,"TrialExpirationDate":null,"OrganizationType":"Developer Edition"}]}}'
36
+ else
37
+ echo '{"status":0,"result":{"records":[{"Id":"00Dxx0000000000EAA","IsSandbox":true,"TrialExpirationDate":null,"OrganizationType":"Developer Edition"}]}}'
38
+ fi
39
+ fi;;
40
+ "project retrieve")
41
+ target_dir=$(arg_after --target-metadata-dir "$@")
42
+ mkdir -p "$target_dir/unpackaged/unpackaged/objects"
43
+ model="${FAKE_CURRENT_MODEL:-Private}"
44
+ if [ -f "$deployed" ]; then model="${FAKE_READBACK_MODEL:-Read}"; fi
45
+ cat > "$target_dir/unpackaged/unpackaged/objects/AgentWork.object" <<XML
46
+ <?xml version="1.0" encoding="UTF-8"?>
47
+ <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
48
+ <externalSharingModel>${FAKE_EXTERNAL_MODEL:-Private}</externalSharingModel>
49
+ <sharingModel>${model}</sharingModel>
50
+ </CustomObject>
51
+ XML
52
+ echo '{"status":0,"result":{"status":"Succeeded","success":true}}';;
53
+ "project deploy")
54
+ : > "$deployed"
55
+ echo '{"status":0,"result":{"id":"0Afxx0000000001AAA","status":"Succeeded","success":true,"details":{"componentFailures":[]}}}';;
56
+ *) echo '{"status":0,"result":{}}';;
57
+ esac
58
+ '''
59
+
60
+
61
+ def last_json(output: str) -> dict:
62
+ decoder = json.JSONDecoder()
63
+ result, index = None, 0
64
+ while index < len(output):
65
+ if output[index] not in "{[":
66
+ index += 1
67
+ continue
68
+ try:
69
+ candidate, end = decoder.raw_decode(output, index)
70
+ if isinstance(candidate, dict):
71
+ result = candidate
72
+ index = end
73
+ except json.JSONDecodeError:
74
+ index += 1
75
+ assert result is not None, output
76
+ return result
77
+
78
+
79
+ class FakeSf:
80
+ def __init__(self):
81
+ self.root: Path | None = None
82
+ self.bin_dir: Path | None = None
83
+ self.state_dir: Path | None = None
84
+
85
+ def __enter__(self):
86
+ self.root = Path(tempfile.mkdtemp(prefix="agentwork-sharing-fake-sf-"))
87
+ self.bin_dir = self.root / "bin"
88
+ self.state_dir = self.root / "state"
89
+ self.bin_dir.mkdir()
90
+ self.state_dir.mkdir()
91
+ executable = self.bin_dir / "sf"
92
+ executable.write_text(_FAKE_SF)
93
+ executable.chmod(0o755)
94
+ return self
95
+
96
+ def run(self, args, extra_env=None):
97
+ assert self.bin_dir is not None and self.state_dir is not None
98
+ env = dict(os.environ)
99
+ env["PATH"] = str(self.bin_dir) + os.pathsep + env.get("PATH", "")
100
+ env["FAKE_STATE_DIR"] = str(self.state_dir)
101
+ if extra_env:
102
+ env.update({key: str(value) for key, value in extra_env.items()})
103
+ proc = subprocess.run(
104
+ ["bash", str(SKILLS_ROOT / SCRIPT), *args],
105
+ capture_output=True,
106
+ text=True,
107
+ env=env,
108
+ cwd=str(SKILLS_ROOT),
109
+ )
110
+ return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
111
+
112
+ def read_state(self, name: str) -> str:
113
+ assert self.state_dir is not None
114
+ path = self.state_dir / name
115
+ return path.read_text() if path.exists() else ""
116
+
117
+ def __exit__(self, *_):
118
+ import shutil
119
+ if self.root:
120
+ shutil.rmtree(self.root, ignore_errors=True)
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
7
+ from _bootstrap import FakeSf, last_json # noqa: E402
8
+
9
+
10
+ class AgentWorkSharingContractTests(unittest.TestCase):
11
+ def test_private_model_deploys_read_and_verifies(self):
12
+ with FakeSf() as fake:
13
+ rc, output = fake.run(["org", "--confirm-org-wide-visibility"])
14
+ trace = fake.read_state("trace")
15
+
16
+ self.assertEqual(rc, 0, output)
17
+ self.assertIn("project deploy start", trace)
18
+ self.assertIn("CustomObject:AgentWork", trace)
19
+ result = last_json(output)
20
+ self.assertEqual(result["status"], "configured")
21
+ self.assertEqual(result["previous_sharing_model"], "Private")
22
+ self.assertEqual(result["sharing_model"], "Read")
23
+ self.assertEqual(result["external_sharing_model"], "Private")
24
+ self.assertTrue(result["changed"])
25
+
26
+ def test_read_model_is_reused_without_deploy(self):
27
+ with FakeSf() as fake:
28
+ rc, output = fake.run(["org"], {"FAKE_CURRENT_MODEL": "Read"})
29
+ trace = fake.read_state("trace")
30
+
31
+ self.assertEqual(rc, 0, output)
32
+ self.assertNotIn("project deploy start", trace)
33
+ result = last_json(output)
34
+ self.assertEqual(result["status"], "reused")
35
+ self.assertFalse(result["changed"])
36
+
37
+ def test_plan_reports_action_without_deploy(self):
38
+ with FakeSf() as fake:
39
+ rc, output = fake.run(["org", "--plan"])
40
+ trace = fake.read_state("trace")
41
+
42
+ self.assertEqual(rc, 2, output)
43
+ self.assertNotIn("project deploy start", trace)
44
+ result = last_json(output)
45
+ self.assertEqual(result["status"], "action_needed")
46
+ self.assertTrue(result["dry_run"])
47
+
48
+ def test_production_blocks_before_deploy(self):
49
+ with FakeSf() as fake:
50
+ rc, output = fake.run(
51
+ ["org", "--confirm-org-wide-visibility"],
52
+ {"FAKE_PRODUCTION": "1"},
53
+ )
54
+ trace = fake.read_state("trace")
55
+
56
+ self.assertEqual(rc, 1, output)
57
+ self.assertNotIn("project deploy start", trace)
58
+ self.assertIn("production", last_json(output)["blocking_issue"].lower())
59
+
60
+ def test_developer_edition_is_safe_for_confirmed_change(self):
61
+ with FakeSf() as fake:
62
+ rc, output = fake.run(
63
+ ["org", "--confirm-org-wide-visibility"],
64
+ {"FAKE_DEVELOPER_EDITION": "1"},
65
+ )
66
+
67
+ self.assertEqual(rc, 0, output)
68
+ self.assertEqual(last_json(output)["status"], "configured")
69
+
70
+ def test_failed_readback_blocks(self):
71
+ with FakeSf() as fake:
72
+ rc, output = fake.run(
73
+ ["org", "--confirm-org-wide-visibility"],
74
+ {"FAKE_READBACK_MODEL": "Private"},
75
+ )
76
+
77
+ self.assertEqual(rc, 1, output)
78
+ result = last_json(output)
79
+ self.assertEqual(result["status"], "blocked")
80
+ self.assertIn("read-back", result["blocking_issue"])
81
+
82
+ def test_change_requires_explicit_org_wide_confirmation(self):
83
+ with FakeSf() as fake:
84
+ rc, output = fake.run(["org"])
85
+ trace = fake.read_state("trace")
86
+
87
+ self.assertEqual(rc, 1, output)
88
+ self.assertNotIn("project deploy start", trace)
89
+ self.assertIn("confirm-org-wide-visibility", last_json(output)["blocking_issue"])
90
+
91
+
92
+ if __name__ == "__main__":
93
+ unittest.main(verbosity=2)
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: service-omni-base-settings-configure
3
+ description: "Use to enable the five Omni-Channel base settings on a Salesforce org via the Metadata API. The canonical writer scripts/configure-and-report.sh detects, deploys, and re-verifies in one call (run mode) or detects only (plan mode); it is idempotent (deploys only when a toggle is false or missing) and guards writes with a sandbox/CDO/trial/DE safe_to_write check. It always surfaces the UI-only login-behavior gap in its report. Triggers: enable Omni-Channel, turn on Omni base settings, configure OmniChannelSettings, deploy Settings:OmniChannel. Do not use to configure downstream Omni entities (queues, presence, permissions — use the entity-specific skill) or to deploy other Settings types (use platform-metadata-deploy)."
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
+ - "platform-metadata-deploy"
11
+ - "service-omni-channel-setup-coordinate"
12
+ accessCheck:
13
+ - type: license
14
+ value: ServiceCloud
15
+ cliTools:
16
+ - tool: ["jq"]
17
+ semver: ">=1.6"
18
+ - tool: ["sf"]
19
+ semver: ">=2.139.6"
20
+ ---
21
+
22
+ # service-omni-base-settings-configure
23
+
24
+ Enable the five `OmniChannelSettings` booleans (`enableOmniChannel`, `enableOmniAutoLoginPrompt`, `enableOmniSecondaryRoutingPriority`, `enableOmniSkillsRouting`, `enableOmniStatusCapModel`) via a single Metadata API deploy. Nothing downstream — queues, presence statuses, routing flows — works without `enableOmniChannel=true`, so this must be the first write in an Omni setup sequence. The skill probes first and deploys only when a toggle is false or missing.
25
+
26
+ ## Inputs
27
+
28
+ ```bash
29
+ bash scripts/configure-and-report.sh run <org-alias> # detect; deploy if needed; re-verify
30
+ bash scripts/configure-and-report.sh plan <org-alias> # read-only: detect only, never deploys
31
+ ```
32
+
33
+ `org-alias` is the only input. All five toggles are always set to `true` — the skill takes no per-toggle overrides; a caller who wants a subset should use `platform-metadata-deploy` with their own `OmniChannel.settings-meta.xml`.
34
+
35
+ ## Preconditions and safety
36
+
37
+ - Target org authenticated via `sf` CLI (My Domain URL, not `.lightning.force.com`), Service Cloud license present, `sf` CLI ≥ 2.139.6.
38
+ - **Production guardrail:** the writer computes `safe_to_write` as `IsSandbox` OR `TrialExpirationDate != null` OR `OrganizationType` in {Developer Edition, Base Edition}, and blocks with no override when it is false. This permits CDOs (`IsSandbox=false` with a non-null `TrialExpirationDate`); do not weaken it to a bare `IsSandbox` check.
39
+
40
+ ## Run
41
+
42
+ `configure-and-report.sh` is the canonical entry point:
43
+
44
+ - **run** — probe; if all five are already enabled, emit `reused` (no deploy); otherwise run the `safe_to_write` guard, deploy the explicit `assets/force-app/main/default/settings/OmniChannel.settings-meta.xml` source file with all five `true` (a `SucceededPartial` is treated as failure), re-probe, and emit `configured` iff all five are now true, else `blocked`. The explicit source path bypasses source-tracking no-op decisions when the org value has drifted.
45
+ - **plan** — probe only; emit `reused` if all enabled, else `action_needed`. Never deploys.
46
+
47
+ The probe retrieves `Settings:OmniChannel` and takes 1–2 minutes (Metadata retrieve is slow); this is expected. The deploy runs from `assets/` (a valid DX project) and names the settings file with `--source-dir`; its template is never mutated at runtime, so the skill stays reproducible and does not report "No local changes to deploy" solely because local source tracking is clean.
48
+
49
+ ## Behavior
50
+
51
+ **Whole-document writes.** `Settings` is a whole-document metadata type — the Metadata API does not accept partial updates, so the skill always deploys the full file rather than PATCHing individual toggles.
52
+
53
+ **Login-behavior gap.** The Omni login-behavior radio (Setup → Omni-Channel Settings → "Define login behavior when an agent opens a new window/tab") has no public API on v66. The report always surfaces its click-path — even on a no-op `reused` run — so a "nothing to do" result never hides the one manual action. `enableOmniAutoLoginPrompt` still deploys cleanly (other Omni features may depend on it internally) but does not drive that radio.
54
+
55
+ **Fail-closed verify.** After a deploy the skill re-probes and requires all five toggles true; a deploy that reports success but does not stick (an uncommon org-level restriction) blocks rather than reporting success.
56
+
57
+ ## Output contract
58
+
59
+ `configure-and-report.sh` emits a single JSON object with `status` ∈ `configured` | `reused` | `action_needed` | `blocked`, a top-level `all_enabled` boolean the coordinator reads to gate downstream skills, full `before`/`after` toggle objects, `deploy_id`, `safe_to_write`, `manual_actions`, and `blocking_issue`.
60
+
61
+ - `configured` — a toggle was false, the deploy ran, and the re-probe shows all true (run mode).
62
+ - `reused` — all five were already true; no deploy.
63
+ - `action_needed` — plan mode saw a disabled toggle; nothing deployed.
64
+ - `blocked` — `safe_to_write=false`, the deploy failed or was `SucceededPartial`, or the re-probe still shows a false toggle.
65
+
66
+ `manual_actions` always includes the login-behavior gap entry on non-blocked reports; `deploy_id` is `null` for `reused`/`action_needed`; `blocking_issue` names the failure only when `status: blocked`.
67
+
68
+ ## Limitations
69
+
70
+ - Configures only `OmniChannelSettings`, always with all five toggles true — other `Settings` types belong to `platform-metadata-deploy`.
71
+ - Cannot turn the login-behavior radio (no public API on v66) — it only surfaces the click-path.
72
+
73
+ ## References
74
+
75
+ | File | When to read |
76
+ |---|---|
77
+ | `references/api-notes.md` | Before a deploy — the five toggles' semantics, dependencies, and the login-behavior caveat |
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <OmniChannelSettings xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <enableOmniAutoLoginPrompt>true</enableOmniAutoLoginPrompt>
4
+ <enableOmniChannel>true</enableOmniChannel>
5
+ <enableOmniSecondaryRoutingPriority>true</enableOmniSecondaryRoutingPriority>
6
+ <enableOmniSkillsRouting>true</enableOmniSkillsRouting>
7
+ <enableOmniStatusCapModel>true</enableOmniStatusCapModel>
8
+ </OmniChannelSettings>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Package xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <types>
4
+ <members>OmniChannel</members>
5
+ <name>Settings</name>
6
+ </types>
7
+ <version>66.0</version>
8
+ </Package>
@@ -0,0 +1,12 @@
1
+ {
2
+ "packageDirectories": [
3
+ {
4
+ "path": "force-app",
5
+ "default": true
6
+ }
7
+ ],
8
+ "name": "service-omni-base-settings-configure",
9
+ "namespace": "",
10
+ "sfdcLoginUrl": "https://login.salesforce.com",
11
+ "sourceApiVersion": "66.0"
12
+ }
@@ -0,0 +1,121 @@
1
+ # OmniChannelSettings — API notes
2
+
3
+ Load this reference **only when a deploy is actually needed** (i.e., after `detect-existing.sh` returned `all_enabled: false`). It covers the five toggles' semantics, cross-field validators, and the login-behavior UI-only caveat.
4
+
5
+ **Metadata type:** `Settings` with name `OmniChannel` (deployed as `Settings:OmniChannel`).
6
+ **API version verified:** v66 (Summer '26 baseline).
7
+ **Whole-document type:** always deploy the full XML, never PATCH individual toggles.
8
+
9
+ ---
10
+
11
+ ## The five toggles
12
+
13
+ All five ship as `true` in this skill's `assets/force-app/main/default/settings/OmniChannel.settings-meta.xml`. This skill does not accept per-toggle overrides — a user who wants a subset should invoke `platform-metadata-deploy` directly with their own XML.
14
+
15
+ ### 1. `enableOmniChannel` (master switch)
16
+
17
+ **Effect:** Enables the Omni-Channel feature on the org. When `false`, no downstream Omni entity can be created (`ServicePresenceStatus`, `PresenceUserConfig`, `QueueRoutingConfig`, `WorkSkillRouting`, etc. all reject with `INVALID_TYPE`).
18
+
19
+ **Values:** `true` | `false`.
20
+
21
+ **Dependencies:** Must be `true` for any of the other four toggles to have meaningful effect.
22
+
23
+ **Reversibility:** Setting to `false` after enablement is not blocked, but is destructive — existing Omni entities remain but stop routing work. Do not disable in a production-like environment without explicit user acknowledgement.
24
+
25
+ ### 2. `enableOmniSkillsRouting`
26
+
27
+ **Effect:** Enables skills-based routing. Required for:
28
+ - `WorkSkillRouting` rules to fire
29
+ - `routeWork` action in Flow with `routingType=SkillsBased`
30
+ - `Skill` records to influence work-item assignment
31
+
32
+ **Values:** `true` | `false`.
33
+
34
+ **Dependencies:** Requires `enableOmniChannel=true`.
35
+
36
+ ### 3. `enableOmniSecondaryRoutingPriority`
37
+
38
+ **Effect:** Enables the secondary routing priority field on queue routing configurations. Allows two-level priority sorting when multiple work items match the same queue.
39
+
40
+ **Values:** `true` | `false`.
41
+
42
+ **Dependencies:** Requires `enableOmniChannel=true`.
43
+
44
+ ### 4. `enableOmniStatusCapModel`
45
+
46
+ **Effect:** Enables the status-based capacity model on `ServiceChannel`. When true, agents can have different capacity ceilings based on which presence status they are in.
47
+
48
+ **Values:** `true` | `false`.
49
+
50
+ **Dependencies:** Requires `enableOmniChannel=true`.
51
+
52
+ ### 5. `enableOmniAutoLoginPrompt` (⚠ caveat — see below)
53
+
54
+ **Effect (documented):** Should control the Omni-Channel login-prompt behavior when an agent opens a new window/tab.
55
+
56
+ **Effect (observed):** Deploys cleanly and round-trips through retrieve — but does **not** drive the UI radio at *Setup → Omni-Channel Settings → "Define login behavior when an agent using Omni-Channel opens a new window or tab"*. Flipping the value `true`/`false` re-retrieves the same value while the UI radio stays unchanged. The login-behavior radio is not reachable through this metadata field.
57
+
58
+ **Values:** `true` | `false`.
59
+
60
+ **Dependencies:** Requires `enableOmniChannel=true`.
61
+
62
+ **This skill still deploys the toggle as `true`.** The XML deploys cleanly and other Omni features may depend on it internally; not shipping it would be an incomplete base-settings deploy.
63
+
64
+ ---
65
+
66
+ ## Cross-field validators
67
+
68
+ The Metadata API does **not** enforce cross-field validators on `OmniChannelSettings` — every combination of the five booleans is accepted by the deploy. However, functional dependencies exist at runtime:
69
+
70
+ | If this is `true` | Then this must also be `true` (for the feature to actually work) |
71
+ |---|---|
72
+ | `enableOmniSkillsRouting` | `enableOmniChannel` |
73
+ | `enableOmniSecondaryRoutingPriority` | `enableOmniChannel` |
74
+ | `enableOmniStatusCapModel` | `enableOmniChannel` |
75
+ | `enableOmniAutoLoginPrompt` | `enableOmniChannel` |
76
+
77
+ Since this skill sets all five to `true`, the dependencies are automatically satisfied.
78
+
79
+ ---
80
+
81
+ ## Deploy metadata name
82
+
83
+ ```text
84
+ Settings:OmniChannel
85
+ ```
86
+
87
+ Command:
88
+
89
+ ```bash
90
+ cd assets
91
+ sf project deploy start --target-org <org-alias> --metadata "Settings:OmniChannel" --json
92
+ ```text
93
+
94
+ Expected response:
95
+
96
+ ```json
97
+ {
98
+ "status": 0,
99
+ "result": {
100
+ "id": "0AfRZ000001Yhef0AC",
101
+ "status": "Succeeded",
102
+ "success": true,
103
+ "numberComponentsDeployed": 1,
104
+ "numberComponentsTotal": 1
105
+ }
106
+ }
107
+ ```
108
+
109
+ **Failure modes:**
110
+
111
+ - `status: Failed` + `componentFailures[0].problem: "Namespace: unable to deploy ..."` — org does not have Omni-Channel provisioned at all. Rare on Service Cloud orgs; possible on stripped-down trial orgs. Fail the skill; the user must contact Salesforce for org-shape correction.
112
+ - `UNKNOWN_EXCEPTION` during retrieve or deploy — transient server-side error. Retry once before failing.
113
+ - `LightningDomain` error on retrieve/deploy — org was authenticated with the `.lightning.force.com` URL instead of the My Domain URL. Re-authenticate with the My Domain URL. This is an auth bug, not an API issue.
114
+
115
+ ---
116
+
117
+ ## Login-behavior UI-only gap (surface always)
118
+
119
+ The `enableOmniAutoLoginPrompt` toggle does not drive the *Setup → Omni-Channel Settings → "Define login behavior when an agent opens a new window/tab"* three-way radio. The full write-up (root cause, coordinator behavior) lives in the `service-omni-channel-setup-coordinate` skill's gap catalog reference.
120
+
121
+ **Skill behavior:** always include the login-behavior click-path in the report's `manual_actions` array, even for `reused` (no-op) runs. The operator may still need to click the radio manually to select their preferred behavior.