@salesforce/afv-skills 1.55.0 → 1.56.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 (220) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-generate/SKILL.md +8 -0
  3. package/skills/agentforce-generate/references/actions-reference.md +2 -2
  4. package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
  5. package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
  6. package/skills/agentforce-observe/SKILL.md +13 -9
  7. package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
  8. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
  9. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
  10. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
  11. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
  12. package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
  13. package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
  14. package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
  15. package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
  16. package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
  17. package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
  18. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
  19. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
  20. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
  21. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
  22. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
  23. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
  24. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
  25. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
  31. package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
  32. package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
  33. package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
  34. package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
  35. package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
  36. package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
  37. package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
  38. package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
  39. package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
  40. package/skills/dx-org-analyze/SKILL.md +0 -1
  41. package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
  42. package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
  43. package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
  44. package/skills/dx-org-manage/SKILL.md +6 -50
  45. package/skills/dx-org-manage/examples/README.md +10 -16
  46. package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
  47. package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
  48. package/skills/dx-org-shape-manage/SKILL.md +4 -3
  49. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
  50. package/skills/experience-accessibility-validate/SKILL.md +8 -5
  51. package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
  52. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
  53. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
  54. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
  55. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
  56. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
  57. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
  58. package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
  59. package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
  60. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
  61. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
  62. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
  63. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
  64. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
  65. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
  66. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
  67. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
  68. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
  69. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
  70. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
  71. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
  72. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
  73. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
  74. package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
  75. package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
  76. package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
  77. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
  78. package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
  79. package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
  80. package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
  81. package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
  82. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
  83. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
  84. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
  85. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
  86. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
  87. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
  88. package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
  89. package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
  90. package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
  91. package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
  92. package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
  93. package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
  94. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
  95. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
  96. package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
  97. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
  98. package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
  99. package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
  100. package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
  101. package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
  102. package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
  103. package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
  104. package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
  105. package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
  106. package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
  107. package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
  108. package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
  109. package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
  110. package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
  111. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
  112. package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
  113. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
  114. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
  115. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
  116. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
  117. package/skills/mobile-apps-create/SKILL.md +1 -1
  118. package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
  119. package/skills/platform-sandbox-configure/SKILL.md +108 -133
  120. package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
  121. package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
  122. package/skills/platform-widget-generate/SKILL.md +26 -18
  123. package/skills/platform-widget-generate/examples/formulas.json +191 -0
  124. package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
  125. package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
  126. package/skills/sales-call-scoring-configure/SKILL.md +318 -0
  127. package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
  128. package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
  129. package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
  130. package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
  131. package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
  132. package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
  133. package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
  134. package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
  135. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
  136. package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
  137. package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
  138. package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
  139. package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
  140. package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
  141. package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
  142. package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
  143. package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
  144. package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
  145. package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
  146. package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
  147. package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
  148. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
  149. package/skills/service-de-channel-activate/SKILL.md +6 -5
  150. package/skills/service-de-channel-create/SKILL.md +3 -3
  151. package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
  152. package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
  153. package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
  154. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
  155. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
  156. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
  157. package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
  158. package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
  159. package/skills/service-helpagent-coordinate/SKILL.md +3 -2
  160. package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
  161. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
  162. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
  163. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
  164. package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
  165. package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
  166. package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
  167. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
  168. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
  169. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
  170. package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
  171. package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
  172. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
  173. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
  174. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
  175. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
  176. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
  177. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
  178. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
  179. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
  180. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
  181. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
  182. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
  183. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
  184. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
  185. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
  186. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
  187. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
  188. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
  189. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
  190. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
  191. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
  192. package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
  193. package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
  194. package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
  195. package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
  196. package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
  197. package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
  198. package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
  199. package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
  200. package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
  201. package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
  202. package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
  203. package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
  204. package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
  205. package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
  206. package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
  207. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
  208. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
  209. package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
  210. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  211. package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
  212. package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
  213. package/skills/dx-org-manage/references/cli_flags.md +0 -67
  214. package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
  215. package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
  216. package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
  217. package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
  218. package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
  219. /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
  220. /package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/references/api-response-schema.md +0 -0
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ echo "Usage: bash analyze.sh <org-alias> [--routing-model enhanced|legacy] [--max-psrs <1-1000000>]" >&2
6
+ }
7
+
8
+ ROUTING_MODEL="enhanced"
9
+ MAX_PSRS=""
10
+ MAX_SOURCE="reference_default"
11
+ ORG=""
12
+
13
+ emit_blocked() {
14
+ local issue="$1"
15
+ jq -n \
16
+ --arg model "$ROUTING_MODEL" \
17
+ --arg issue "$issue" \
18
+ '{
19
+ skill: "service-omni-channel-limits-analyze",
20
+ status: "blocked",
21
+ routing_model: $model,
22
+ metrics: null,
23
+ caveats: [],
24
+ blocking_issue: $issue
25
+ }'
26
+ }
27
+
28
+ if [ $# -lt 1 ]; then
29
+ usage
30
+ exit 1
31
+ fi
32
+
33
+ ORG="$1"
34
+ shift
35
+
36
+ while [ $# -gt 0 ]; do
37
+ case "$1" in
38
+ --routing-model)
39
+ if [ $# -lt 2 ]; then
40
+ usage
41
+ exit 1
42
+ fi
43
+ ROUTING_MODEL=$(printf '%s' "$2" | tr '[:upper:]' '[:lower:]')
44
+ if [ "$ROUTING_MODEL" != "enhanced" ] && [ "$ROUTING_MODEL" != "legacy" ]; then
45
+ emit_blocked "Unsupported routing model '$2'. Use enhanced or legacy."
46
+ exit 1
47
+ fi
48
+ shift 2
49
+ ;;
50
+ --max-psrs)
51
+ if [ $# -lt 2 ] || ! [[ "$2" =~ ^[0-9]+$ ]] || [ "$2" -lt 1 ] || [ "$2" -gt 1000000 ]; then
52
+ emit_blocked "max-psrs must be an integer from 1 through 1000000."
53
+ exit 1
54
+ fi
55
+ MAX_PSRS="$2"
56
+ MAX_SOURCE="admin_supplied"
57
+ shift 2
58
+ ;;
59
+ --help|-h)
60
+ usage
61
+ exit 0
62
+ ;;
63
+ *)
64
+ usage
65
+ exit 1
66
+ ;;
67
+ esac
68
+ done
69
+
70
+ if [ -z "$MAX_PSRS" ]; then
71
+ if [ "$ROUTING_MODEL" = "enhanced" ]; then
72
+ MAX_PSRS="300000"
73
+ else
74
+ MAX_PSRS="200000"
75
+ fi
76
+ fi
77
+
78
+ if ! FORCE_COLOR=0 sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
79
+ emit_blocked "The target org is not authenticated or cannot be reached. Authenticate it with Salesforce CLI and retry."
80
+ exit 1
81
+ fi
82
+
83
+ WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/omni-channel-limits.XXXXXX")
84
+ trap 'rm -rf "$WORK_DIR"' EXIT
85
+ RAW_OUTPUT="$WORK_DIR/query-output.txt"
86
+ QUERY_JSON="$WORK_DIR/query.json"
87
+ ERROR_OUTPUT="$WORK_DIR/query-error.txt"
88
+ QUERY="SELECT COUNT() FROM PendingServiceRouting"
89
+
90
+ if ! FORCE_COLOR=0 sf data query \
91
+ --query "$QUERY" \
92
+ --target-org "$ORG" \
93
+ --json >"$RAW_OUTPUT" 2>"$ERROR_OUTPUT"; then
94
+ sed -n '/^[[:space:]]*{/,$p' "$RAW_OUTPUT" >"$QUERY_JSON"
95
+ QUERY_ERROR=$(jq -r '.name // .message // empty' "$QUERY_JSON" 2>/dev/null || true)
96
+ if printf '%s' "$QUERY_ERROR" | grep -qiE 'INVALID_TYPE|not supported'; then
97
+ emit_blocked "PendingServiceRouting is unavailable. Confirm Omni-Channel is enabled and the running user can access the object; usage was not treated as zero."
98
+ elif printf '%s' "$QUERY_ERROR" | grep -qiE 'insufficient|permission|access denied|forbidden'; then
99
+ emit_blocked "The PendingServiceRouting query was denied. Run the analysis as a user with permission to view Omni routing records."
100
+ else
101
+ emit_blocked "The PendingServiceRouting query failed. Verify org connectivity, feature availability, and permissions, then retry."
102
+ fi
103
+ exit 1
104
+ fi
105
+
106
+ sed -n '/^[[:space:]]*{/,$p' "$RAW_OUTPUT" >"$QUERY_JSON"
107
+ if ! jq -e '.status == 0 and (.result | type == "object")' "$QUERY_JSON" >/dev/null 2>&1; then
108
+ emit_blocked "The PendingServiceRouting query returned an unsupported response. No usage was inferred."
109
+ exit 1
110
+ fi
111
+
112
+ CURRENT_PSRS=$(jq -r '.result.records[0].expr0 // .result.totalSize // empty' "$QUERY_JSON")
113
+ if ! [[ "$CURRENT_PSRS" =~ ^[0-9]+$ ]]; then
114
+ emit_blocked "The PendingServiceRouting query did not return a valid count. No usage was inferred."
115
+ exit 1
116
+ fi
117
+
118
+ USAGE_PERCENT=$(jq -n \
119
+ --argjson current "$CURRENT_PSRS" \
120
+ --argjson maximum "$MAX_PSRS" \
121
+ '($current * 10000 / $maximum | round) / 100')
122
+
123
+ jq -n \
124
+ --arg model "$ROUTING_MODEL" \
125
+ --argjson current "$CURRENT_PSRS" \
126
+ --argjson maximum "$MAX_PSRS" \
127
+ --argjson usage "$USAGE_PERCENT" \
128
+ --arg max_source "$MAX_SOURCE" \
129
+ '{
130
+ skill: "service-omni-channel-limits-analyze",
131
+ status: "analyzed",
132
+ routing_model: $model,
133
+ metrics: {
134
+ current_pending_service_routings: {
135
+ used: $current,
136
+ limit: $maximum,
137
+ usage_percent: $usage,
138
+ used_source: "live_soql",
139
+ limit_source: $max_source,
140
+ limit_verified_by_api: false,
141
+ usage_is_approximate: true
142
+ },
143
+ pending_service_routing_rate_per_hour: {
144
+ used: null,
145
+ limit: 45000,
146
+ usage_percent: null,
147
+ used_source: "not_available_via_supported_api",
148
+ limit_source: "reference_default",
149
+ limit_verified_by_api: false
150
+ }
151
+ },
152
+ caveats: [
153
+ "The PSR count is a point-in-time live query.",
154
+ "The PSR maximum is not API-verified; it is an administrator-supplied value or a routing-model reference default.",
155
+ "Live hourly usage is not exposed by a supported customer-org API and was not inferred.",
156
+ "No org configuration was changed."
157
+ ],
158
+ blocking_issue: null
159
+ }'
@@ -0,0 +1,87 @@
1
+ """Fake Salesforce CLI harness for the Omni-Channel limits analyzer."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ SKILLS_ROOT = Path(__file__).resolve().parents[3]
12
+ SCRIPT = SKILLS_ROOT / "service-omni-channel-limits-analyze/scripts/analyze.sh"
13
+
14
+ _FAKE_SF = r'''#!/usr/bin/env bash
15
+ command_name="${1:-} ${2:-}"
16
+ case "$command_name" in
17
+ "org display")
18
+ if [ "${FAKE_AUTH:-ok}" = "fail" ]; then
19
+ echo '{"status":1,"message":"not authenticated"}' >&2
20
+ exit 1
21
+ fi
22
+ echo '{"status":0,"result":{}}'
23
+ ;;
24
+ "data query")
25
+ printf '%s\n' "$*" >> "${FAKE_SF_LOG:?}"
26
+ if [ -n "${FAKE_QUERY_BODY:-}" ]; then
27
+ printf '%s\n' "$FAKE_QUERY_BODY"
28
+ else
29
+ printf '%s\n' '{"status":0,"result":{"totalSize":2,"records":[]}}'
30
+ fi
31
+ if [ "${FAKE_QUERY_STATUS:-ok}" = "fail" ]; then
32
+ exit 1
33
+ fi
34
+ ;;
35
+ *)
36
+ echo '{"status":1,"message":"unexpected command"}' >&2
37
+ exit 1
38
+ ;;
39
+ esac
40
+ '''
41
+
42
+
43
+ class FakeSf:
44
+ def __enter__(self):
45
+ self._directory = tempfile.mkdtemp(prefix="omni-limits-fake-sf-")
46
+ self.log_path = Path(self._directory) / "commands.log"
47
+ sf_path = Path(self._directory) / "sf"
48
+ sf_path.write_text(_FAKE_SF)
49
+ sf_path.chmod(0o755)
50
+ return self
51
+
52
+ def run(self, args: list[str], **values):
53
+ env = dict(os.environ)
54
+ env["PATH"] = self._directory + os.pathsep + env.get("PATH", "")
55
+ env["FAKE_SF_LOG"] = str(self.log_path)
56
+ for key, value in values.items():
57
+ env[key] = json.dumps(value) if key == "FAKE_QUERY_BODY" and not isinstance(value, str) else str(value)
58
+ process = subprocess.run(
59
+ ["bash", str(SCRIPT), *args],
60
+ capture_output=True,
61
+ text=True,
62
+ env=env,
63
+ cwd=str(SKILLS_ROOT),
64
+ )
65
+ commands = self.log_path.read_text() if self.log_path.exists() else ""
66
+ return process.returncode, process.stdout + process.stderr, commands
67
+
68
+ def __exit__(self, *_):
69
+ shutil.rmtree(self._directory, ignore_errors=True)
70
+
71
+
72
+ def last_json(output: str) -> dict:
73
+ decoder = json.JSONDecoder()
74
+ parsed = None
75
+ position = 0
76
+ while position < len(output):
77
+ if output[position] == "{":
78
+ try:
79
+ candidate, end = decoder.raw_decode(output, position)
80
+ parsed = candidate
81
+ position = end
82
+ continue
83
+ except json.JSONDecodeError:
84
+ pass
85
+ position += 1
86
+ assert parsed is not None, f"No JSON object found in output:\n{output}"
87
+ return parsed
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import unittest
5
+
6
+ from _bootstrap import FakeSf, last_json
7
+
8
+
9
+ class InputContractTests(unittest.TestCase):
10
+ def test_missing_org_is_rejected(self):
11
+ with FakeSf() as fake:
12
+ return_code, output, commands = fake.run([])
13
+ self.assertEqual(return_code, 1, output)
14
+ self.assertIn("Usage", output)
15
+ self.assertEqual(commands, "")
16
+
17
+ def test_invalid_routing_model_is_blocked_before_query(self):
18
+ with FakeSf() as fake:
19
+ return_code, output, commands = fake.run(["org", "--routing-model", "future"])
20
+ self.assertEqual(return_code, 1, output)
21
+ self.assertEqual(last_json(output)["status"], "blocked")
22
+ self.assertEqual(commands, "")
23
+
24
+ def test_invalid_maximum_is_blocked_before_query(self):
25
+ with FakeSf() as fake:
26
+ return_code, output, commands = fake.run(["org", "--max-psrs", "0"])
27
+ self.assertEqual(return_code, 1, output)
28
+ self.assertIn("1 through 1000000", last_json(output)["blocking_issue"])
29
+ self.assertEqual(commands, "")
30
+
31
+ def test_unauthenticated_org_is_blocked(self):
32
+ with FakeSf() as fake:
33
+ return_code, output, commands = fake.run(["org"], FAKE_AUTH="fail")
34
+ self.assertEqual(return_code, 1, output)
35
+ self.assertEqual(last_json(output)["status"], "blocked")
36
+ self.assertEqual(commands, "")
37
+
38
+
39
+ class LimitsContractTests(unittest.TestCase):
40
+ def test_enhanced_report_uses_live_count_and_reference_default(self):
41
+ with FakeSf() as fake:
42
+ return_code, output, commands = fake.run(["org"])
43
+ self.assertEqual(return_code, 0, output)
44
+ result = last_json(output)
45
+ current = result["metrics"]["current_pending_service_routings"]
46
+ hourly = result["metrics"]["pending_service_routing_rate_per_hour"]
47
+ self.assertEqual(current["used"], 2)
48
+ self.assertEqual(current["limit"], 300000)
49
+ self.assertEqual(current["limit_source"], "reference_default")
50
+ self.assertTrue(current["usage_is_approximate"])
51
+ self.assertIsNone(hourly["used"])
52
+ self.assertIsNone(hourly["usage_percent"])
53
+ self.assertIn("SELECT COUNT() FROM PendingServiceRouting", commands)
54
+ self.assertNotRegex(commands, r"\b(create|update|delete|deploy)\b")
55
+
56
+ def test_legacy_model_selects_legacy_reference_default(self):
57
+ with FakeSf() as fake:
58
+ return_code, output, _ = fake.run(["org", "--routing-model", "legacy"])
59
+ self.assertEqual(return_code, 0, output)
60
+ result = last_json(output)
61
+ self.assertEqual(result["routing_model"], "legacy")
62
+ self.assertEqual(result["metrics"]["current_pending_service_routings"]["limit"], 200000)
63
+
64
+ def test_admin_maximum_is_labeled_and_not_api_verified(self):
65
+ with FakeSf() as fake:
66
+ return_code, output, _ = fake.run(["org", "--max-psrs", "500000"])
67
+ self.assertEqual(return_code, 0, output)
68
+ metric = last_json(output)["metrics"]["current_pending_service_routings"]
69
+ self.assertEqual(metric["limit"], 500000)
70
+ self.assertEqual(metric["limit_source"], "admin_supplied")
71
+ self.assertFalse(metric["limit_verified_by_api"])
72
+
73
+ def test_aggregate_expr0_shape_is_supported(self):
74
+ body = {"status": 0, "result": {"totalSize": 1, "records": [{"expr0": 17}]}}
75
+ with FakeSf() as fake:
76
+ return_code, output, _ = fake.run(["org"], FAKE_QUERY_BODY=body)
77
+ self.assertEqual(return_code, 0, output)
78
+ used = last_json(output)["metrics"]["current_pending_service_routings"]["used"]
79
+ self.assertEqual(used, 17)
80
+
81
+ def test_unavailable_object_is_blocked_not_zero(self):
82
+ body = {"status": 1, "name": "INVALID_TYPE", "message": "sObject type is not supported"}
83
+ with FakeSf() as fake:
84
+ return_code, output, _ = fake.run(["org"], FAKE_QUERY_STATUS="fail", FAKE_QUERY_BODY=body)
85
+ self.assertEqual(return_code, 1, output)
86
+ result = last_json(output)
87
+ self.assertEqual(result["status"], "blocked")
88
+ self.assertIsNone(result["metrics"])
89
+ self.assertIn("usage was not treated as zero", result["blocking_issue"])
90
+
91
+ def test_malformed_success_is_blocked(self):
92
+ with FakeSf() as fake:
93
+ return_code, output, _ = fake.run(["org"], FAKE_QUERY_BODY={"status": 0, "result": {}})
94
+ self.assertEqual(return_code, 1, output)
95
+ self.assertEqual(last_json(output)["status"], "blocked")
96
+
97
+
98
+ if __name__ == "__main__":
99
+ unittest.main(verbosity=2)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: service-omni-channel-setup-coordinate
3
- description: "Use to stand up Omni-Channel setup headlessly on Salesforce: base settings, agent users, service channels, routing configs, queues (create and align) and members, presence statuses, permission assignments, the classic Omni Supervisor config plus its action/tab surface, and — for Case and VoiceCall — the record-triggered routing flow. Supports Case, Incident, MessagingSession, and VoiceCall targets; reuses existing records and creates what is missing. Opt-in extras: presence user config, Omni sidebar, skills-based routing, and field-based WorkSkillRouting; also reports (read-only) whether the org is on the classic supervisor surface or Command Center V2. Triggers: set up Omni-Channel, configure Omni routing, provision Omni agents or supervisors, configure Case/Incident/Messaging/Voice routing. Do not use for Agentforce agents or bots, a single existing Omni field change, bare metadata deployment (use platform-metadata-deploy), or to enable Command Center V2 (not writable via the Metadata API)."
3
+ description: "Use to stand up Omni-Channel headlessly: base settings, users, service channels, routing configs, queues and members, presence statuses, permissions, supervisor configuration, and Case or VoiceCall routing flows. Reuses existing records and creates missing ones. Optional stages cover Command Center V2 on supported releases, agent experience, and skills-based routing. TRIGGER when: set up Omni-Channel, configure Omni routing, provision Omni agents, provision Omni supervisors, or configure Case, Incident, MessagingSession, or VoiceCall routing. Do not use for Agentforce bots, one existing field change, or bare metadata deployment."
4
4
  allowed-tools: Bash Read Write Edit Glob Grep AskUserQuestion TodoWrite
5
5
  metadata:
6
6
  version: "1.0"
@@ -11,6 +11,7 @@ metadata:
11
11
  - "service-agentforce-human-escalation-configure"
12
12
  - "service-omni-agent-users-create"
13
13
  - "service-omni-base-settings-configure"
14
+ - "service-omni-channel-inventory-analyze"
14
15
  - "service-omni-command-center-analyze"
15
16
  - "service-omni-permission-set-assign"
16
17
  - "service-omni-presence-status-deploy"
@@ -79,20 +80,22 @@ bash scripts/integration-driver.sh --run <org-alias> [agent_count] [routing_tar
79
80
  - `Incident` targets require the Incident Management feature enabled; `MessagingSession` requires Enhanced Messaging; `VoiceCall` requires Service Cloud Voice. The readiness check hard-fails with a click-path when a requested target sObject is absent.
80
81
  - **Production guardrail:** `safe_to_write` is `true` iff any of `IsSandbox = true`, `TrialExpirationDate != null`, or `OrganizationType IN ('Developer Edition', 'Base Edition')`. The run hard-refuses when it is false. CDOs return `IsSandbox=false` with a non-null `TrialExpirationDate` and must be permitted, so `IsSandbox` alone is never the guard.
81
82
 
82
- Readiness check (silent, mandatory, before any leaf runs):
83
+ Readiness check (mandatory, before any write leaf runs):
83
84
 
84
85
  1. `sf org display` → confirm the org alias is authenticated; refuse (exit 1) if not.
85
86
  2. `Organization` (`IsSandbox`, `TrialExpirationDate`, `OrganizationType`) → compute `safe_to_write`; refuse (exit 1) if false.
86
- 3. Preflight entity availability → for each **non-`Case`** routing target, confirm the sObject exists via `EntityDefinition`; a missing target hard-fails with a feature-enablement click-path (exit 2). `Case` is assumed present and skipped.
87
+ 3. `service-omni-channel-inventory-analyze` → capture the complete channel/routing baseline through the approved read-only aggregate operation; a blocked inventory prevents writes because the coordinator cannot safely establish the starting state.
88
+ 4. Preflight entity availability → for each **non-`Case`** routing target, confirm the sObject exists via `EntityDefinition`; a missing target hard-fails with a feature-enablement click-path (exit 2). `Case` is assumed present and skipped.
87
89
 
88
90
  Omni-Channel base-settings enablement is not a separate upfront probe — it is detected by the base-settings leaf (step 1), and downstream Omni-dependent leaves are deferred as pending in `--plan` until it is on. The driver does not pre-check `UserLicense` headroom or `ServicePresenceStatus`/`QueueRoutingConfig` availability in this readiness pass.
89
91
 
90
92
  ## Skills this coordinates
91
93
 
92
- The coordinator delegates every write to a leaf skill. sObject-specific leaves (3, 5, 6, 6c, 7) loop once per routing target; the rest run once. For SkillsBased routing, steps 6a and 6b complete before any trigger flow is activated. Step 10a (`command-center-analyze`) is a read-only advisory and always runs. Step 11a (`supervisor-surface-deploy`) always runs after the supervisor config succeeds. Leaves 12–13 are the opt-in rep-experience stages (`OMNI_REP_EXPERIENCE=1`).
94
+ The coordinator delegates every write to a leaf skill. sObject-specific leaves (3, 5, 6, 6c, 7) loop once per routing target; the rest run once. For SkillsBased routing, steps 6a and 6b complete before any trigger flow is activated. Step 10a (`command-center-configure`) is opt-in through `OMNI_COMMAND_CENTER_V2=1`; step 10b (`command-center-analyze`) is a read-only advisory and always runs. Step 11a (`supervisor-surface-deploy`) always runs after the supervisor config succeeds. Leaves 12–13 are the opt-in rep-experience stages (`OMNI_REP_EXPERIENCE=1`).
93
95
 
94
96
  | # | Child skill | Role |
95
97
  |---|---|---|
98
+ | 0 | `service-omni-channel-inventory-analyze` | Capture the read-only cross-channel and routing baseline before any writes |
96
99
  | 1 | `service-omni-base-settings-configure` | Deploy `Settings:OmniChannel` (5 toggles) |
97
100
  | 2 | `service-omni-agent-users-create` | Provision the requested agent users |
98
101
  | 3 | `service-omni-service-channel-configure` | Reuse the standard `ServiceChannel` for the target sObject, or deploy the canonical XML |
@@ -106,7 +109,8 @@ The coordinator delegates every write to a leaf skill. sObject-specific leaves (
106
109
  | 8 | `service-omni-permission-set-assign` | Assign the `Omni_Agent` PermissionSet to agent users; self-heals the bundled metadata when absent (run mode only) |
107
110
  | 9 | `service-omni-supervisor-users-create` | Provision supervisor users |
108
111
  | 10 | `service-omni-supervisor-permset-assign` | Assign the standard `ContactCenterSupervisor` PermissionSet to supervisor users |
109
- | 10a | `service-omni-command-center-analyze` | **Read-only advisory** — report whether the org is on the classic supervisor surface or Command Center V2; never writes, never hard-blocks |
112
+ | 10a | `service-omni-command-center-configure` | *(opt-in)* Enable Command Center V2 through preserved `OmniChannel.settings` and verify its seeded page and tab |
113
+ | 10b | `service-omni-command-center-analyze` | **Read-only advisory** — report whether the org is on the classic supervisor surface or Command Center V2; never writes, never hard-blocks |
110
114
  | 11 | `service-omni-supervisor-config-deploy` | Deploy `OmniSupervisorConfig` binding supervisor users to the queues discovered in step 5 |
111
115
  | 11a | `service-omni-supervisor-surface-deploy` | Populate the supervisor **action + tab surface** (`OmniSupervisorConfigAction` / `OmniSupervisorConfigTab`) on the config from step 11 |
112
116
  | 12 | `service-omni-presence-user-config-deploy` | *(opt-in)* Deploy `PresenceUserConfig` + `PresenceDeclineReason` (decline + ACW) and assign the provisioned agents |
@@ -119,7 +123,7 @@ The coordinator delegates every write to a leaf skill. sObject-specific leaves (
119
123
 
120
124
  **Ordering and gating.** Leaves run in dependency order. Base settings are a hard prerequisite for every write below. For SkillsBased routing, agent `SkillUser` bindings (6a) and the `WorkSkillRouting` rule (6b) must succeed before the routing-flow leaf (6c) can activate the trigger. The flow also requires the queue (5) and its members (6), and it round-trips `FlowDefinitionView.ActiveVersionId` to prove the flow is Active. A required runtime proof does not pass merely because a PSR exists: SkillsBased requires at least one `SkillRequirement` on that PSR.
121
125
 
122
- **Supervisor path.** Supervisor users (9) use the same Anonymous Apex `System.setPassword` model as agent users. Because a password literal can be captured in an `ApexLog` when a debug TraceFlag is active, both user-create leaves fail closed *before* the first `System.setPassword`: they prove via a SOQL-filtered Tooling API query (`ExpirationDate > now`) that no active TraceFlag exists for the running user, and if that cannot be positively proven they set no password at all (users left ACTIVE and `reset_required`, with a `security_warning`). They never delete logs, so unrelated audit logs are untouched. The supervisor permset leaf (10) assigns the standard `ContactCenterSupervisor` — never the custom `Omni_Supervisor`, whose license-gated permissions fail assignment — and a contract test asserts this wiring. After the supervisor permset, a read-only Command Center analysis (10a) reports the org's supervisor surface (classic vs Command Center V2); it never writes and never hard-blocks (ambiguous detection is yellow, a clean read green). Once the config lands (11), the surface leaf (11a) inserts the standard action + tab companions (`OmniSupervisorConfigAction` / `OmniSupervisorConfigTab`) against the config's Id — plain Data API rows, so no metadata-file redeploy — and is idempotent (only missing types are inserted). It runs only after the config succeeds; a skipped or failed config skips the surface with a dependency note.
126
+ **Supervisor path.** Supervisor users (9) use the same Anonymous Apex `System.setPassword` model as agent users. Because a password literal can be captured in an `ApexLog` when a debug TraceFlag is active, both user-create leaves fail closed *before* the first `System.setPassword`: they prove via a SOQL-filtered Tooling API query (`ExpirationDate > now`) that no active TraceFlag exists for the running user, and if that cannot be positively proven they set no password at all (users left ACTIVE and `reset_required`, with a `security_warning`). They never delete logs, so unrelated audit logs are untouched. The supervisor permset leaf (10) assigns the standard `ContactCenterSupervisor` — never the custom `Omni_Supervisor`, whose license-gated permissions fail assignment — and a contract test asserts this wiring. With `OMNI_COMMAND_CENTER_V2=1`, the coordinator enables and verifies the V2 org preference through `service-omni-command-center-configure`; optional `OMNI_COMMAND_CENTER_*` booleans select its subordinate controls. A read-only Command Center analysis then reports the resulting supervisor surface and never hard-blocks (ambiguous detection is yellow, a clean read green). Once the classic config lands (11), the surface leaf (11a) inserts the standard action + tab companions (`OmniSupervisorConfigAction` / `OmniSupervisorConfigTab`) against the config's Id — plain Data API rows, so no metadata-file redeploy — and is idempotent (only missing types are inserted). It runs only after the config succeeds; a skipped or failed config skips the surface with a dependency note.
123
127
 
124
128
  **Optional rep-experience stages.** With `OMNI_REP_EXPERIENCE=1`, presence user config (12) deploys a validator-safe `PresenceUserConfig` with its decline reason and assigns every resolved agent username, including reused users. Sidebar (13) pins the Omni utility region on `OMNI_CONSOLE_APP`, or auto-detects a single console app. SkillsBased routing is not a cosmetic rep-experience stage: when requested, its SkillUser and WorkSkillRouting prerequisites run before flow activation.
125
129
 
@@ -142,23 +146,11 @@ Report content rules:
142
146
  ## Limitations
143
147
 
144
148
  - Routing-flow deploy covers `Case` and `VoiceCall` only.
145
- - Classic `OmniSupervisorConfig` (including its action + tab surface) only. Command Center V2 (Enhanced Omni-Channel) is reported by the read-only analysis step but not enabled — the org preference is not writable via the Metadata API. The surface leaf populates only the reference-free standard action/tab types; custom-action, FlexiPage, AWS-dashboard, and AI-agent surfaces need an external reference and must be set up in Setup.
149
+ - Classic `OmniSupervisorConfig`, including its standard action and tab surface, is supported. Command Center V2 is an explicit opt-in (`OMNI_COMMAND_CENTER_V2=1`) and runs only when the org's Metadata API exposes the V2 settings; otherwise the configure leaf fails closed. Custom actions, FlexiPages, AWS dashboards, and AI-agent surfaces still need external references and separate setup.
146
150
  - The rep-experience stages (presence user config, decline reasons, sidebar) run only with `OMNI_REP_EXPERIENCE=1`. SkillsBased routing is selected explicitly or inferred from a complete `OMNI_WSR_*` mapping and runs independently of that flag.
147
151
  - Teardown is **plan-only**: `--run` emits `deploy_ids.json` (a provenance manifest) and a dry-run `teardown.sh` that prints the reverse-order removal/restore plan, but does not execute destructive changes. Automated reversal is deliberately deferred until every leaf emits a normalized provenance block and the reversal is validated live — metadata-deployed components need destructiveChanges deploys and adopted records must never be deleted. Reversal today is a manual operator task guided by the plan and the per-skill artifacts.
148
152
 
149
- ### Use-case coverage (current)
150
-
151
- | Use case | Status | What is / isn't headless |
152
- | --- | --- | --- |
153
- | Case routing | Ready | Full create-and-bind thread (queue + QRC + members + routing flow), idempotent; runtime `PendingServiceRouting` proof optional. |
154
- | VoiceCall / AFCC routing | Conditional | QRC + queue + members + routing flow are deployable, but require a **provisioned Amazon/AFCC contact center** and a live Voice runtime to prove end-to-end. Contact-center provisioning is the caller's responsibility. |
155
- | Open CTI → AFCC migration | Partial | The coordinator creates the Omni routing resources; it does not provision or migrate the contact center itself. |
156
- | Incident routing | Partial | Service channel + presence are supported; the coordinator **verifies/adopts** Incident routing but does not create a missing queue/QRC/flow for it. |
157
- | Generic MessagingSession routing | Partial | Service channel + presence supported; full create-and-bind is delegated to `service-agentforce-human-escalation-configure`, not run generically here. |
158
- | Classic Omni Supervisor | Ready | Users, permissions, config, queues/users, and the action/tab surface companions are all implemented. |
159
- | Command Center V2 | Detection only | The read-only analysis step reports classic vs V2; the org preference `CommandCenterForServiceV2` is **not writable via the Metadata API** (tracked: W-23827287) and must be enabled in Setup. |
160
- | Supervisor monitoring OrgValues | Not implemented | `OmniSuperConvMonitor`, `OmniSuperAgentSneakpeek`, `OmniSuperClientSneakpeek`, `OmniSuperWhisper`, `OmniSuperAdminSkillQueue` are OrgValues with no proven supported external write API. |
161
- | Omni Inbox | Not implemented | The sidebar leaf pins the Omni sidebar (`isOmniPinnedViewEnabled`); it does not create or configure Omni Inbox, a console app, or utility items. |
153
+ Detailed coverage and known headless gaps are maintained in `references/gap-catalog.md`. In short, Case routing is fully coordinated; VoiceCall needs a provisioned voice runtime; Incident and generic MessagingSession routing are adopted rather than created; and Omni Inbox or external supervisor surfaces remain outside this workflow.
162
154
 
163
155
  ## References
164
156
 
@@ -68,6 +68,25 @@ SUPERVISOR_COUNT="${4:-1}"
68
68
  AGENT_PROFILE="${OMNI_AGENT_PROFILE:-}"
69
69
  SUPERVISOR_PROFILE="${OMNI_SUPERVISOR_PROFILE:-}"
70
70
  SUPERVISOR_CONFIG_DN="${OMNI_SUPERVISOR_CONFIG_DEVELOPER_NAME:-Omni_Supervisor}"
71
+ COMMAND_CENTER_V2="${OMNI_COMMAND_CENTER_V2:-0}"
72
+
73
+ case "$COMMAND_CENTER_V2" in
74
+ 0|1) ;;
75
+ *) echo "{\"error\":\"Invalid OMNI_COMMAND_CENTER_V2 '$COMMAND_CENTER_V2'. Supported: 0 or 1.\"}" >&2; exit 2 ;;
76
+ esac
77
+
78
+ for command_center_setting in \
79
+ OMNI_COMMAND_CENTER_CONVERSATION_MONITORING \
80
+ OMNI_COMMAND_CENTER_AGENT_SNEAK_PEEK \
81
+ OMNI_COMMAND_CENTER_CUSTOMER_SNEAK_PEEK \
82
+ OMNI_COMMAND_CENTER_WHISPER_MESSAGING \
83
+ OMNI_COMMAND_CENTER_QUEUES_AND_SKILLS; do
84
+ command_center_value="${!command_center_setting:-}"
85
+ if [ -n "$command_center_value" ] && [ "$command_center_value" != "true" ] && [ "$command_center_value" != "false" ]; then
86
+ echo "{\"error\":\"Invalid $command_center_setting '$command_center_value'. Supported: true or false.\"}" >&2
87
+ exit 2
88
+ fi
89
+ done
71
90
 
72
91
  # A complete WorkSkillRouting mapping is an unambiguous SBR request. Infer SkillsBased so callers do
73
92
  # not need a hidden second switch; an explicit OMNI_ROUTING_TYPE still wins.
@@ -544,7 +563,7 @@ run_skill() {
544
563
  # configured/bound/assigned/ok/success/skipped.
545
564
  local color
546
565
  case "$status" in
547
- reused|updated|created|deployed|configured|bound|assigned|ok|success|skipped)
566
+ analyzed|reused|updated|created|deployed|configured|bound|assigned|ok|success|skipped)
548
567
  color="green"
549
568
  ;;
550
569
  action_needed|partial|unsupported_v1|not_evaluated)
@@ -611,6 +630,16 @@ run_skill() {
611
630
  return 0
612
631
  }
613
632
 
633
+ # ---- Read-only cross-channel baseline ----
634
+ # A blocked inventory means the coordinator cannot establish an authoritative starting state, so no
635
+ # write leaf is allowed to run.
636
+ run_skill "service-omni-channel-inventory-analyze" \
637
+ "service-omni-channel-inventory-analyze" \
638
+ "Omni channel and routing inventory (read-only preflight)" \
639
+ '.status' \
640
+ analyze.sh "$ORG" \
641
+ || exit 1
642
+
614
643
  # ---- Base settings ----
615
644
  # Plan detects; run deploys the 5 OmniChannelSettings toggles + re-verifies. Hard prerequisite for
616
645
  # every write skill below (INVALID_TYPE when Omni is off), so a red here fails fast.
@@ -1247,11 +1276,32 @@ else
1247
1276
  || true
1248
1277
  fi
1249
1278
 
1279
+ # ---- Command Center V2 configuration (explicit opt-in) ----
1280
+ if [ "$COMMAND_CENTER_V2" = "1" ]; then
1281
+ if [ "$MODE" = "run" ]; then
1282
+ COMMAND_CENTER_ARGS=(run "$ORG" --apply)
1283
+ else
1284
+ COMMAND_CENTER_ARGS=(plan "$ORG")
1285
+ fi
1286
+
1287
+ [ -z "${OMNI_COMMAND_CENTER_CONVERSATION_MONITORING:-}" ] || COMMAND_CENTER_ARGS+=(--conversation-monitoring "$OMNI_COMMAND_CENTER_CONVERSATION_MONITORING")
1288
+ [ -z "${OMNI_COMMAND_CENTER_AGENT_SNEAK_PEEK:-}" ] || COMMAND_CENTER_ARGS+=(--agent-sneak-peek "$OMNI_COMMAND_CENTER_AGENT_SNEAK_PEEK")
1289
+ [ -z "${OMNI_COMMAND_CENTER_CUSTOMER_SNEAK_PEEK:-}" ] || COMMAND_CENTER_ARGS+=(--customer-sneak-peek "$OMNI_COMMAND_CENTER_CUSTOMER_SNEAK_PEEK")
1290
+ [ -z "${OMNI_COMMAND_CENTER_WHISPER_MESSAGING:-}" ] || COMMAND_CENTER_ARGS+=(--whisper-messaging "$OMNI_COMMAND_CENTER_WHISPER_MESSAGING")
1291
+ [ -z "${OMNI_COMMAND_CENTER_QUEUES_AND_SKILLS:-}" ] || COMMAND_CENTER_ARGS+=(--queues-and-skills "$OMNI_COMMAND_CENTER_QUEUES_AND_SKILLS")
1292
+
1293
+ run_skill "service-omni-command-center-configure" \
1294
+ "service-omni-command-center-configure" \
1295
+ "Command Center for Service V2 (opt-in Metadata API configuration)" \
1296
+ '.status' \
1297
+ configure-and-report.sh "${COMMAND_CENTER_ARGS[@]}" \
1298
+ || true
1299
+ fi
1300
+
1250
1301
  # ---- Command Center surface analysis (read-only advisory) ----
1251
- # Reports whether the org is on the classic Omni Supervisor surface or Command Center V2 (Enhanced
1252
- # Omni-Channel). Advisory only: this coordinator configures the classic surface; V2 enablement is an
1253
- # org-preference flip outside the Metadata API. It never writes, so it runs identically in plan and
1254
- # run and never hard-blocks - ambiguous detection surfaces as yellow, a clean read as green.
1302
+ # Reports whether the org is on the classic Omni Supervisor surface or Command Center V2 after any
1303
+ # requested V2 configuration. It never writes and never hard-blocks: ambiguous detection surfaces
1304
+ # as yellow, while a clean read is green.
1255
1305
  run_skill "service-omni-command-center-analyze" \
1256
1306
  "service-omni-command-center-analyze" \
1257
1307
  "Command Center surface analysis (classic vs V2, read-only)" \
@@ -586,6 +586,26 @@ class SkillsBasedRoutingFlowTests(unittest.TestCase):
586
586
  return _run(_FLOW_SCRIPT, args, path_prefix=fake_dir, extra_env=merged)
587
587
 
588
588
 
589
+ class CoordinatorInventoryPreflightTests(unittest.TestCase):
590
+ """The authoritative cross-channel inventory must run before the first write leaf."""
591
+
592
+ DRIVER = SKILLS_ROOT / "service-omni-channel-setup-coordinate" / "scripts" / "integration-driver.sh"
593
+
594
+ def test_inventory_runs_before_base_settings(self):
595
+ source = self.DRIVER.read_text()
596
+ inventory_index = source.index('run_skill "service-omni-channel-inventory-analyze"')
597
+ base_index = source.index('run_skill "service-omni-base-settings-configure"')
598
+ self.assertLess(inventory_index, base_index)
599
+
600
+ def test_inventory_failure_stops_the_driver(self):
601
+ source = self.DRIVER.read_text()
602
+ invocation = re.search(
603
+ r'run_skill "service-omni-channel-inventory-analyze"[\s\S]+?\|\| exit 1',
604
+ source,
605
+ )
606
+ self.assertIsNotNone(invocation, "blocked inventory must stop the coordinator before writes")
607
+
608
+
589
609
  class VoiceMembershipContractTests(unittest.TestCase):
590
610
  """(7) Explicit real-agent membership contract for service-omni-queue-members-assign."""
591
611
 
@@ -765,6 +785,24 @@ class CoordinatorSupervisorSurfaceTests(unittest.TestCase):
765
785
  self.src, r'(?m)^run_skill "service-omni-command-center-analyze"',
766
786
  "command-center-analyze must be invoked top-level, outside the OMNI_REP_EXPERIENCE gate")
767
787
 
788
+ def test_command_center_v2_configuration_is_explicit_and_precedes_analysis(self):
789
+ self.assertIn('COMMAND_CENTER_V2="${OMNI_COMMAND_CENTER_V2:-0}"', self.src)
790
+ self.assertIn('run_skill "service-omni-command-center-configure"', self.src)
791
+ configure_at = self.src.index('run_skill "service-omni-command-center-configure"')
792
+ analyze_at = self.src.index('run_skill "service-omni-command-center-analyze"')
793
+ self.assertLess(configure_at, analyze_at)
794
+ self.assertIn('COMMAND_CENTER_ARGS=(run "$ORG" --apply)', self.src)
795
+
796
+ def test_command_center_control_inputs_are_forwarded(self):
797
+ for option in (
798
+ "--conversation-monitoring",
799
+ "--agent-sneak-peek",
800
+ "--customer-sneak-peek",
801
+ "--whisper-messaging",
802
+ "--queues-and-skills",
803
+ ):
804
+ self.assertIn(option, self.src)
805
+
768
806
  def test_supervisor_surface_invoked_after_config(self):
769
807
  self.assertIn("service-omni-supervisor-surface-deploy", self.src,
770
808
  "coordinator never invokes supervisor-surface-deploy")
@@ -60,7 +60,7 @@ bash scripts/analyze.sh <org-alias> [supervisor_username_or_id]
60
60
  | `v2_ready` | Capability + seed present (and supervisor has permission, if checked) | verify only (coordinator proceeds to V2 verification) |
61
61
  | `v2_permission_missing` | V2 enabled but the named supervisor lacks `CommandCenterForServiceUser` | Manual — assign `CommandCenterForServiceUser` (headless-capable via PermissionSet; packaging pending) |
62
62
  | `v2_seed_incomplete` | Capability present but the FlexiPage/tab provisioning is inconsistent | Manual — re-check in Setup → Omni-Channel → Supervisor Settings (no supported public write API) |
63
- | `v2_available_not_enabled` | Org supports V2 but the preference is off | Manual — enable in Setup → Omni-Channel → Supervisor Settings (no supported Metadata/Tooling write API) |
63
+ | `v2_available_not_enabled` | Org supports V2 but the preference is off | `service-omni-command-center-configure` when the W-24039822 Metadata API contract is available; otherwise enable manually |
64
64
  | `legacy_selected` | V2 capability absent | `service-omni-supervisor-config-deploy` |
65
65
  | `ambiguous` | A required signal could not be read through a supported API | none — **blocks**, do not guess |
66
66
 
@@ -72,7 +72,7 @@ A single JSON object: `status` (`detected` | `blocked`), `state` (one of the six
72
72
 
73
73
  ## Limitations
74
74
 
75
- - The `CommandCenterForServiceV2` org preference has no supported public read; its state is **inferred** from the seeded FlexiPage (the platform seeds on the ON-flip). This is called out in `signals` and is why a bare capability-without-seed reads as `v2_available_not_enabled`.
75
+ - The analyzer keeps using the seeded FlexiPage as its cross-release read signal. On releases containing W-24039822, `service-omni-command-center-configure` can write and re-read the preference through `Settings:OmniChannel`.
76
76
  - The release gater cannot be read directly; capability is inferred from the presence of the user-permission column on the org schema.
77
77
  - Tab detection is best-effort; when the tab query cannot run, `v2_tab_present` is `"unknown"` and state is derived from the remaining signals.
78
78
  - Read-only: it never enables V2, seeds pages, assigns permissions, or deploys config.
@@ -142,8 +142,8 @@ else
142
142
  REC_SKILL="null"
143
143
  else
144
144
  STATE="v2_available_not_enabled"
145
- REC_ACTION="Org supports V2 but the CommandCenterForServiceV2 preference is off (no seeded page). Enable it manually in Setup → Omni-Channel → Supervisor Settings; there is no supported Metadata/Tooling write API for this preference."
146
- REC_SKILL="null"
145
+ REC_ACTION="Org supports V2 but the CommandCenterForServiceV2 preference is off (no seeded page). Use service-omni-command-center-configure when the W-24039822 Metadata API contract is available; otherwise enable it manually in Setup → Omni-Channel → Supervisor Settings."
146
+ REC_SKILL='"service-omni-command-center-configure"'
147
147
  fi
148
148
  fi
149
149
 
@@ -153,7 +153,7 @@ case "$STATE" in
153
153
  v2_permission_missing)
154
154
  MANUAL_ACTIONS=$(jq -n '[{id:"ASSIGN_V2_PERM",title:"Assign the CommandCenterForServiceUser permission to the supervisor (headless-capable via PermissionSet; packaging pending)",target_skill:null}]') ;;
155
155
  v2_available_not_enabled|v2_seed_incomplete)
156
- MANUAL_ACTIONS=$(jq -n '[{id:"ENABLE_V2_PREF",title:"Enable Command Center for Service V2 in Setup → Omni-Channel → Supervisor Settings (no supported public write API; manual)",target_skill:null}]') ;;
156
+ MANUAL_ACTIONS=$(jq -n '[{id:"ENABLE_V2_PREF",title:"Enable Command Center for Service V2 with service-omni-command-center-configure when the W-24039822 API field is available; otherwise use Setup → Omni-Channel → Supervisor Settings",target_skill:"service-omni-command-center-configure"}]') ;;
157
157
  esac
158
158
 
159
159
  # Emit
@@ -1,9 +0,0 @@
1
- {
2
- "status": 1,
3
- "name": "DuplicateValue",
4
- "message": "A snapshot named 'MySnapshot' already exists in this Dev Hub. Use a different, unique name.",
5
- "exitCode": 1,
6
- "commandName": "SnapshotCreate",
7
- "stack": "DuplicateValue: A snapshot named 'MySnapshot' already exists in this Dev Hub...",
8
- "warnings": []
9
- }