@salesforce/afv-skills 1.46.0 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/agentforce-observe/SKILL.md +32 -4
- package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
- package/skills/dx-devops-project-manage/SKILL.md +197 -0
- package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
- package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
- package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
- package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
- package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
- package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
- package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
- package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
- package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
- package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
- package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
- package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
- package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
- package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
- package/skills/education-cloud-domain-configure/SKILL.md +234 -0
- package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
- package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
- package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
- package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
- package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
- package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
- package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
- package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
- package/skills/experience-cms-brand-apply/SKILL.md +5 -5
- package/skills/experience-cms-brand-create/SKILL.md +2 -2
- package/skills/experience-cms-content-generate/SKILL.md +1 -0
- package/skills/experience-cms-content-render/SKILL.md +173 -0
- package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
- package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
- package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
- package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
- package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
- package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
- package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
- package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
- package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
- package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
- package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
- package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
- package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
- package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
- package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
- package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
- package/skills/experience-cms-content-render/references/package-api.md +106 -0
- package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
- package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
- package/skills/experience-cms-content-render/references/verify.md +49 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
- package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
- package/skills/experience-search-coordinate/SKILL.md +198 -0
- package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
- package/skills/experience-search-coordinate/references/content-route.md +313 -0
- package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
- package/skills/experience-search-coordinate/references/media-route.md +172 -0
- package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
- package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
- package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
- package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
- package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
- package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
- package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
- package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
- package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
- package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
- package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
- package/skills/life-sciences-territory-configure/SKILL.md +217 -0
- package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
- package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
- package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
- package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
- package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
- package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
- package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
- package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
- package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
- package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
- package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
- package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
- package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
- package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
- package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
- package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
- package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
- package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
- package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
- package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
- package/skills/service-omni-agent-users-create/SKILL.md +96 -0
- package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
- package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
- package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
- package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
- package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
- package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
- package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
- package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
- package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
- package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
- package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
- package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
- package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
- package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
- package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
- package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
- package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
- package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
- package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
- package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
- package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
- package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
- package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
- package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
- package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
- package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
- package/skills/service-omni-queue-deploy/SKILL.md +91 -0
- package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
- package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
- package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
- package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
- package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
- package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
- package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
- package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
- package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
- package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
- package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
- package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
- package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
- package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
- package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
- package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
- package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
- package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
- package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
- package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
- package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
- package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
- package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
- package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
- package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
- package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
- package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
- package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
- package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
- package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
- package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
- package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
- package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
- package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
- package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
- package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Contract tests for service-omni-sidebar-configure/scripts/enable-and-report.sh.
|
|
3
|
+
|
|
4
|
+
Run standalone: ``python3 test_sidebar_contracts.py`` (or under pytest / unittest discovery).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
import unittest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
14
|
+
from _bootstrap import ( # noqa: E402
|
|
15
|
+
FakeSf,
|
|
16
|
+
_FAKE_SF_PROD,
|
|
17
|
+
_FAKE_SF_SANDBOX,
|
|
18
|
+
run,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _last_json(out: str) -> dict:
|
|
23
|
+
"""Parse the last JSON object printed to stdout+stderr."""
|
|
24
|
+
dec = json.JSONDecoder()
|
|
25
|
+
obj, i, n = None, 0, len(out)
|
|
26
|
+
while i < n:
|
|
27
|
+
ch = out[i]
|
|
28
|
+
if ch in "{[":
|
|
29
|
+
try:
|
|
30
|
+
cand, end = dec.raw_decode(out, i)
|
|
31
|
+
if isinstance(cand, dict):
|
|
32
|
+
obj = cand
|
|
33
|
+
i = end
|
|
34
|
+
continue
|
|
35
|
+
except json.JSONDecodeError:
|
|
36
|
+
pass
|
|
37
|
+
i += 1
|
|
38
|
+
assert obj is not None, f"no JSON object found in output:\n{out}"
|
|
39
|
+
return obj
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InputContractTests(unittest.TestCase):
|
|
43
|
+
"""Argument guarantees must hold before any `sf` call (PATH has no sf)."""
|
|
44
|
+
|
|
45
|
+
def test_missing_org_rejected(self):
|
|
46
|
+
rc, out = run([], scrub_sf=True)
|
|
47
|
+
self.assertEqual(rc, 1, out)
|
|
48
|
+
self.assertIn("Usage", out)
|
|
49
|
+
|
|
50
|
+
def test_injection_shaped_app_name_rejected(self):
|
|
51
|
+
rc, out = run(["myorg", "Bad Name; DROP"], scrub_sf=True)
|
|
52
|
+
self.assertEqual(rc, 1, out)
|
|
53
|
+
self.assertIn("Invalid app_developer_name", out)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ProductionGuardTests(unittest.TestCase):
|
|
57
|
+
def test_refuses_production_org(self):
|
|
58
|
+
with FakeSf(_FAKE_SF_PROD) as p:
|
|
59
|
+
rc, out = run(["prodorg", "Demo_Console"], path_prefix=p)
|
|
60
|
+
self.assertEqual(rc, 1, out)
|
|
61
|
+
v = _last_json(out)
|
|
62
|
+
self.assertEqual(v["status"], "blocked", out)
|
|
63
|
+
self.assertIn("production", v["blocking_issue"].lower())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AutoDetectTests(unittest.TestCase):
|
|
67
|
+
def test_blocks_when_no_console_app(self):
|
|
68
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
69
|
+
rc, out = run(["sandbox"], path_prefix=p, extra_env={"FAKE_CONSOLE_APPS": "0"})
|
|
70
|
+
self.assertEqual(rc, 1, out)
|
|
71
|
+
v = _last_json(out)
|
|
72
|
+
self.assertEqual(v["status"], "blocked", out)
|
|
73
|
+
self.assertIn("No deployable custom Lightning console app", v["blocking_issue"])
|
|
74
|
+
|
|
75
|
+
def test_blocks_when_multiple_console_apps(self):
|
|
76
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
77
|
+
rc, out = run(["sandbox"], path_prefix=p, extra_env={"FAKE_CONSOLE_APPS": "2"})
|
|
78
|
+
self.assertEqual(rc, 1, out)
|
|
79
|
+
v = _last_json(out)
|
|
80
|
+
self.assertEqual(v["status"], "blocked", out)
|
|
81
|
+
self.assertIn("explicit app_developer_name", v["blocking_issue"])
|
|
82
|
+
|
|
83
|
+
def test_adopts_single_console_app(self):
|
|
84
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
85
|
+
rc, out = run(["sandbox"], path_prefix=p, extra_env={"FAKE_CONSOLE_APPS": "1"})
|
|
86
|
+
self.assertEqual(rc, 0, out)
|
|
87
|
+
v = _last_json(out)
|
|
88
|
+
self.assertEqual(v["app_developer_name"], "Demo_Console", out)
|
|
89
|
+
|
|
90
|
+
def test_autodetect_uses_deployable_custom_application_metadata(self):
|
|
91
|
+
src = (Path(__file__).resolve().parents[1] / "enable-and-report.sh").read_text()
|
|
92
|
+
start = src.index("APP_METADATA_JSON=$(sf org list metadata")
|
|
93
|
+
block = src[start:src.index("APP_COUNT=", start)]
|
|
94
|
+
self.assertIn("--metadata-type CustomApplication", block)
|
|
95
|
+
self.assertIn('startswith("standard__") | not', block)
|
|
96
|
+
self.assertIn("<navType>Console</navType>", block)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class EnableAndReuseTests(unittest.TestCase):
|
|
100
|
+
def test_enables_when_absent_and_verifies_roundtrip(self):
|
|
101
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
102
|
+
rc, out = run(["sandbox", "Demo_Console"], path_prefix=p,
|
|
103
|
+
extra_env={"FAKE_APP_PINNED": "0"})
|
|
104
|
+
self.assertEqual(rc, 0, out)
|
|
105
|
+
v = _last_json(out)
|
|
106
|
+
self.assertEqual(v["status"], "enabled", out)
|
|
107
|
+
self.assertFalse(v["before"], out)
|
|
108
|
+
self.assertTrue(v["after"], out)
|
|
109
|
+
self.assertIsNotNone(v["deploy_id"], out)
|
|
110
|
+
|
|
111
|
+
def test_reused_when_already_pinned_without_deploy(self):
|
|
112
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
113
|
+
calllog = str(Path(p) / "calls.log")
|
|
114
|
+
rc, out = run(["sandbox", "Demo_Console"], path_prefix=p,
|
|
115
|
+
extra_env={"FAKE_APP_PINNED": "1", "FAKE_SF_CALLLOG": calllog})
|
|
116
|
+
log = Path(calllog).read_text() if Path(calllog).exists() else ""
|
|
117
|
+
self.assertEqual(rc, 0, out)
|
|
118
|
+
v = _last_json(out)
|
|
119
|
+
self.assertEqual(v["status"], "reused", out)
|
|
120
|
+
self.assertTrue(v["before"], out)
|
|
121
|
+
self.assertTrue(v["after"], out)
|
|
122
|
+
self.assertNotIn("project deploy", log, "reused path must not deploy")
|
|
123
|
+
|
|
124
|
+
def test_plan_only_reports_action_without_writing(self):
|
|
125
|
+
with FakeSf(_FAKE_SF_SANDBOX) as p:
|
|
126
|
+
calllog = str(Path(p) / "calls.log")
|
|
127
|
+
rc, out = run(["sandbox", "Demo_Console"], path_prefix=p,
|
|
128
|
+
extra_env={"FAKE_APP_PINNED": "0", "PLAN_ONLY": "1",
|
|
129
|
+
"FAKE_SF_CALLLOG": calllog})
|
|
130
|
+
log = Path(calllog).read_text() if Path(calllog).exists() else ""
|
|
131
|
+
self.assertEqual(rc, 0, out)
|
|
132
|
+
v = _last_json(out)
|
|
133
|
+
self.assertTrue(v.get("plan_mode"), out)
|
|
134
|
+
self.assertEqual(v["status"], "action_needed", out)
|
|
135
|
+
self.assertNotIn("project deploy", log, "plan mode must not deploy")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-omni-skills-based-routing-configure
|
|
3
|
+
description: "Use to make agents routable by Omni skills-based routing: ensure an Omni Skill exists, then assign it to each active agent through the classic Omni-Channel SkillUser junction. Idempotent — existing skills and user bindings are reused, and only missing bindings are created. Works on Service Cloud orgs without requiring Field Service. Triggers: set up skills-based routing agents, assign Omni skills to agents, make agents skill-routable, create an Omni skill and bind users, repair missing agent skill assignments. Do not use to author WorkSkillRouting rules, create agent users, or configure queues."
|
|
4
|
+
allowed-tools: Bash Read Write Edit Glob Grep
|
|
5
|
+
metadata:
|
|
6
|
+
version: "1.0"
|
|
7
|
+
domains: ["Service"]
|
|
8
|
+
minApiVersion: "66.0"
|
|
9
|
+
relatedSkills:
|
|
10
|
+
- "service-omni-agent-users-create"
|
|
11
|
+
- "service-omni-base-settings-configure"
|
|
12
|
+
accessCheck:
|
|
13
|
+
- type: license
|
|
14
|
+
value: ServiceCloud
|
|
15
|
+
cliTools:
|
|
16
|
+
- tool: ["jq"]
|
|
17
|
+
semver: ">=1.6"
|
|
18
|
+
- tool: ["python3"]
|
|
19
|
+
semver: ">=3.8"
|
|
20
|
+
- tool: ["sf"]
|
|
21
|
+
semver: ">=2.139.6"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# service-omni-skills-based-routing-configure
|
|
25
|
+
|
|
26
|
+
Make agents routable by Omni **skills-based routing (SBR)**. A routable agent needs the **Skill** and a **SkillUser** junction assigning that skill to the agent's user. `SkillUser` is the classic Omni-Channel model written by Setup > Skills > Assign Users and evaluated when work has skill requirements. This skill ensures both, idempotently, for one skill across a set of active agent users.
|
|
27
|
+
|
|
28
|
+
`ServiceResource` and `ServiceResourceSkill` are the Field Service skill model. On a plain Service Cloud org, `ServiceResource.IsActive` is not writable or queryable, so a newly created resource remains inactive and the junction is rejected. This skill therefore uses the supported, license-appropriate `SkillUser` path.
|
|
29
|
+
|
|
30
|
+
## Inputs
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bash scripts/configure-and-report.sh <org-alias> <skill_developer_name> [agent_members_csv] [skill_master_label]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `org-alias` (required).
|
|
37
|
+
- `skill_developer_name` (required). The Omni `Skill` DeveloperName to ensure and bind (e.g. `Omni_Demo_Voice`).
|
|
38
|
+
- `agent_members_csv` (optional). Comma-separated agents to make skill-routable — each token a Username (`…@…`) or a 15/18-char User Id (`005…`). May also be supplied via `AGENT_MEMBERS_CSV`. When omitted, the skill blocks (it will not guess which agents to skill).
|
|
39
|
+
- `skill_master_label` (optional). MasterLabel used only when the skill must be created; defaults to a title-cased form of the DeveloperName.
|
|
40
|
+
|
|
41
|
+
## Preconditions and safety
|
|
42
|
+
|
|
43
|
+
- Target org authenticated via `sf` CLI, Service Cloud license, `sf` CLI ≥ 2.139.6.
|
|
44
|
+
- Omni-Channel base settings enabled with **skills-based routing** turned on (`service-omni-base-settings-configure`).
|
|
45
|
+
- The named agents exist and are active (`service-omni-agent-users-create`).
|
|
46
|
+
- The three-way `safe_to_write` production guard applies.
|
|
47
|
+
|
|
48
|
+
## Run
|
|
49
|
+
|
|
50
|
+
`configure-and-report.sh` proceeds in two idempotent phases, each detect-before-write:
|
|
51
|
+
|
|
52
|
+
1. **Skill** — query `Skill` by DeveloperName. Present → reused; absent → deploy a `Skill` component via the Metadata API and re-query for its Id.
|
|
53
|
+
2. **SkillUser** — query existing `SkillUser` records for the Skill and resolved users, then create only missing (`SkillId`, `UserId`) bindings via REST DML. A concurrent `DUPLICATE_VALUE` is reconciled as reused.
|
|
54
|
+
|
|
55
|
+
## Behavior
|
|
56
|
+
|
|
57
|
+
**Idempotent + non-destructive.** Existing skills and bindings are reused; the skill never deletes or reassigns anything it did not create, so re-runs converge without duplication. Only **active** users are resolved — an inactive user cannot be routed work, so it is reported rather than silently skilled.
|
|
58
|
+
|
|
59
|
+
**Partial safety.** If some bindings succeed and others fail, the status is `partial` with per-agent errors in `blocking_issue`; the successful bindings are left in place (each junction is an independent write).
|
|
60
|
+
|
|
61
|
+
## Output contract
|
|
62
|
+
|
|
63
|
+
A single JSON object: `status` ∈ `configured` | `reused` | `partial` | `blocked`, `skill` (`{developer_name, id, state}`), `agents` (array of `{username, user_id, skill_binding_id, skill_binding_state}`), `created_counts`, `reused_counts`, `manual_actions`, `blocking_issue`.
|
|
64
|
+
|
|
65
|
+
- `reused` — skill and all bindings already existed.
|
|
66
|
+
- `configured` — at least one component created and all requested bindings now exist.
|
|
67
|
+
- `partial` / `blocked` — see `blocking_issue`; `manual_actions` names the prerequisite skill when the cause is a missing precondition.
|
|
68
|
+
|
|
69
|
+
## Limitations
|
|
70
|
+
|
|
71
|
+
- One skill per invocation; run again per skill an agent set needs.
|
|
72
|
+
- Does not author `WorkSkillRouting` rules or field-based skill criteria — that is a separate skill.
|
|
73
|
+
- Does not create agent users, queues, or presence configuration.
|
|
74
|
+
- `SkillUser.SkillLevel` is left at the platform default; forking the DML body is required to set explicit proficiency.
|
|
75
|
+
|
|
76
|
+
## References
|
|
77
|
+
|
|
78
|
+
| File | When to read |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `references/api-notes.md` | Skill metadata vs Skill sObject, the SkillUser agent-binding model, and SBR base-setting prerequisites |
|
|
81
|
+
| `scripts/tests/test_sbr_contracts.py` | When validating changes — run `python3 scripts/tests/test_sbr_contracts.py` from this skill directory |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# API notes — service-omni-skills-based-routing-configure
|
|
2
|
+
|
|
3
|
+
## The two objects skills-based routing needs
|
|
4
|
+
|
|
5
|
+
| Object | API | Purpose |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `Skill` | Metadata API (`Skill`) to create; `Skill` sObject to read | The skill definition. Skills are metadata-backed — they cannot be `INSERT`ed via DML, so a missing skill is created by deploying a `Skill` component, then re-queried for its Id. |
|
|
8
|
+
| `SkillUser` | REST DML | Junction assigning a `Skill` to an agent `User`, matching Setup > Skills > Assign Users. Required: `SkillId`, `UserId`. |
|
|
9
|
+
|
|
10
|
+
## Skill: metadata create, sObject read
|
|
11
|
+
|
|
12
|
+
The `Skill` sObject exposes `Id`, `DeveloperName`, `MasterLabel` and is queryable, so detection is a
|
|
13
|
+
SOQL read by DeveloperName. Creation is a Metadata API deploy of:
|
|
14
|
+
|
|
15
|
+
```xml
|
|
16
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
17
|
+
<Skill xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
18
|
+
<description>…</description>
|
|
19
|
+
<label>…</label>
|
|
20
|
+
</Skill>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
After the deploy the skill's Id is obtained by re-querying the sObject (the deploy response carries
|
|
24
|
+
the fullName, not the record Id). A freshly-deployed skill may lag a moment before it is queryable;
|
|
25
|
+
if the re-query returns nothing the skill blocks and asks for a re-run rather than binding to an
|
|
26
|
+
empty Id.
|
|
27
|
+
|
|
28
|
+
## SkillUser
|
|
29
|
+
|
|
30
|
+
- The skill deduplicates `SkillUser` by `(SkillId, UserId)` and creates only missing bindings for
|
|
31
|
+
active users. `SkillLevel` is optional and remains at the platform default.
|
|
32
|
+
- `SkillUser` is created through REST DML. A concurrent create can return `DUPLICATE_VALUE`; the
|
|
33
|
+
script reconciles that response as reused because the desired binding now exists.
|
|
34
|
+
- `ServiceResourceSkill` is not used. It belongs to the Field Service model and requires an active
|
|
35
|
+
`ServiceResource`. Plain Service Cloud orgs do not expose a writable or queryable
|
|
36
|
+
`ServiceResource.IsActive`, so that path produces inactive resources and rejected bindings.
|
|
37
|
+
|
|
38
|
+
## Prerequisite: base settings
|
|
39
|
+
|
|
40
|
+
Skills-based routing must be enabled in Omni-Channel settings
|
|
41
|
+
(`service-omni-base-settings-configure`) before skills route work. A Skill deploy can succeed while
|
|
42
|
+
SBR is off, so the skill points the operator at the base-settings skill when the deploy fails with
|
|
43
|
+
an SBR-disabled error.
|
|
44
|
+
|
|
45
|
+
## Out of scope: WorkSkillRouting
|
|
46
|
+
|
|
47
|
+
Authoring `WorkSkillRouting` rules (field-based skill criteria such as "add Voice when Case
|
|
48
|
+
Priority = Medium") is a **separate** capability — it is a Tooling-API rule header
|
|
49
|
+
(`WorkSkillRoutingStandardEntity`: `MasterLabel`, `DeveloperName`, `IsActive`) with skill-requirement
|
|
50
|
+
and field-criteria children, and only one rule is permitted per entity. It is not created here.
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# configure-and-report.sh - make agents routable via skills-based routing: ensure a Skill and a SkillUser
|
|
3
|
+
# binding per agent (the classic Omni-Channel agent-skill model); detect-before-write, idempotent.
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
if [ $# -lt 2 ]; then
|
|
8
|
+
echo '{"error":"Usage: bash configure-and-report.sh <org-alias> <skill_developer_name> [agent_members_csv] [skill_master_label]"}' >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
ORG="$1"
|
|
13
|
+
SKILL_DN="$2"
|
|
14
|
+
AGENTS_CSV="${3:-${AGENT_MEMBERS_CSV:-}}"
|
|
15
|
+
SKILL_LABEL_ARG="${4:-}"
|
|
16
|
+
API_VERSION="v66.0"
|
|
17
|
+
|
|
18
|
+
# Skill DeveloperName grammar: leading letter, then letters/digits/underscore, max 80.
|
|
19
|
+
if ! [[ "$SKILL_DN" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
|
|
20
|
+
echo "{\"error\":\"Invalid skill_developer_name '$SKILL_DN'. Must start with a letter and contain only letters, digits, and underscores (max 80).\"}" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [ -z "$AGENTS_CSV" ]; then
|
|
25
|
+
echo '{"error":"No agents supplied. Pass agent_members_csv (Usernames and/or 005... User Ids) or set AGENT_MEMBERS_CSV."}' >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
|
|
30
|
+
echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Default MasterLabel from DeveloperName (underscores -> spaces) when not supplied.
|
|
35
|
+
if [ -n "$SKILL_LABEL_ARG" ]; then
|
|
36
|
+
SKILL_LABEL="$SKILL_LABEL_ARG"
|
|
37
|
+
else
|
|
38
|
+
SKILL_LABEL=$(printf '%s' "$SKILL_DN" | tr '_' ' ')
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Escape the five XML predefined entities so a caller-supplied SKILL_LABEL with & < > " ' cannot
|
|
42
|
+
# break or inject into the hand-built Skill metadata XML below.
|
|
43
|
+
xml_escape() {
|
|
44
|
+
local s="$1"
|
|
45
|
+
s="${s//&/&}"; s="${s//</<}"; s="${s//>/>}"; s="${s//\"/"}"; s="${s//\'/'}"
|
|
46
|
+
printf '%s' "$s"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
emit_blocked() {
|
|
50
|
+
local msg="$1"; local target_skill="${2:-}"
|
|
51
|
+
local manual_actions='[]'
|
|
52
|
+
if [ -n "$target_skill" ]; then
|
|
53
|
+
manual_actions=$(jq -n --arg ts "$target_skill" '[{id:"MISSING_PREREQ",title:"Run prerequisite skill",target_skill:$ts}]')
|
|
54
|
+
fi
|
|
55
|
+
jq -n --arg msg "$msg" --arg dn "$SKILL_DN" --argjson ma "$manual_actions" \
|
|
56
|
+
'{skill:"service-omni-skills-based-routing-configure",status:"blocked",
|
|
57
|
+
skill_configured:{developer_name:$dn,id:null,state:"not_created"},
|
|
58
|
+
agents:[],created_counts:{},reused_counts:{},manual_actions:$ma,blocking_issue:$msg}'
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
emit_plan() {
|
|
63
|
+
local status="$1"; local detail="$2"
|
|
64
|
+
jq -n --arg status "$status" --arg detail "$detail" --arg dn "$SKILL_DN" \
|
|
65
|
+
'{skill:"service-omni-skills-based-routing-configure",status:$status,plan_mode:true,
|
|
66
|
+
plan_detail:$detail,skill_configured:{developer_name:$dn},manual_actions:[],blocking_issue:null}'
|
|
67
|
+
exit 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# REST POST helper: echoes the created Id on success, empty on failure. Error detail is written to a
|
|
71
|
+
# file because this function is called inside command substitution and shell variables would be lost.
|
|
72
|
+
POST_ERR_FILE="$(mktemp)"
|
|
73
|
+
trap 'rm -f "$POST_ERR_FILE"' EXIT
|
|
74
|
+
post_err() { cat "$POST_ERR_FILE" 2>/dev/null || true; }
|
|
75
|
+
rest_post() {
|
|
76
|
+
local sobject="$1"; local body="$2"
|
|
77
|
+
local stderr_f; stderr_f="$(mktemp)"
|
|
78
|
+
local res
|
|
79
|
+
res=$(sf api request rest --target-org "$ORG" \
|
|
80
|
+
"/services/data/$API_VERSION/sobjects/$sobject" --method POST --body "$body" 2>"$stderr_f" || true)
|
|
81
|
+
local ok; ok=$(echo "$res" | jq -r '.success // false' 2>/dev/null || echo false)
|
|
82
|
+
if [ "$ok" = "true" ]; then
|
|
83
|
+
: > "$POST_ERR_FILE"
|
|
84
|
+
echo "$res" | jq -r '.id'
|
|
85
|
+
else
|
|
86
|
+
local code msg
|
|
87
|
+
code=$(echo "$res" | jq -r 'if type=="array" then .[0].errorCode else .errorCode // "UNKNOWN" end' 2>/dev/null || echo UNKNOWN)
|
|
88
|
+
msg=$(echo "$res" | jq -r 'if type=="array" then .[0].message else .message // "" end' 2>/dev/null || echo "")
|
|
89
|
+
if [ -z "$msg" ] || [ "$msg" = "null" ]; then
|
|
90
|
+
msg=$(printf '%s' "$res" | tr '\n' ' ' | head -c 200)
|
|
91
|
+
[ -n "$msg" ] || msg=$(grep -v '^Warning:' "$stderr_f" | head -1)
|
|
92
|
+
[ -n "$msg" ] || msg="no error detail returned by the sf CLI"
|
|
93
|
+
fi
|
|
94
|
+
printf '%s - %s' "$code" "$msg" > "$POST_ERR_FILE"
|
|
95
|
+
echo ""
|
|
96
|
+
fi
|
|
97
|
+
rm -f "$stderr_f"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Step 1 - Organization + safe_to_write guard
|
|
101
|
+
ORG_JSON=$(sf data query --target-org "$ORG" \
|
|
102
|
+
--query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" --json 2>/dev/null) \
|
|
103
|
+
|| emit_blocked "Failed to query Organization for the safe_to_write guard."
|
|
104
|
+
IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox')
|
|
105
|
+
TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
|
|
106
|
+
ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType')
|
|
107
|
+
SAFE_TO_WRITE=false
|
|
108
|
+
if [ "$IS_SANDBOX" = "true" ] || [ "$TRIAL_EXP" != "null" ] \
|
|
109
|
+
|| [ "$ORG_TYPE" = "Developer Edition" ] || [ "$ORG_TYPE" = "Base Edition" ]; then
|
|
110
|
+
SAFE_TO_WRITE=true
|
|
111
|
+
fi
|
|
112
|
+
[ "$SAFE_TO_WRITE" = "true" ] || emit_blocked "Refusing to configure skills-based routing on a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org."
|
|
113
|
+
|
|
114
|
+
# Step 2 - Resolve active agents from the CSV (Usernames and/or 005... Ids)
|
|
115
|
+
USERNAME_IN=""; ID_IN=""; BADTOK=""
|
|
116
|
+
while IFS= read -r tok; do
|
|
117
|
+
[ -z "$tok" ] && continue
|
|
118
|
+
if [[ "$tok" =~ ^005[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$ ]]; then
|
|
119
|
+
ID_IN="${ID_IN:+$ID_IN,}'$tok'"
|
|
120
|
+
elif [[ "$tok" =~ ^[^,\'\"[:space:]]+@[^,\'\"[:space:]]+$ ]]; then
|
|
121
|
+
USERNAME_IN="${USERNAME_IN:+$USERNAME_IN,}'$tok'"
|
|
122
|
+
else
|
|
123
|
+
BADTOK="$BADTOK '$tok'"
|
|
124
|
+
fi
|
|
125
|
+
done <<< "$(printf '%s' "$AGENTS_CSV" | tr ',' '\n' | sed 's/^ *//;s/ *$//')"
|
|
126
|
+
[ -z "$BADTOK" ] || emit_blocked "agent_members_csv contains token(s) that are neither a Username (...@...) nor a 15/18-char User Id (005...):$BADTOK"
|
|
127
|
+
|
|
128
|
+
WHERE_PARTS=""
|
|
129
|
+
[ -n "$USERNAME_IN" ] && WHERE_PARTS="Username IN ($USERNAME_IN)"
|
|
130
|
+
[ -n "$ID_IN" ] && WHERE_PARTS="${WHERE_PARTS:+$WHERE_PARTS OR }Id IN ($ID_IN)"
|
|
131
|
+
|
|
132
|
+
# Fail-closed with structured JSON: without `|| emit_blocked`, a non-zero query makes the command
|
|
133
|
+
# substitution fail and set -e would exit before any blocked output. The jq -e guard separates a real
|
|
134
|
+
# zero-result from an inconclusive/unparseable read so the two get distinct remediation.
|
|
135
|
+
U_JSON=$(sf data query --target-org "$ORG" \
|
|
136
|
+
--query "SELECT Id, Username, Name FROM User WHERE ($WHERE_PARTS) AND IsActive=true" --json 2>/dev/null) \
|
|
137
|
+
|| emit_blocked "Could not query User to resolve agent_members_csv (query failed: authentication, network, or API access). Re-run with a reachable org." "service-omni-agent-users-create"
|
|
138
|
+
echo "$U_JSON" | jq -e '.result.totalSize | numbers' >/dev/null 2>&1 \
|
|
139
|
+
|| emit_blocked "User resolution returned no parseable result (inconclusive). Re-run against a reachable org." "service-omni-agent-users-create"
|
|
140
|
+
U_COUNT=$(echo "$U_JSON" | jq -r '.result.totalSize')
|
|
141
|
+
[ "$U_COUNT" -gt 0 ] || emit_blocked "No ACTIVE users resolved from agent_members_csv. Verify the usernames/IDs are correct and the users are active." "service-omni-agent-users-create"
|
|
142
|
+
|
|
143
|
+
USERS_JSON=$(echo "$U_JSON" | jq -c '[.result.records[] | {id:.Id, username:.Username, name:.Name}]')
|
|
144
|
+
USER_IDS_IN=$(echo "$USERS_JSON" | jq -r '[.[].id] | map("'"'"'" + . + "'"'"'") | join(",")')
|
|
145
|
+
|
|
146
|
+
# Step 3 - Ensure the Skill exists (reuse sObject; else deploy Skill metadata, then re-query)
|
|
147
|
+
skill_id_by_name() {
|
|
148
|
+
sf data query --target-org "$ORG" --json \
|
|
149
|
+
--query "SELECT Id FROM Skill WHERE DeveloperName='$SKILL_DN'" 2>/dev/null \
|
|
150
|
+
| jq -r '.result.records[0].Id // ""' 2>/dev/null
|
|
151
|
+
}
|
|
152
|
+
SKILL_ID=$(skill_id_by_name)
|
|
153
|
+
SKILL_STATE="reused"
|
|
154
|
+
|
|
155
|
+
# PLAN_ONLY - report intent from read-only detection and stop before any write.
|
|
156
|
+
if [ "${PLAN_ONLY:-}" = "1" ]; then
|
|
157
|
+
if [ -n "$SKILL_ID" ]; then
|
|
158
|
+
emit_plan "action_needed" "Skill '$SKILL_DN' exists; would ensure a SkillUser binding for $U_COUNT active agent(s)."
|
|
159
|
+
else
|
|
160
|
+
emit_plan "action_needed" "Would deploy Skill '$SKILL_DN' and ensure a SkillUser binding for $U_COUNT active agent(s)."
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if [ -z "$SKILL_ID" ]; then
|
|
165
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
166
|
+
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK" "$POST_ERR_FILE"' EXIT
|
|
167
|
+
cp "$SCRIPT_DIR/../assets/sfdx-project.json" "$WORK/sfdx-project.json" 2>/dev/null \
|
|
168
|
+
|| echo '{"packageDirectories":[{"path":"force-app","default":true}],"sourceApiVersion":"66.0"}' > "$WORK/sfdx-project.json"
|
|
169
|
+
SKILL_DIR="$WORK/force-app/main/default/skills"; mkdir -p "$SKILL_DIR"
|
|
170
|
+
cat > "$SKILL_DIR/${SKILL_DN}.skill-meta.xml" <<XML
|
|
171
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
172
|
+
<Skill xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
173
|
+
<description>Created by service-omni-skills-based-routing-configure.</description>
|
|
174
|
+
<label>$(xml_escape "$SKILL_LABEL")</label>
|
|
175
|
+
</Skill>
|
|
176
|
+
XML
|
|
177
|
+
DEP_JSON=$(cd "$WORK" && sf project deploy start --target-org "$ORG" \
|
|
178
|
+
--metadata "Skill:${SKILL_DN}" --json 2>/dev/null || true)
|
|
179
|
+
DEP_OK=$(echo "$DEP_JSON" | jq -r '.result.success // false' 2>/dev/null || echo false)
|
|
180
|
+
if [ "$DEP_OK" != "true" ]; then
|
|
181
|
+
FAIL=$(echo "$DEP_JSON" | jq -r '.result.details.componentFailures // [] | map(.problem) | join("; ")' 2>/dev/null)
|
|
182
|
+
[ -n "$FAIL" ] && [ "$FAIL" != "null" ] || FAIL=$(echo "$DEP_JSON" | jq -r '.message // "Unknown deploy error"' 2>/dev/null)
|
|
183
|
+
emit_blocked "Failed to deploy Skill '$SKILL_DN': $FAIL. Ensure skills-based routing is enabled in Omni-Channel settings." "service-omni-base-settings-configure"
|
|
184
|
+
fi
|
|
185
|
+
SKILL_ID=$(skill_id_by_name)
|
|
186
|
+
SKILL_STATE="created"
|
|
187
|
+
[ -n "$SKILL_ID" ] || emit_blocked "Deployed Skill '$SKILL_DN' but could not re-query its Id (org may still be indexing). Re-run to bind agents."
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# Step 4 - Ensure a SkillUser binding (SkillId, UserId) per active agent. This is the classic
|
|
191
|
+
# Omni-Channel agent-skill model used by Setup > Skills > Assign Users. ServiceResourceSkill is a
|
|
192
|
+
# Field Service model and cannot be used on a plain Service Cloud org where ServiceResource.IsActive
|
|
193
|
+
# is unavailable.
|
|
194
|
+
SU_JSON=$(sf data query --target-org "$ORG" --json \
|
|
195
|
+
--query "SELECT Id, UserId FROM SkillUser WHERE SkillId='$SKILL_ID' AND UserId IN ($USER_IDS_IN)" 2>/dev/null || echo '{}')
|
|
196
|
+
EXISTING_SU=$(echo "$SU_JSON" | jq -c '[.result.records[]? | {user_id:.UserId, su_id:.Id}]' 2>/dev/null || echo '[]')
|
|
197
|
+
|
|
198
|
+
AGENTS_OUT='[]'
|
|
199
|
+
BIND_CREATED=0; BIND_REUSED=0
|
|
200
|
+
PARTIAL_ERRORS=()
|
|
201
|
+
|
|
202
|
+
# For each agent: ensure a SkillUser binding.
|
|
203
|
+
while IFS= read -r row; do
|
|
204
|
+
[ -z "$row" ] && continue
|
|
205
|
+
USR_ID=$(echo "$row" | jq -r '.id')
|
|
206
|
+
UNAME=$(echo "$row" | jq -r '.username')
|
|
207
|
+
|
|
208
|
+
SU_ID=$(echo "$EXISTING_SU" | jq -r --arg u "$USR_ID" '.[] | select(.user_id==$u) | .su_id' | head -1)
|
|
209
|
+
BIND_STATE="reused"
|
|
210
|
+
if [ -z "$SU_ID" ] || [ "$SU_ID" = "null" ]; then
|
|
211
|
+
SU_ID=$(rest_post "SkillUser" "{\"SkillId\":\"$SKILL_ID\",\"UserId\":\"$USR_ID\"}")
|
|
212
|
+
if [ -z "$SU_ID" ]; then
|
|
213
|
+
ERR=$(post_err)
|
|
214
|
+
if printf '%s' "$ERR" | grep -q "DUPLICATE_VALUE"; then
|
|
215
|
+
BIND_STATE="reused"; BIND_REUSED=$((BIND_REUSED+1))
|
|
216
|
+
SU_ID=""
|
|
217
|
+
else
|
|
218
|
+
PARTIAL_ERRORS+=("$UNAME: skill binding failed - $ERR")
|
|
219
|
+
BIND_STATE="not_created"
|
|
220
|
+
fi
|
|
221
|
+
else
|
|
222
|
+
BIND_STATE="created"; BIND_CREATED=$((BIND_CREATED+1))
|
|
223
|
+
fi
|
|
224
|
+
else
|
|
225
|
+
BIND_REUSED=$((BIND_REUSED+1))
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
SU_JQ='null'; [ -n "$SU_ID" ] && [ "$SU_ID" != "null" ] && SU_JQ=$(jq -n --arg s "$SU_ID" '$s')
|
|
229
|
+
AGENTS_OUT=$(echo "$AGENTS_OUT" | jq -c \
|
|
230
|
+
--arg u "$UNAME" --arg uid "$USR_ID" --argjson su "$SU_JQ" --arg bs "$BIND_STATE" \
|
|
231
|
+
'. + [{username:$u,user_id:$uid,skill_binding_id:$su,skill_binding_state:$bs}]')
|
|
232
|
+
done < <(echo "$USERS_JSON" | jq -c '.[]')
|
|
233
|
+
|
|
234
|
+
# Step 5 - Status + report
|
|
235
|
+
if [ ${#PARTIAL_ERRORS[@]} -gt 0 ]; then
|
|
236
|
+
# Any success at all → partial; nothing succeeded → blocked.
|
|
237
|
+
if [ "$BIND_CREATED" -gt 0 ] || [ "$BIND_REUSED" -gt 0 ]; then
|
|
238
|
+
STATUS="partial"
|
|
239
|
+
else
|
|
240
|
+
STATUS="blocked"
|
|
241
|
+
fi
|
|
242
|
+
BLOCKING="Partial skills-based-routing configuration. Failures: ${PARTIAL_ERRORS[*]}"
|
|
243
|
+
elif [ "$BIND_CREATED" -eq 0 ] && [ "$SKILL_STATE" = "reused" ]; then
|
|
244
|
+
STATUS="reused"; BLOCKING=""
|
|
245
|
+
else
|
|
246
|
+
STATUS="configured"; BLOCKING=""
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
BLOCKING_JQ='null'
|
|
250
|
+
[ -n "$BLOCKING" ] && BLOCKING_JQ=$(jq -n --arg s "$BLOCKING" '$s')
|
|
251
|
+
|
|
252
|
+
jq -n \
|
|
253
|
+
--arg status "$STATUS" \
|
|
254
|
+
--arg dn "$SKILL_DN" \
|
|
255
|
+
--arg sid "$SKILL_ID" \
|
|
256
|
+
--arg sstate "$SKILL_STATE" \
|
|
257
|
+
--argjson agents "$AGENTS_OUT" \
|
|
258
|
+
--argjson bind_created "$BIND_CREATED" \
|
|
259
|
+
--argjson bind_reused "$BIND_REUSED" \
|
|
260
|
+
--argjson blocking "$BLOCKING_JQ" \
|
|
261
|
+
'{
|
|
262
|
+
skill: "service-omni-skills-based-routing-configure",
|
|
263
|
+
status: $status,
|
|
264
|
+
skill_configured: {developer_name:$dn, id:$sid, state:$sstate},
|
|
265
|
+
agents: $agents,
|
|
266
|
+
created_counts: {skill_bindings:$bind_created},
|
|
267
|
+
reused_counts: {skill_bindings:$bind_reused},
|
|
268
|
+
manual_actions: [],
|
|
269
|
+
blocking_issue: $blocking
|
|
270
|
+
}'
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Shared paths + fake-`sf` harness for the skills-based-routing contract suite.
|
|
2
|
+
|
|
3
|
+
Drives ``configure-and-report.sh`` through ``subprocess`` and asserts on exit codes + emitted JSON.
|
|
4
|
+
Org-free and network-free. ``jq`` comes from the real PATH.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
SKILLS_ROOT = Path(__file__).resolve().parents[3]
|
|
14
|
+
SKILL_DIR = "service-omni-skills-based-routing-configure"
|
|
15
|
+
SCRIPT = f"{SKILL_DIR}/scripts/configure-and-report.sh"
|
|
16
|
+
|
|
17
|
+
# PRODUCTION org - safe_to_write must refuse before any write.
|
|
18
|
+
_FAKE_SF_PROD = r"""#!/usr/bin/env bash
|
|
19
|
+
sub="${1:-} ${2:-}"; args="$*"
|
|
20
|
+
case "$sub" in
|
|
21
|
+
"org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
|
|
22
|
+
"data query")
|
|
23
|
+
if printf '%s' "$args" | grep -q "FROM Organization"; then
|
|
24
|
+
echo '{"status":0,"result":{"records":[{"IsSandbox":false,"TrialExpirationDate":null,"OrganizationType":"Enterprise Edition"}]}}'
|
|
25
|
+
else
|
|
26
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
27
|
+
fi
|
|
28
|
+
exit 0;;
|
|
29
|
+
"project deploy") echo '{"status":1,"result":{"success":false},"message":"FAKE_SF_DEPLOY_SHOULD_NOT_BE_REACHED"}'; exit 1;;
|
|
30
|
+
"api request") echo '{"success":false,"errorCode":"FAKE_POST_SHOULD_NOT_BE_REACHED"}'; exit 0;;
|
|
31
|
+
*) echo '{"status":0,"result":{}}'; exit 0;;
|
|
32
|
+
esac
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# SANDBOX org. Tunables (all default to the "everything must be created" path):
|
|
36
|
+
# FAKE_SKILL_PRESENT=1 → the Skill already exists (reused); else absent → deploy then re-query.
|
|
37
|
+
# FAKE_BIND_PRESENT=1 → a SkillUser binding already exists for each user (reused); else create.
|
|
38
|
+
# FAKE_BIND_POST_FAIL=1 → the SkillUser POST fails for the second agent.
|
|
39
|
+
# FAKE_BIND_DUPLICATE=1 → the SkillUser POST returns DUPLICATE_VALUE and is reconciled as reused.
|
|
40
|
+
# Two active users (005...0001 / 005...0002) are always resolved from the User query.
|
|
41
|
+
_FAKE_SF_SANDBOX = r"""#!/usr/bin/env bash
|
|
42
|
+
sub="${1:-} ${2:-}"; args="$*"
|
|
43
|
+
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
44
|
+
SKILL_PRESENT="${FAKE_SKILL_PRESENT:-0}"
|
|
45
|
+
BIND_PRESENT="${FAKE_BIND_PRESENT:-0}"
|
|
46
|
+
BIND_POST_FAIL="${FAKE_BIND_POST_FAIL:-0}"
|
|
47
|
+
BIND_DUPLICATE="${FAKE_BIND_DUPLICATE:-0}"
|
|
48
|
+
if [ -n "${FAKE_SF_CALLLOG:-}" ]; then printf '%s\n' "$args" >> "$FAKE_SF_CALLLOG"; fi
|
|
49
|
+
case "$sub" in
|
|
50
|
+
"org display") echo '{"status":0,"result":{"id":"00Dxx0000000000EAA"}}'; exit 0;;
|
|
51
|
+
"data query")
|
|
52
|
+
if printf '%s' "$args" | grep -q "FROM Organization"; then
|
|
53
|
+
echo '{"status":0,"result":{"records":[{"IsSandbox":true,"TrialExpirationDate":null,"OrganizationType":"Developer Edition"}]}}'
|
|
54
|
+
elif printf '%s' "$args" | grep -q "FROM User"; then
|
|
55
|
+
echo '{"status":0,"result":{"records":[{"Id":"0050000000000001AAA","Username":"agent1@example.com","Name":"Agent One"},{"Id":"0050000000000002AAA","Username":"agent2@example.com","Name":"Agent Two"}],"totalSize":2}}'
|
|
56
|
+
elif printf '%s' "$args" | grep -q "FROM SkillUser"; then
|
|
57
|
+
if [ "$BIND_PRESENT" = "1" ]; then
|
|
58
|
+
echo '{"status":0,"result":{"records":[{"Id":"0C90000000000001AAA","UserId":"0050000000000001AAA"},{"Id":"0C90000000000002AAA","UserId":"0050000000000002AAA"}],"totalSize":2}}'
|
|
59
|
+
else echo '{"status":0,"result":{"records":[],"totalSize":0}}'; fi
|
|
60
|
+
elif printf '%s' "$args" | grep -q "FROM Skill"; then
|
|
61
|
+
if [ "$SKILL_PRESENT" = "1" ] || [ -f "$SELF_DIR/.skill_deployed" ]; then
|
|
62
|
+
echo '{"status":0,"result":{"records":[{"Id":"0PS0000000000001AAA"}],"totalSize":1}}'
|
|
63
|
+
else echo '{"status":0,"result":{"records":[],"totalSize":0}}'; fi
|
|
64
|
+
else
|
|
65
|
+
echo '{"status":0,"result":{"records":[],"totalSize":0}}'
|
|
66
|
+
fi
|
|
67
|
+
exit 0;;
|
|
68
|
+
"project deploy")
|
|
69
|
+
touch "$SELF_DIR/.skill_deployed"
|
|
70
|
+
echo '{"status":0,"result":{"success":true,"files":[{"fullName":"'"$(printf '%s' "$args" | sed -n 's/.*Skill:\([A-Za-z0-9_]*\).*/\1/p')"'","type":"Skill","state":"Created"}]}}'
|
|
71
|
+
exit 0;;
|
|
72
|
+
"api request")
|
|
73
|
+
if printf '%s' "$args" | grep -q "sobjects/SkillUser"; then
|
|
74
|
+
if [ "$BIND_DUPLICATE" = "1" ]; then
|
|
75
|
+
echo '[{"errorCode":"DUPLICATE_VALUE","message":"duplicate value found: unique constraint (SkillId, UserId)"}]';
|
|
76
|
+
elif [ "$BIND_POST_FAIL" = "1" ] && printf '%s' "$args" | grep -q "0050000000000002AAA"; then
|
|
77
|
+
echo '[{"errorCode":"FIELD_CUSTOM_VALIDATION_EXCEPTION","message":"forced binding failure"}]';
|
|
78
|
+
else echo '{"success":true,"id":"0C90000000000009AAA"}'; fi
|
|
79
|
+
else
|
|
80
|
+
echo '{"success":true,"id":"000000000000000AAA"}'
|
|
81
|
+
fi
|
|
82
|
+
exit 0;;
|
|
83
|
+
*) echo '{"status":0,"result":{}}'; exit 0;;
|
|
84
|
+
esac
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def run(args, path_prefix=None, scrub_sf=False, extra_env=None):
|
|
89
|
+
env = dict(os.environ)
|
|
90
|
+
if scrub_sf:
|
|
91
|
+
kept = [d for d in env.get("PATH", "").split(os.pathsep)
|
|
92
|
+
if d and not os.path.exists(os.path.join(d, "sf"))]
|
|
93
|
+
env["PATH"] = os.pathsep.join(kept)
|
|
94
|
+
if path_prefix:
|
|
95
|
+
env["PATH"] = path_prefix + os.pathsep + env.get("PATH", "")
|
|
96
|
+
if extra_env:
|
|
97
|
+
env.update({k: str(v) for k, v in extra_env.items()})
|
|
98
|
+
proc = subprocess.run(
|
|
99
|
+
["bash", str(SKILLS_ROOT / SCRIPT), *args],
|
|
100
|
+
capture_output=True, text=True, env=env, cwd=str(SKILLS_ROOT),
|
|
101
|
+
)
|
|
102
|
+
return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FakeSf:
|
|
106
|
+
def __init__(self, body=_FAKE_SF_PROD):
|
|
107
|
+
self._body = body
|
|
108
|
+
self._tmp = None
|
|
109
|
+
|
|
110
|
+
def __enter__(self):
|
|
111
|
+
self._tmp = tempfile.mkdtemp(prefix="sbr-fake-sf-")
|
|
112
|
+
sf = Path(self._tmp) / "sf"
|
|
113
|
+
sf.write_text(self._body)
|
|
114
|
+
sf.chmod(0o755)
|
|
115
|
+
return self._tmp
|
|
116
|
+
|
|
117
|
+
def __exit__(self, *exc):
|
|
118
|
+
import shutil
|
|
119
|
+
if self._tmp:
|
|
120
|
+
shutil.rmtree(self._tmp, ignore_errors=True)
|