@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.
- package/package.json +1 -1
- package/skills/agentforce-generate/SKILL.md +8 -0
- package/skills/agentforce-generate/references/actions-reference.md +2 -2
- package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
- package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
- package/skills/agentforce-observe/SKILL.md +13 -9
- package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
- package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
- package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
- package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
- package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
- package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
- package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
- package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
- package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
- package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
- package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
- package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
- package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
- package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
- package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
- package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
- package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
- package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
- package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
- package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
- package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
- package/skills/dx-org-analyze/SKILL.md +0 -1
- package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
- package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
- package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
- package/skills/dx-org-manage/SKILL.md +6 -50
- package/skills/dx-org-manage/examples/README.md +10 -16
- package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
- package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
- package/skills/dx-org-shape-manage/SKILL.md +4 -3
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
- package/skills/experience-accessibility-validate/SKILL.md +8 -5
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
- package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
- package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
- package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
- package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
- package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
- package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
- package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
- package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
- package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
- package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
- package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
- package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
- package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
- package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
- package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
- package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
- package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
- package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
- package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
- package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
- package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
- package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
- package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
- package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
- package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
- package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
- package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
- package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
- package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
- package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
- package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
- package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
- package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
- package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
- package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
- package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
- package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
- package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
- package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
- package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
- package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
- package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
- package/skills/mobile-apps-create/SKILL.md +1 -1
- package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
- package/skills/platform-sandbox-configure/SKILL.md +108 -133
- package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
- package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
- package/skills/platform-widget-generate/SKILL.md +26 -18
- package/skills/platform-widget-generate/examples/formulas.json +191 -0
- package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
- package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
- package/skills/sales-call-scoring-configure/SKILL.md +318 -0
- package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
- package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
- package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
- package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
- package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
- package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
- package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
- package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
- package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
- package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
- package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
- package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
- package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
- package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
- package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
- package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
- package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
- package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
- package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
- package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
- package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
- package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
- package/skills/service-de-channel-activate/SKILL.md +6 -5
- package/skills/service-de-channel-create/SKILL.md +3 -3
- package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
- package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
- package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
- package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
- package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
- package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
- package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
- package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
- package/skills/service-helpagent-coordinate/SKILL.md +3 -2
- package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
- package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
- package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
- package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
- package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
- package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
- package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
- package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
- package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
- package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
- package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
- package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
- package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
- package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
- package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
- package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
- package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
- package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
- package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
- package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
- package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
- package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
- package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
- package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
- package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
- package/skills/dx-org-manage/references/cli_flags.md +0 -67
- package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
- package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
- package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
- package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
- package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
- /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
- /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
|
package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py
ADDED
|
@@ -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
|
|
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 (
|
|
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.
|
|
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-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
1252
|
-
#
|
|
1253
|
-
#
|
|
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 |
|
|
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
|
|
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).
|
|
146
|
-
REC_SKILL="
|
|
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
|
|
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
|
-
}
|