@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.
Files changed (317) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-observe/SKILL.md +32 -4
  3. package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
  4. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
  5. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
  6. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
  7. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
  8. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
  9. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
  10. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
  11. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
  12. package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
  13. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
  14. package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
  15. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
  16. package/skills/dx-devops-project-manage/SKILL.md +197 -0
  17. package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
  18. package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
  19. package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
  20. package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
  21. package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
  22. package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
  23. package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
  24. package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
  25. package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
  26. package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
  27. package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
  28. package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
  29. package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
  30. package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
  31. package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
  32. package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
  33. package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
  34. package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
  35. package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
  36. package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
  37. package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
  38. package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
  39. package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
  40. package/skills/education-cloud-domain-configure/SKILL.md +234 -0
  41. package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
  42. package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
  43. package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
  44. package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
  45. package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
  46. package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
  47. package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
  48. package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
  49. package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
  50. package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
  51. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
  52. package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
  53. package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
  54. package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
  55. package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
  56. package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
  57. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
  58. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
  59. package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
  60. package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
  61. package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
  62. package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
  63. package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
  64. package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
  65. package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
  66. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
  67. package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
  68. package/skills/experience-cms-brand-apply/SKILL.md +5 -5
  69. package/skills/experience-cms-brand-create/SKILL.md +2 -2
  70. package/skills/experience-cms-content-generate/SKILL.md +1 -0
  71. package/skills/experience-cms-content-render/SKILL.md +173 -0
  72. package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
  73. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
  74. package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
  75. package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
  76. package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
  77. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
  78. package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
  79. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
  80. package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
  81. package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
  82. package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
  83. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
  84. package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
  85. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
  86. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
  87. package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
  88. package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
  89. package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
  90. package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
  91. package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
  92. package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
  93. package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
  94. package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
  95. package/skills/experience-cms-content-render/references/package-api.md +106 -0
  96. package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
  97. package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
  98. package/skills/experience-cms-content-render/references/verify.md +49 -0
  99. package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
  100. package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
  101. package/skills/experience-search-coordinate/SKILL.md +198 -0
  102. package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
  103. package/skills/experience-search-coordinate/references/content-route.md +313 -0
  104. package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
  105. package/skills/experience-search-coordinate/references/media-route.md +172 -0
  106. package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
  107. package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
  108. package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
  109. package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
  110. package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
  111. package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
  112. package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
  113. package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
  114. package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
  115. package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
  116. package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
  117. package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
  118. package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
  119. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
  120. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
  121. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
  122. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
  123. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
  124. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
  125. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
  126. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
  127. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
  128. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
  129. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
  130. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
  131. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
  132. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
  133. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
  134. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
  135. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
  136. package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
  137. package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
  138. package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
  139. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
  140. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
  141. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
  142. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
  143. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
  144. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
  145. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
  146. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
  147. package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
  148. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
  149. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
  150. package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
  151. package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
  152. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
  153. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
  154. package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
  155. package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
  156. package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
  157. package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
  158. package/skills/life-sciences-territory-configure/SKILL.md +217 -0
  159. package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
  160. package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
  161. package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
  162. package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
  163. package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
  164. package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
  165. package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
  166. package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
  167. package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
  168. package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
  169. package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
  170. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
  171. package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
  172. package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
  173. package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
  174. package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
  175. package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
  176. package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
  177. package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
  178. package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
  179. package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
  180. package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
  181. package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
  182. package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
  183. package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
  184. package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
  185. package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
  186. package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
  187. package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
  188. package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
  189. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
  190. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
  191. package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
  192. package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
  193. package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
  194. package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
  195. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
  196. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
  197. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
  198. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
  199. package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
  200. package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
  201. package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
  202. package/skills/service-omni-agent-users-create/SKILL.md +96 -0
  203. package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
  204. package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
  205. package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
  206. package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
  207. package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
  208. package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
  209. package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
  210. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
  211. package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
  212. package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
  213. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
  214. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
  215. package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
  216. package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
  217. package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
  218. package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
  219. package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
  220. package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
  221. package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
  222. package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
  223. package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
  224. package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
  225. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
  226. package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
  227. package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
  228. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
  229. package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
  230. package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
  231. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
  232. package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
  233. package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
  234. package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
  235. package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
  236. package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
  237. package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
  238. package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
  239. package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
  240. package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
  241. package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
  242. package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
  243. package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
  244. package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
  245. package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
  246. package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
  247. package/skills/service-omni-queue-deploy/SKILL.md +91 -0
  248. package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
  249. package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
  250. package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
  251. package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
  252. package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
  253. package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
  254. package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
  255. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
  256. package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
  257. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
  258. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
  259. package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
  260. package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
  261. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
  262. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
  263. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
  264. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
  265. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
  266. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
  267. package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
  268. package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
  269. package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
  270. package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
  271. package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
  272. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
  273. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
  274. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
  275. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
  276. package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
  277. package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
  278. package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
  279. package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
  280. package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
  281. package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
  282. package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
  283. package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
  284. package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
  285. package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
  286. package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
  287. package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
  288. package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
  289. package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
  290. package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
  291. package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
  292. package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
  293. package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
  294. package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
  295. package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
  296. package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
  297. package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
  298. package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
  299. package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
  300. package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
  301. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
  302. package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
  303. package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
  304. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
  305. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
  306. package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
  307. package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
  308. package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
  309. package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
  310. package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
  311. package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
  312. package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
  313. package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
  314. package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
  315. package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
  316. package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
  317. package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
@@ -0,0 +1,705 @@
1
+ #!/usr/bin/env bash
2
+ # verify-and-configure.sh - configure + verify Agentforce agent → human escalation (queue/QRC/member/flow + doc-driven canEscalate/outboundRoute verify); idempotent, PLAN_ONLY=1 previews. Inputs, ownership, exit codes: SKILL.md + references/.
3
+
4
+ set -euo pipefail
5
+
6
+ SKILL_NAME="service-agentforce-human-escalation-configure"
7
+ API_VERSION="v66.0"
8
+ # Runtime flow developer name (env-overridable); the temp deploy is written as ${FLOW_DEV_NAME}.
9
+ FLOW_DEV_NAME="${FLOW_DEVELOPER_NAME:-Human_Escalation_Outbound_Flow}"
10
+ # Human-readable Flow label; defaults to the developer name with underscores as spaces.
11
+ FLOW_LABEL="${FLOW_LABEL:-${FLOW_DEV_NAME//_/ }}"
12
+ # Work object routed to the queue (default MessagingSession; Case/VoiceCall pair with SERVICE_CHANNEL_*).
13
+ CONTEXT_OBJECT="${CONTEXT_OBJECT:-MessagingSession}"
14
+ SERVICE_CHANNEL_DEV_NAME="${SERVICE_CHANNEL_DEV_NAME:-sfdc_livemessage}"
15
+ SERVICE_CHANNEL_LABEL="${SERVICE_CHANNEL_LABEL:-Messaging}"
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ # Bundled flow is a tokenized template; rendered into ${FLOW_DEV_NAME}.flow-meta.xml at deploy time.
19
+ ASSET_FLOW="$SCRIPT_DIR/../assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml"
20
+
21
+ # Escape the five XML predefined entities, then sed replacement metacharacters, so a caller-supplied
22
+ # FLOW_LABEL / SERVICE_CHANNEL_LABEL with & < > " ' | cannot corrupt the rendered flow XML.
23
+ xml_escape() {
24
+ local s="$1"
25
+ s="${s//&/&amp;}"; s="${s//</&lt;}"; s="${s//>/&gt;}"; s="${s//\"/&quot;}"; s="${s//\'/&apos;}"
26
+ printf '%s' "$s"
27
+ }
28
+ sed_repl_escape() { printf '%s' "$1" | sed -e 's/[\\&|]/\\&/g'; }
29
+
30
+ # Cleanup trap: remove temp working dirs on exit.
31
+ TWORK=""
32
+ _cleanup() { [ -n "$TWORK" ] && rm -rf "$TWORK" 2>/dev/null || true; }
33
+ trap _cleanup EXIT
34
+
35
+ # Args
36
+ if [ $# -lt 1 ]; then
37
+ echo '{"error":"Usage: bash verify-and-configure.sh <org-alias> [<agent-developer-name>] [<queue-developer-name>] [<escalation-topic-api-name>] [<planner-bundle-api-name>]"}' >&2
38
+ exit 1
39
+ fi
40
+
41
+ ORG="$1"
42
+ AGENT_DN="${2:-${AGENT_DEVELOPER_NAME:-Support_Agent}}"
43
+ QUEUE_DN="${3:-${QUEUE_DEVELOPER_NAME:-Human_Support_Queue}}"
44
+ ESCALATION_TOPIC="${4:-${ESCALATION_TOPIC_API_NAME:-}}"
45
+ PLANNER_BUNDLE="${5:-${PLANNER_BUNDLE_API_NAME:-}}"
46
+ THRESHOLD_AUTHORED="${THRESHOLD_AUTHORED:-0}"
47
+ # Consecutive failed attempts before hand-off (default 2); rendered into the directive + verdict.
48
+ DEFAULT_FAILURE_THRESHOLD="${DEFAULT_FAILURE_THRESHOLD:-2}"
49
+
50
+ # Input contract - validate BEFORE any sf call. DeveloperName grammar: leading letter, then
51
+ # letters/digits/underscore, max 80.
52
+ DN_RE='^[A-Za-z][A-Za-z0-9_]{0,79}$'
53
+ if ! [[ "$AGENT_DN" =~ $DN_RE ]]; then
54
+ echo "{\"error\":\"Invalid agent_developer_name '$AGENT_DN'. Must start with a letter and contain only letters, digits, and underscores (max 80 chars).\"}" >&2
55
+ exit 1
56
+ fi
57
+ if ! [[ "$QUEUE_DN" =~ $DN_RE ]]; then
58
+ echo "{\"error\":\"Invalid queue_developer_name '$QUEUE_DN'. Must start with a letter and contain only letters, digits, and underscores (max 80 chars).\"}" >&2
59
+ exit 1
60
+ fi
61
+ if [ -n "$ESCALATION_TOPIC" ] && ! [[ "$ESCALATION_TOPIC" =~ $DN_RE ]]; then
62
+ echo "{\"error\":\"Invalid escalation_topic_api_name '$ESCALATION_TOPIC'. Must match the DeveloperName grammar.\"}" >&2
63
+ exit 1
64
+ fi
65
+ if [ -n "$PLANNER_BUNDLE" ] && ! [[ "$PLANNER_BUNDLE" =~ $DN_RE ]]; then
66
+ echo "{\"error\":\"Invalid planner_bundle_api_name '$PLANNER_BUNDLE'. Must match the DeveloperName grammar.\"}" >&2
67
+ exit 1
68
+ fi
69
+ # CONTEXT_OBJECT is an sObject API name; validate with the same grammar (safe for SOQL/XML interpolation).
70
+ if ! [[ "$CONTEXT_OBJECT" =~ $DN_RE ]]; then
71
+ echo "{\"error\":\"Invalid context_object '$CONTEXT_OBJECT'. Must be an sObject API name (leading letter, then letters, digits, and underscores).\"}" >&2
72
+ exit 1
73
+ fi
74
+ if ! [[ "$FLOW_DEV_NAME" =~ $DN_RE ]]; then
75
+ echo "{\"error\":\"Invalid flow_developer_name '$FLOW_DEV_NAME'. Must match the DeveloperName grammar.\"}" >&2
76
+ exit 1
77
+ fi
78
+ # DEFAULT_FAILURE_THRESHOLD must be a small positive integer (1-99).
79
+ if ! [[ "$DEFAULT_FAILURE_THRESHOLD" =~ ^[1-9][0-9]?$ ]]; then
80
+ echo "{\"error\":\"Invalid DEFAULT_FAILURE_THRESHOLD '$DEFAULT_FAILURE_THRESHOLD'. Must be a positive integer between 1 and 99.\"}" >&2
81
+ exit 1
82
+ fi
83
+
84
+ # QUEUE_MEMBER_USERNAMES (optional): parse/trim/validate the full list before any sf call, so bad
85
+ # input never mutates the org first. The cleaned, comma-joined list is what the members step consumes.
86
+ UNAME_RE='^[A-Za-z0-9._%+@-]+$'
87
+ QUEUE_MEMBER_USERNAMES_CLEAN=""
88
+ if [ -n "${QUEUE_MEMBER_USERNAMES:-}" ]; then
89
+ IFS=',' read -ra _UNAMES <<< "$QUEUE_MEMBER_USERNAMES"
90
+ for _raw in "${_UNAMES[@]}"; do
91
+ uname="${_raw#"${_raw%%[![:space:]]*}"}"; uname="${uname%"${uname##*[![:space:]]}"}"
92
+ [ -z "$uname" ] && continue
93
+ if ! [[ "$uname" =~ $UNAME_RE ]]; then
94
+ echo "{\"error\":\"Invalid QUEUE_MEMBER_USERNAMES entry '$uname'. Usernames may contain only letters, digits, and . _ % + @ - characters.\"}" >&2
95
+ exit 1
96
+ fi
97
+ if [ -z "$QUEUE_MEMBER_USERNAMES_CLEAN" ]; then
98
+ QUEUE_MEMBER_USERNAMES_CLEAN="$uname"
99
+ else
100
+ QUEUE_MEMBER_USERNAMES_CLEAN="$QUEUE_MEMBER_USERNAMES_CLEAN,$uname"
101
+ fi
102
+ done
103
+ fi
104
+
105
+ if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
106
+ echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
107
+ exit 1
108
+ fi
109
+
110
+ # Helpers
111
+ emit_blocked() {
112
+ local msg="$1"; local click_path="${2:-}"
113
+ local manual_actions='[]'
114
+ if [ -n "$click_path" ]; then
115
+ manual_actions=$(jq -n --arg cp "$click_path" '[{id:"MANUAL_FIX", title:"Manual fix required", click_path:$cp}]')
116
+ fi
117
+ jq -n \
118
+ --arg skill "$SKILL_NAME" --arg msg "$msg" --arg agent "$AGENT_DN" --arg queue "$QUEUE_DN" \
119
+ --argjson ma "$manual_actions" \
120
+ '{skill:$skill, status:"BLOCKED", agent:{developer_name:$agent}, queue:{developer_name:$queue},
121
+ manual_actions:$ma, blocking_issue:$msg}'
122
+ exit 1
123
+ }
124
+
125
+ emit_plan() {
126
+ local detail="$1"
127
+ jq -n --arg skill "$SKILL_NAME" --arg detail "$detail" --arg agent "$AGENT_DN" --arg queue "$QUEUE_DN" \
128
+ '{skill:$skill, status:"PLAN", plan_mode:true, plan_detail:$detail,
129
+ agent:{developer_name:$agent}, queue:{developer_name:$queue}, manual_actions:[], blocking_issue:null}'
130
+ exit 0
131
+ }
132
+
133
+ # Step 1 - Organization + safe_to_write guard
134
+ ORG_JSON=$(sf data query --target-org "$ORG" \
135
+ --query "SELECT Id, IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" \
136
+ --json 2>/dev/null)
137
+
138
+ IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox')
139
+ TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
140
+ ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType')
141
+
142
+ SAFE_TO_WRITE=false
143
+ if [ "$IS_SANDBOX" = "true" ]; then
144
+ SAFE_TO_WRITE=true
145
+ elif [ "$TRIAL_EXP" != "null" ]; then
146
+ SAFE_TO_WRITE=true
147
+ elif [ "$ORG_TYPE" = "Developer Edition" ] || [ "$ORG_TYPE" = "Base Edition" ]; then
148
+ SAFE_TO_WRITE=true
149
+ fi
150
+
151
+ if [ "$SAFE_TO_WRITE" != "true" ]; then
152
+ emit_blocked "Refusing to configure escalation on a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org."
153
+ fi
154
+
155
+ # Step 2 - Precondition: agent exists AND latest version Active
156
+ BOT_JSON=$(sf data query --target-org "$ORG" \
157
+ --query "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE DeveloperName='$AGENT_DN'" \
158
+ --json 2>/dev/null)
159
+
160
+ TWORK=$(mktemp -d)
161
+ echo "$BOT_JSON" > "$TWORK/bot.json"
162
+ AGENT_VERDICT=$(node "$SCRIPT_DIR/classify-agent-active.mjs" "$TWORK/bot.json" "$AGENT_DN") || {
163
+ emit_blocked "Could not read BotDefinition for '$AGENT_DN' - surface the raw sf error and retry. Raw: $BOT_JSON"
164
+ }
165
+ AGENT_READY=$(echo "$AGENT_VERDICT" | jq -r '.ready')
166
+ if [ "$AGENT_READY" != "true" ]; then
167
+ AGENT_REASON=$(echo "$AGENT_VERDICT" | jq -r '.reason')
168
+ emit_blocked "Agent precondition failed ($AGENT_REASON). Create and activate the agent '$AGENT_DN' before wiring escalation." \
169
+ "Setup → Agentforce → Agents → '$AGENT_DN' → activate the latest version"
170
+ fi
171
+ AGENT_ACTIVE=true
172
+
173
+ # Step 3 - Precondition: for MessagingSession handoff, an active Enhanced Chat / MIAW MessagingChannel
174
+ # must exist. Other context objects use a different transport and aren't gated here.
175
+ if [ "$CONTEXT_OBJECT" = "MessagingSession" ]; then
176
+ MC_JSON=$(sf data query --target-org "$ORG" \
177
+ --query "SELECT Id, DeveloperName FROM MessagingChannel WHERE IsActive=true LIMIT 1" \
178
+ --json 2>/dev/null)
179
+ MC_COUNT=$(echo "$MC_JSON" | jq -r '.result.totalSize // 0' 2>/dev/null || echo "0")
180
+ if [ "$MC_COUNT" -eq 0 ]; then
181
+ emit_blocked "No active MessagingChannel (Enhanced Chat / MIAW) found. Provision the messaging channel first, then re-run." \
182
+ "Setup → Messaging Settings → New Channel (Messaging for In-App and Web)"
183
+ fi
184
+ fi
185
+
186
+ # Step 3b - Preflight: determine which Agentforce authoring model the org exposes. classic =
187
+ # GenAiPlugin + GenAiPlannerBundle (canEscalate + outboundRouteConfigs surfaces). nga = AiAuthoringBundle
188
+ # (Agent Script; escalation via @utils.escalate/create-record, routed through channel Omni to the queue).
189
+ # none = neither -> fail closed. Model rationale: references/nga-escalation.md.
190
+ MDT_JSON=$(sf org list metadata-types --target-org "$ORG" --json 2>/dev/null || echo '{}')
191
+ HAS_PLUGIN=$(echo "$MDT_JSON" | jq -r '[.result.metadataObjects[]?.xmlName] | index("GenAiPlugin") // empty' 2>/dev/null || echo "")
192
+ HAS_BUNDLE=$(echo "$MDT_JSON" | jq -r '[.result.metadataObjects[]?.xmlName] | index("GenAiPlannerBundle") // empty' 2>/dev/null || echo "")
193
+ HAS_AIAUTHORING=$(echo "$MDT_JSON" | jq -r '[.result.metadataObjects[]?.xmlName] | index("AiAuthoringBundle") // empty' 2>/dev/null || echo "")
194
+
195
+ AUTHORING_MODEL="none"
196
+ HAS_CLASSIC_SURFACE=false
197
+ HAS_NGA_SURFACE=false
198
+ [ -n "$HAS_BUNDLE" ] && [ -n "$HAS_PLUGIN" ] && HAS_CLASSIC_SURFACE=true
199
+ [ -n "$HAS_AIAUTHORING" ] && HAS_NGA_SURFACE=true
200
+
201
+ # When an org exposes both authoring generations, classify the selected target rather
202
+ # than defaulting the whole org to classic. A target-specific retrieve is read-only and
203
+ # prevents an NGA agent from being verified against unrelated classic metadata.
204
+ TARGET_CLASSIC=false
205
+ TARGET_NGA=false
206
+ if [ "$HAS_NGA_SURFACE" = "true" ]; then
207
+ MODEL_DIR="$TWORK/model-nga"
208
+ mkdir -p "$MODEL_DIR"
209
+ if sf project retrieve start --target-org "$ORG" --metadata "AiAuthoringBundle:$AGENT_DN" \
210
+ --target-metadata-dir "$MODEL_DIR" --unzip --json >/dev/null 2>&1 \
211
+ && find "$MODEL_DIR" -type f -print -quit | grep -q .; then
212
+ TARGET_NGA=true
213
+ fi
214
+ fi
215
+ if [ "$HAS_CLASSIC_SURFACE" = "true" ]; then
216
+ MODEL_DIR="$TWORK/model-classic"
217
+ mkdir -p "$MODEL_DIR"
218
+ CLASSIC_TARGET="${PLANNER_BUNDLE:-$AGENT_DN}"
219
+ if sf project retrieve start --target-org "$ORG" --metadata "GenAiPlannerBundle:$CLASSIC_TARGET" \
220
+ --target-metadata-dir "$MODEL_DIR" --unzip --json >/dev/null 2>&1 \
221
+ && find "$MODEL_DIR" -type f -print -quit | grep -q .; then
222
+ TARGET_CLASSIC=true
223
+ fi
224
+ fi
225
+
226
+ if [ "$TARGET_NGA" = "true" ] && [ "$TARGET_CLASSIC" != "true" ]; then
227
+ AUTHORING_MODEL="nga"
228
+ elif [ "$TARGET_CLASSIC" = "true" ] && [ "$TARGET_NGA" != "true" ]; then
229
+ AUTHORING_MODEL="classic"
230
+ elif [ "$TARGET_CLASSIC" = "true" ] && [ "$TARGET_NGA" = "true" ]; then
231
+ if [ -z "${AUTHORING_MODEL_OVERRIDE:-}" ]; then
232
+ emit_blocked "The selected agent resolves in both classic and next-gen Agentforce authoring metadata. Set AUTHORING_MODEL_OVERRIDE=classic or nga after confirming the intended target model."
233
+ fi
234
+ elif [ "$HAS_CLASSIC_SURFACE" = "true" ] && [ "$HAS_NGA_SURFACE" != "true" ]; then
235
+ AUTHORING_MODEL="classic"
236
+ elif [ "$HAS_NGA_SURFACE" = "true" ] && [ "$HAS_CLASSIC_SURFACE" != "true" ]; then
237
+ AUTHORING_MODEL="nga"
238
+ elif [ "$HAS_CLASSIC_SURFACE" = "true" ] && [ "$HAS_NGA_SURFACE" = "true" ]; then
239
+ if [ -z "${AUTHORING_MODEL_OVERRIDE:-}" ]; then
240
+ emit_blocked "This org exposes both Agentforce authoring models, but the selected target could not be classified. Set AUTHORING_MODEL_OVERRIDE=classic or nga after confirming the target metadata."
241
+ fi
242
+ fi
243
+ # AUTHORING_MODEL_OVERRIDE (classic|nga) lets a caller pin the branch when both surfaces coexist.
244
+ case "${AUTHORING_MODEL_OVERRIDE:-}" in
245
+ classic) [ "$HAS_CLASSIC_SURFACE" = "true" ] && AUTHORING_MODEL="classic" ;;
246
+ nga) [ "$HAS_NGA_SURFACE" = "true" ] && AUTHORING_MODEL="nga" ;;
247
+ esac
248
+
249
+ if [ "$AUTHORING_MODEL" = "none" ]; then
250
+ emit_blocked "This org exposes neither the classic Agentforce authoring metadata (GenAiPlugin + GenAiPlannerBundle) nor the next-gen AiAuthoringBundle, so no agent escalation surface can be authored or verified here. Enable Agentforce, then re-run. To provision only the deterministic routing infrastructure (queue, members, QueueRoutingConfig, outbound RoutingFlow) on this org, use the service-omni-* leaf skills directly." \
251
+ "Setup → Einstein / Agentforce → enable Agentforce so the authoring metadata (GenAiPlannerBundle or AiAuthoringBundle) is exposed"
252
+ fi
253
+
254
+ # Step 4 - Create-or-adopt the human queue (+ QueueSobject for the context object)
255
+ Q_JSON=$(sf data query --target-org "$ORG" \
256
+ --query "SELECT Id FROM Group WHERE DeveloperName='$QUEUE_DN' AND Type='Queue'" \
257
+ --json 2>/dev/null)
258
+ QUEUE_ID=$(echo "$Q_JSON" | jq -r '.result.records[0].Id // ""')
259
+
260
+ if [ "${PLAN_ONLY:-}" = "1" ]; then
261
+ if [ -n "$QUEUE_ID" ]; then
262
+ emit_plan "Agent '$AGENT_DN' active; queue '$QUEUE_DN' exists. Would ensure the $CONTEXT_OBJECT QueueSobject and at least one active direct-user member (adding from QUEUE_MEMBER_USERNAMES if empty); ONLY if the queue has an eligible member would it then create/adopt+bind the QueueRoutingConfig and deploy outbound flow '$FLOW_DEV_NAME'; finally round-trip escalation config."
263
+ else
264
+ emit_plan "Agent '$AGENT_DN' active; queue '$QUEUE_DN' missing. Would create the queue ($CONTEXT_OBJECT QueueSobject) and ensure at least one active direct-user member (adding from QUEUE_MEMBER_USERNAMES if empty); ONLY if the queue has an eligible member would it then create+bind the QueueRoutingConfig and deploy outbound flow '$FLOW_DEV_NAME'; finally round-trip escalation config."
265
+ fi
266
+ fi
267
+
268
+ if [ -z "$QUEUE_ID" ]; then
269
+ # Create the queue + context-object QueueSobject via Metadata API.
270
+ TQ="$TWORK/queue/force-app/main/default/queues"
271
+ mkdir -p "$TQ"
272
+ cat > "$TWORK/queue/sfdx-project.json" <<'JSON'
273
+ { "packageDirectories": [{ "path": "force-app", "default": true }], "namespace": "", "sourceApiVersion": "67.0" }
274
+ JSON
275
+ # Queue XSD order: doesSendEmailToMembers, name, queueSobject (Metadata API rejects other orders).
276
+ cat > "$TQ/${QUEUE_DN}.queue-meta.xml" <<XML
277
+ <?xml version="1.0" encoding="UTF-8"?>
278
+ <Queue xmlns="http://soap.sforce.com/2006/04/metadata">
279
+ <doesSendEmailToMembers>false</doesSendEmailToMembers>
280
+ <name>${QUEUE_DN}</name>
281
+ <queueSobject>
282
+ <sobjectType>${CONTEXT_OBJECT}</sobjectType>
283
+ </queueSobject>
284
+ </Queue>
285
+ XML
286
+ if ! DEPLOY_OUT=$(sf project deploy start --target-org "$ORG" --source-dir "$TWORK/queue/force-app" --json 2>/dev/null); then
287
+ emit_blocked "Failed to create queue '$QUEUE_DN'. Raw: $(echo "$DEPLOY_OUT" | head -c 600)"
288
+ fi
289
+ Q_JSON=$(sf data query --target-org "$ORG" \
290
+ --query "SELECT Id FROM Group WHERE DeveloperName='$QUEUE_DN' AND Type='Queue'" --json 2>/dev/null)
291
+ QUEUE_ID=$(echo "$Q_JSON" | jq -r '.result.records[0].Id // ""')
292
+ [ -z "$QUEUE_ID" ] && emit_blocked "Queue '$QUEUE_DN' not found after create attempt."
293
+ fi
294
+
295
+ # Ensure the context-object QueueSobject exists; an adopted queue may lack it - bind additively via Data API.
296
+ QS_JSON=$(sf data query --target-org "$ORG" \
297
+ --query "SELECT Id FROM QueueSobject WHERE QueueId='$QUEUE_ID' AND SobjectType='$CONTEXT_OBJECT'" \
298
+ --json 2>/dev/null)
299
+ QUEUE_SOBJECT_PRESENT=false
300
+ [ "$(echo "$QS_JSON" | jq -r '.result.totalSize // 0')" -gt 0 ] && QUEUE_SOBJECT_PRESENT=true
301
+
302
+ if [ "$QUEUE_SOBJECT_PRESENT" != "true" ]; then
303
+ QS_STDERR="$(mktemp)"
304
+ QS_POST=$(sf api request rest --target-org "$ORG" \
305
+ "/services/data/$API_VERSION/sobjects/QueueSobject" \
306
+ --method POST \
307
+ --body "$(jq -n --arg q "$QUEUE_ID" --arg so "$CONTEXT_OBJECT" '{QueueId:$q, SobjectType:$so}')" 2>"$QS_STDERR" || true)
308
+ rm -f "$QS_STDERR"
309
+ if [ "$(echo "$QS_POST" | jq -r '.success // false' 2>/dev/null)" = "true" ]; then
310
+ QUEUE_SOBJECT_PRESENT=true
311
+ else
312
+ QS_ERR=$(echo "$QS_POST" | jq -r '.[0].message // .message // "unknown error"' 2>/dev/null)
313
+ emit_blocked "Queue '$QUEUE_DN' exists but is not bound to $CONTEXT_OBJECT, and the QueueSobject bind failed: $QS_ERR" \
314
+ "Setup → Queues → '$QUEUE_DN' → Supported Objects → add the context object → Save"
315
+ fi
316
+ fi
317
+
318
+ # Step 4b - Ensure the queue has >=1 active direct-user member (Id prefix 005); an empty queue can't
319
+ # receive escalations, so routing (Steps 5/6) is refused without one. Necessary but not sufficient for
320
+ # routability (Omni perms/presence proven only at runtime) - hence the honest evidence key
321
+ # queueHasActiveDirectUserMember. Members come from the pre-validated QUEUE_MEMBER_USERNAMES_CLEAN list.
322
+ count_active_user_members() {
323
+ local gm ids uq
324
+ gm=$(sf data query --target-org "$ORG" \
325
+ --query "SELECT UserOrGroupId FROM GroupMember WHERE GroupId='$QUEUE_ID'" --json 2>/dev/null)
326
+ # Only direct User members (Id prefix 005) can be resolved to IsActive deterministically.
327
+ ids=$(echo "$gm" | jq -r '[.result.records[]?.UserOrGroupId | select(type=="string" and startswith("005"))] | join("\u0027,\u0027")' 2>/dev/null || echo "")
328
+ if [ -z "$ids" ]; then echo 0; return; fi
329
+ uq=$(sf data query --target-org "$ORG" \
330
+ --query "SELECT COUNT(Id) c FROM User WHERE IsActive=true AND Id IN ('$ids')" --json 2>/dev/null)
331
+ echo "$uq" | jq -r '.result.records[0].c // (.result.totalSize // 0)' 2>/dev/null || echo 0
332
+ }
333
+
334
+ MEMBER_COUNT=$(count_active_user_members)
335
+ [ -z "$MEMBER_COUNT" ] && MEMBER_COUNT=0
336
+
337
+ if [ "$MEMBER_COUNT" -eq 0 ] && [ -n "$QUEUE_MEMBER_USERNAMES_CLEAN" ]; then
338
+ IFS=',' read -ra _UNAMES <<< "$QUEUE_MEMBER_USERNAMES_CLEAN"
339
+ for uname in "${_UNAMES[@]}"; do
340
+ [ -z "$uname" ] && continue
341
+ U_JSON=$(sf data query --target-org "$ORG" \
342
+ --query "SELECT Id FROM User WHERE Username='$uname' AND IsActive=true LIMIT 1" --json 2>/dev/null)
343
+ U_ID=$(echo "$U_JSON" | jq -r '.result.records[0].Id // ""')
344
+ if [ -z "$U_ID" ]; then
345
+ emit_blocked "QUEUE_MEMBER_USERNAMES user '$uname' was not found as an active User on '$ORG'. Create or reactivate the user, or correct the username, then rerun."
346
+ fi
347
+ GM_STDERR="$(mktemp)"
348
+ GM_POST=$(sf api request rest --target-org "$ORG" \
349
+ "/services/data/$API_VERSION/sobjects/GroupMember" \
350
+ --method POST \
351
+ --body "$(jq -n --arg g "$QUEUE_ID" --arg u "$U_ID" '{GroupId:$g, UserOrGroupId:$u}')" 2>"$GM_STDERR" || true)
352
+ rm -f "$GM_STDERR"
353
+ if [ "$(echo "$GM_POST" | jq -r '.success // false' 2>/dev/null)" != "true" ]; then
354
+ # A duplicate membership (already a member) is benign; anything else is fatal.
355
+ if ! echo "$GM_POST" | jq -e '((.[0].errorCode // "") | test("DUPLICATE"))' >/dev/null 2>&1; then
356
+ GM_ERR=$(echo "$GM_POST" | jq -r '.[0].message // .message // "unknown error"' 2>/dev/null)
357
+ emit_blocked "Failed to add '$uname' to queue '$QUEUE_DN' as a member: $GM_ERR"
358
+ fi
359
+ fi
360
+ done
361
+ MEMBER_COUNT=$(count_active_user_members)
362
+ [ -z "$MEMBER_COUNT" ] && MEMBER_COUNT=0
363
+ fi
364
+
365
+ QUEUE_HAS_ACTIVE_DIRECT_USER_MEMBER=false
366
+ [ "$MEMBER_COUNT" -gt 0 ] && QUEUE_HAS_ACTIVE_DIRECT_USER_MEMBER=true
367
+
368
+ # Membership gate: Steps 5 (QRC bind) + 6 (Flow) run only with an eligible member; else left
369
+ # unconfigured and the verdict is INCOMPLETE (never a flow routing to nobody).
370
+ QUEUE_ROUTING_CONFIG_PRESENT=false
371
+ QUEUE_ROUTING_CONFIG_BOUND=false
372
+ RC_ID=""
373
+ FLOW_ACTIVE_ID=""
374
+ if [ "$QUEUE_HAS_ACTIVE_DIRECT_USER_MEMBER" = "true" ]; then
375
+
376
+ # Step 5 - Ensure a QueueRoutingConfig, then bind it to the queue (Group.QueueRoutingConfigId)
377
+ RC_DN="${QUEUE_DN}_RC"
378
+ RC_JSON=$(sf data query --target-org "$ORG" \
379
+ --query "SELECT Id FROM QueueRoutingConfig WHERE DeveloperName='$RC_DN'" --json 2>/dev/null)
380
+ RC_ID=$(echo "$RC_JSON" | jq -r '.result.records[0].Id // ""')
381
+
382
+ if [ -z "$RC_ID" ]; then
383
+ # Create QRC via Data API. RoutingPriority is required (REQUIRED_FIELD_MISSING if omitted).
384
+ RC_STDERR="$(mktemp)"
385
+ RC_POST=$(sf api request rest --target-org "$ORG" \
386
+ "/services/data/$API_VERSION/sobjects/QueueRoutingConfig" \
387
+ --method POST \
388
+ --body "$(jq -n --arg dn "$RC_DN" --arg ml "${QUEUE_DN} Routing Config" \
389
+ '{DeveloperName:$dn, MasterLabel:$ml, RoutingModel:"MostAvailable", CapacityWeight:5, RoutingPriority:1, IsAttributeBased:false}')" 2>"$RC_STDERR" || true)
390
+ rm -f "$RC_STDERR"
391
+ if [ "$(echo "$RC_POST" | jq -r '.success // false' 2>/dev/null)" = "true" ]; then
392
+ RC_ID=$(echo "$RC_POST" | jq -r '.id')
393
+ else
394
+ if echo "$RC_POST" | jq -e '(.name // "") == "INVALID_TYPE" or ((.[0].errorCode // "") == "INVALID_TYPE")' >/dev/null 2>&1; then
395
+ emit_blocked "QueueRoutingConfig is not available because Omni-Channel is not enabled on this org. Enable Omni-Channel in Setup, then rerun."
396
+ fi
397
+ RC_ERR=$(echo "$RC_POST" | jq -r '.[0].message // .message // "unknown error"' 2>/dev/null)
398
+ emit_blocked "Failed to create QueueRoutingConfig '$RC_DN': $RC_ERR"
399
+ fi
400
+ fi
401
+ QUEUE_ROUTING_CONFIG_PRESENT=false
402
+ [ -n "$RC_ID" ] && QUEUE_ROUTING_CONFIG_PRESENT=true
403
+
404
+ # Bind the QRC to the queue (Group.QueueRoutingConfigId) if not already linked.
405
+ QUEUE_ROUTING_CONFIG_BOUND=false
406
+ if [ -n "$RC_ID" ]; then
407
+ G_JSON=$(sf data query --target-org "$ORG" \
408
+ --query "SELECT Id, QueueRoutingConfigId FROM Group WHERE Id='$QUEUE_ID'" --json 2>/dev/null)
409
+ CUR_RC=$(echo "$G_JSON" | jq -r '.result.records[0].QueueRoutingConfigId // ""')
410
+ if [ "$CUR_RC" = "$RC_ID" ]; then
411
+ QUEUE_ROUTING_CONFIG_BOUND=true
412
+ else
413
+ G_STDERR="$(mktemp)"
414
+ sf api request rest --target-org "$ORG" \
415
+ "/services/data/$API_VERSION/sobjects/Group/$QUEUE_ID" \
416
+ --method PATCH \
417
+ --body "$(jq -n --arg rc "$RC_ID" '{QueueRoutingConfigId:$rc}')" >/dev/null 2>"$G_STDERR" || true
418
+ G_ERR=$(head -c 400 "$G_STDERR" 2>/dev/null || true)
419
+ rm -f "$G_STDERR"
420
+ # PATCH returns 204 (empty body) on success - re-query to CONFIRM the binding persisted.
421
+ G_JSON=$(sf data query --target-org "$ORG" \
422
+ --query "SELECT Id, QueueRoutingConfigId FROM Group WHERE Id='$QUEUE_ID'" --json 2>/dev/null)
423
+ [ "$(echo "$G_JSON" | jq -r '.result.records[0].QueueRoutingConfigId // ""')" = "$RC_ID" ] && QUEUE_ROUTING_CONFIG_BOUND=true
424
+ if [ "$QUEUE_ROUTING_CONFIG_BOUND" != "true" ]; then
425
+ emit_blocked "Created/adopted QueueRoutingConfig '$RC_DN' but failed to bind it to queue '$QUEUE_DN' (Group.QueueRoutingConfigId did not persist).${G_ERR:+ Raw: $G_ERR}"
426
+ fi
427
+ fi
428
+ fi
429
+
430
+ # Step 6 - Deploy the bundled outbound QueueBased RoutingFlow (tokens substituted)
431
+ FLOW_JSON=$(sf data query --target-org "$ORG" \
432
+ --query "SELECT ApiName, ActiveVersionId FROM FlowDefinitionView WHERE ApiName='$FLOW_DEV_NAME' AND ProcessType='RoutingFlow'" \
433
+ --json 2>/dev/null)
434
+ FLOW_ACTIVE_ID=$(echo "$FLOW_JSON" | jq -r '.result.records[0].ActiveVersionId // ""')
435
+
436
+ if [ -z "$FLOW_ACTIVE_ID" ]; then
437
+ [ -f "$ASSET_FLOW" ] || emit_blocked "Bundled outbound flow asset not found at $ASSET_FLOW"
438
+ TF="$TWORK/flow/force-app/main/default/flows"
439
+ mkdir -p "$TF"
440
+ cat > "$TWORK/flow/sfdx-project.json" <<'JSON'
441
+ { "packageDirectories": [{ "path": "force-app", "default": true }], "namespace": "", "sourceApiVersion": "67.0" }
442
+ JSON
443
+ SC_LABEL_SED="$(sed_repl_escape "$(xml_escape "$SERVICE_CHANNEL_LABEL")")"
444
+ FLOW_LABEL_SED="$(sed_repl_escape "$(xml_escape "$FLOW_LABEL")")"
445
+ sed -e "s|__QUEUE_DEVELOPER_NAME__|${QUEUE_DN}|g" \
446
+ -e "s|__SERVICE_CHANNEL_DEV_NAME__|${SERVICE_CHANNEL_DEV_NAME}|g" \
447
+ -e "s|__SERVICE_CHANNEL_LABEL__|${SC_LABEL_SED}|g" \
448
+ -e "s|__FLOW_DEVELOPER_NAME__|${FLOW_DEV_NAME}|g" \
449
+ -e "s|__FLOW_LABEL__|${FLOW_LABEL_SED}|g" \
450
+ "$ASSET_FLOW" > "$TF/${FLOW_DEV_NAME}.flow-meta.xml"
451
+ # Deploy from inside the temp project so sf uses its sourceApiVersion (67.0), not an ancestor's.
452
+ if ! DEPLOY_OUT=$(cd "$TWORK/flow" && sf project deploy start --target-org "$ORG" --source-dir force-app --json 2>/dev/null); then
453
+ emit_blocked "Failed to deploy outbound flow '$FLOW_DEV_NAME'. Raw: $(echo "$DEPLOY_OUT" | head -c 600)"
454
+ fi
455
+ FLOW_JSON=$(sf data query --target-org "$ORG" \
456
+ --query "SELECT ApiName, ActiveVersionId FROM FlowDefinitionView WHERE ApiName='$FLOW_DEV_NAME' AND ProcessType='RoutingFlow'" \
457
+ --json 2>/dev/null)
458
+ FLOW_ACTIVE_ID=$(echo "$FLOW_JSON" | jq -r '.result.records[0].ActiveVersionId // ""')
459
+ fi
460
+
461
+ fi # end membership gate - QRC bind + Flow activation skipped when the queue has no eligible member
462
+
463
+ # Step 6b - Optional deterministic authoring of the doc-driven surfaces (AUTHOR_SURFACES=1). Default is
464
+ # verify-only; with the topic/bundle names supplied this authors canEscalate + outboundRouteConfigs
465
+ # (retrieve → patch → deploy → reactivate). Runs only after the membership gate. See references/.
466
+ if [ "${AUTHOR_SURFACES:-0}" = "1" ] && [ "$QUEUE_HAS_ACTIVE_DIRECT_USER_MEMBER" = "true" ] && [ "$AUTHORING_MODEL" = "classic" ]; then
467
+ if [ -z "$ESCALATION_TOPIC" ] && [ -z "$PLANNER_BUNDLE" ]; then
468
+ emit_blocked "AUTHOR_SURFACES=1 requires escalation_topic_api_name and/or planner_bundle_api_name so the skill knows which topic/bundle to author. Re-run with those identifiers or omit AUTHOR_SURFACES."
469
+ fi
470
+
471
+ # Authoring lifecycle: publish → retrieve (metadata --unzip) → deactivate → XSD-ordered patch →
472
+ # deploy (--metadata-dir) → activate (fail closed) → re-read latest BotVersion. See references/classifier-contracts.md.
473
+
474
+ # 1) Publish so the compiled planner bundle exists/refreshed.
475
+ if ! sf agent publish authoring-bundle --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1; then
476
+ emit_blocked "AUTHOR_SURFACES: 'sf agent publish authoring-bundle' failed for '$AGENT_DN'; cannot author escalation surfaces."
477
+ fi
478
+
479
+ # 2) Retrieve in metadata format + --unzip (else --target-metadata-dir yields a ZIP); find the XML + package.xml root.
480
+ APLUGIN=""; ABUNDLE=""; AROOT_PLUGIN=""; AROOT_BUNDLE=""
481
+ if [ -n "$ESCALATION_TOPIC" ]; then
482
+ ARET="$TWORK/author-plugin"; mkdir -p "$ARET"
483
+ if ! sf project retrieve start --target-org "$ORG" --metadata "GenAiPlugin:$ESCALATION_TOPIC" \
484
+ --target-metadata-dir "$ARET" --unzip >/dev/null 2>&1; then
485
+ emit_blocked "AUTHOR_SURFACES: failed to retrieve GenAiPlugin '$ESCALATION_TOPIC' (metadata format)."
486
+ fi
487
+ APLUGIN=$(find "$ARET" -type f \( -name '*.genAiPlugin' -o -name '*.genAiPlugin-meta.xml' \) 2>/dev/null | head -1)
488
+ AROOT_PLUGIN=$(dirname "$(find "$ARET" -type f -name package.xml 2>/dev/null | head -1)" 2>/dev/null || echo "")
489
+ if [ -z "$APLUGIN" ] || [ -z "$AROOT_PLUGIN" ] || [ ! -d "$AROOT_PLUGIN" ]; then
490
+ emit_blocked "AUTHOR_SURFACES: retrieved GenAiPlugin '$ESCALATION_TOPIC' but found no metadata-format file/package.xml to patch."
491
+ fi
492
+ fi
493
+ if [ -n "$PLANNER_BUNDLE" ]; then
494
+ ARETB="$TWORK/author-bundle"; mkdir -p "$ARETB"
495
+ if ! sf project retrieve start --target-org "$ORG" --metadata "GenAiPlannerBundle:$PLANNER_BUNDLE" \
496
+ --target-metadata-dir "$ARETB" --unzip >/dev/null 2>&1; then
497
+ emit_blocked "AUTHOR_SURFACES: failed to retrieve GenAiPlannerBundle '$PLANNER_BUNDLE' (metadata format)."
498
+ fi
499
+ ABUNDLE=$(find "$ARETB" -type f \( -name '*.genAiPlannerBundle' -o -name '*.genAiPlannerBundle-meta.xml' \) 2>/dev/null | head -1)
500
+ AROOT_BUNDLE=$(dirname "$(find "$ARETB" -type f -name package.xml 2>/dev/null | head -1)" 2>/dev/null || echo "")
501
+ if [ -z "$ABUNDLE" ] || [ -z "$AROOT_BUNDLE" ] || [ ! -d "$AROOT_BUNDLE" ]; then
502
+ emit_blocked "AUTHOR_SURFACES: retrieved GenAiPlannerBundle '$PLANNER_BUNDLE' but found no metadata-format file/package.xml to patch."
503
+ fi
504
+ fi
505
+
506
+ # 3) Deactivate (deploys fail while active).
507
+ if ! { echo "Y" | sf agent deactivate --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1; }; then
508
+ emit_blocked "AUTHOR_SURFACES: 'sf agent deactivate' failed for '$AGENT_DN'; refusing to deploy while the agent is active."
509
+ fi
510
+ AGENT_ACTIVE=false
511
+
512
+ # 4) Patch (XSD-ordered) + deploy (--metadata-dir); on failure, best-effort reactivate then block.
513
+ if [ -n "$APLUGIN" ]; then
514
+ if ! node "$SCRIPT_DIR/patch-escalation-surfaces.mjs" canEscalate "$APLUGIN" >/dev/null 2>&1; then
515
+ echo "Y" | sf agent activate --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1 || true
516
+ emit_blocked "AUTHOR_SURFACES: could not safely set canEscalate=true on GenAiPlugin '$ESCALATION_TOPIC' (no safe XSD-ordered insertion point)."
517
+ fi
518
+ if ! DEPLOY_OUT=$(sf project deploy start --target-org "$ORG" --metadata-dir "$AROOT_PLUGIN" --json 2>/dev/null); then
519
+ echo "Y" | sf agent activate --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1 || true
520
+ emit_blocked "AUTHOR_SURFACES: failed to deploy canEscalate patch for '$ESCALATION_TOPIC'. Raw: $(echo "$DEPLOY_OUT" | head -c 400)"
521
+ fi
522
+ fi
523
+ if [ -n "$ABUNDLE" ]; then
524
+ if ! node "$SCRIPT_DIR/patch-escalation-surfaces.mjs" outboundRoute "$ABUNDLE" "$FLOW_DEV_NAME" >/dev/null 2>&1; then
525
+ echo "Y" | sf agent activate --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1 || true
526
+ emit_blocked "AUTHOR_SURFACES: could not safely wire outboundRouteConfigs on a Messaging surface of '$PLANNER_BUNDLE' (no Messaging-class planner surface / <surface> anchor found)."
527
+ fi
528
+ if ! DEPLOY_OUT=$(sf project deploy start --target-org "$ORG" --metadata-dir "$AROOT_BUNDLE" --json 2>/dev/null); then
529
+ echo "Y" | sf agent activate --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1 || true
530
+ emit_blocked "AUTHOR_SURFACES: failed to deploy outboundRouteConfigs patch for '$PLANNER_BUNDLE'. Raw: $(echo "$DEPLOY_OUT" | head -c 400)"
531
+ fi
532
+ fi
533
+
534
+ # 5) Reactivate - FAIL CLOSED (never swallow with || true; an inactive agent cannot serve).
535
+ if ! { echo "Y" | sf agent activate --api-name "$AGENT_DN" --target-org "$ORG" --json >/dev/null 2>&1; }; then
536
+ emit_blocked "AUTHOR_SURFACES: 'sf agent activate' failed for '$AGENT_DN' after deploying the escalation surfaces; the agent is left INACTIVE." \
537
+ "Setup → Agentforce → Agents → '$AGENT_DN' → activate the latest version"
538
+ fi
539
+
540
+ # 6) Re-verify the LATEST BotVersion is Active (never trust the pre-authoring AGENT_ACTIVE value).
541
+ BOT_JSON_AFTER=$(sf data query --target-org "$ORG" \
542
+ --query "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE DeveloperName='$AGENT_DN'" \
543
+ --json 2>/dev/null)
544
+ echo "$BOT_JSON_AFTER" > "$TWORK/bot-after.json"
545
+ AGENT_VERDICT_AFTER=$(node "$SCRIPT_DIR/classify-agent-active.mjs" "$TWORK/bot-after.json" "$AGENT_DN" 2>/dev/null || echo '{"ready":false}')
546
+ if [ "$(echo "$AGENT_VERDICT_AFTER" | jq -r '.ready')" = "true" ]; then
547
+ AGENT_ACTIVE=true
548
+ else
549
+ AGENT_ACTIVE=false
550
+ emit_blocked "AUTHOR_SURFACES: after authoring + reactivation the latest BotVersion for '$AGENT_DN' is not Active; refusing to report CONFIGURED against a stale/inactive version." \
551
+ "Setup → Agentforce → Agents → '$AGENT_DN' → activate the latest version"
552
+ fi
553
+ fi
554
+
555
+ # Step 7 - Verify doc-driven surfaces when their identifiers were provided
556
+ CAN_ESCALATE=false
557
+ if [ -n "$ESCALATION_TOPIC" ]; then
558
+ RET="$TWORK/plugin"
559
+ mkdir -p "$RET"
560
+ # --unzip: without it --target-metadata-dir yields a ZIP and the grep below scans binary, so a
561
+ # correctly configured topic is misreported as incomplete (mirrors the authoring path at Step 6).
562
+ if sf project retrieve start --target-org "$ORG" --metadata "GenAiPlugin:$ESCALATION_TOPIC" \
563
+ --target-metadata-dir "$RET" --unzip >/dev/null 2>&1; then
564
+ if grep -Rql "<canEscalate>true</canEscalate>" "$RET" 2>/dev/null; then
565
+ CAN_ESCALATE=true
566
+ fi
567
+ fi
568
+ fi
569
+
570
+ OUTBOUND_ROUTE_NAME=""
571
+ OUTBOUND_ROUTE_TYPE=""
572
+ OUTBOUND_ROUTE_SURFACE=""
573
+ OUTBOUND_ROUTE_SAME_BLOCK=false
574
+ OUTBOUND_ROUTE_MESSAGING_SURFACE=false
575
+ if [ -n "$PLANNER_BUNDLE" ]; then
576
+ RETB="$TWORK/bundle"
577
+ mkdir -p "$RETB"
578
+ # --unzip: else --target-metadata-dir yields a ZIP and the find/cat below reads binary, so a wired
579
+ # outbound route is misreported as missing.
580
+ if sf project retrieve start --target-org "$ORG" --metadata "GenAiPlannerBundle:$PLANNER_BUNDLE" \
581
+ --target-metadata-dir "$RETB" --unzip >/dev/null 2>&1; then
582
+ # Couple outboundRouteName + outboundRouteType within the same planner-surface block.
583
+ BUNDLE_CAT="$TWORK/bundle-cat.xml"
584
+ find "$RETB" -type f -name '*.xml' -exec cat {} \; > "$BUNDLE_CAT" 2>/dev/null || true
585
+ ROUTE_JSON=$(node "$SCRIPT_DIR/extract-outbound-route.mjs" "$BUNDLE_CAT" "$FLOW_DEV_NAME" 2>/dev/null || echo '{}')
586
+ OUTBOUND_ROUTE_NAME=$(echo "$ROUTE_JSON" | jq -r '.name // ""')
587
+ OUTBOUND_ROUTE_TYPE=$(echo "$ROUTE_JSON" | jq -r '.type // ""')
588
+ OUTBOUND_ROUTE_SURFACE=$(echo "$ROUTE_JSON" | jq -r '.surface // ""')
589
+ OUTBOUND_ROUTE_SAME_BLOCK=$(echo "$ROUTE_JSON" | jq -r '.sameBlock // false')
590
+ OUTBOUND_ROUTE_MESSAGING_SURFACE=$(echo "$ROUTE_JSON" | jq -r '.messagingSurface // false')
591
+ fi
592
+ fi
593
+
594
+ # Step 7b (NGA) - Verify the next-gen escalation surface by retrieving the agent's AiAuthoringBundle and
595
+ # scanning its Agent Script: a Service agent needs a reachable @utils.escalate (with connection messaging);
596
+ # an Employee agent (no messaging) needs a create-record action. Authoring the surface itself is delegated
597
+ # to agentforce-generate (see next_steps); this skill verifies + reports. Details: references/nga-escalation.md.
598
+ NGA_SURFACE_PRESENT=false
599
+ NGA_AGENT_TYPE="unknown"
600
+ if [ "$AUTHORING_MODEL" = "nga" ]; then
601
+ NRET="$TWORK/nga-bundle"
602
+ mkdir -p "$NRET"
603
+ # --unzip: else --target-metadata-dir yields a ZIP and the NGA classifier scans binary, so a present
604
+ # escalation surface is misreported as missing.
605
+ if sf project retrieve start --target-org "$ORG" --metadata "AiAuthoringBundle:$AGENT_DN" \
606
+ --target-metadata-dir "$NRET" --unzip >/dev/null 2>&1; then
607
+ NGA_JSON=$(node "$SCRIPT_DIR/classify-nga-escalation.mjs" "$NRET" 2>/dev/null || echo '{}')
608
+ NGA_SURFACE_PRESENT=$(echo "$NGA_JSON" | jq -r '.escalationSurfacePresent // false')
609
+ NGA_AGENT_TYPE=$(echo "$NGA_JSON" | jq -r '.agentType // "unknown"')
610
+ fi
611
+ fi
612
+
613
+ THRESHOLD_FLAG=false
614
+ [ "$THRESHOLD_AUTHORED" = "1" ] && THRESHOLD_FLAG=true
615
+
616
+ # Render the numeric threshold into the directive template (echoed in the verdict as evidence).
617
+ THRESHOLD_TEMPLATE="$SCRIPT_DIR/../assets/escalation-thresholds.instructions.md"
618
+ DIRECTIVE_RENDERED=false
619
+ if [ -f "$THRESHOLD_TEMPLATE" ]; then
620
+ RENDERED_DIRECTIVE="$TWORK/escalation-thresholds.rendered.md"
621
+ sed "s/__DEFAULT_FAILURES__/${DEFAULT_FAILURE_THRESHOLD}/g" "$THRESHOLD_TEMPLATE" > "$RENDERED_DIRECTIVE" 2>/dev/null \
622
+ && DIRECTIVE_RENDERED=true
623
+ fi
624
+
625
+ # Step 8 - Assemble evidence + render the deterministic verdict
626
+ EVID="$TWORK/evidence.json"
627
+ jq -n \
628
+ --argjson canEscalate "$CAN_ESCALATE" \
629
+ --arg outboundRouteName "$OUTBOUND_ROUTE_NAME" \
630
+ --arg outboundRouteType "$OUTBOUND_ROUTE_TYPE" \
631
+ --arg outboundRouteSurface "$OUTBOUND_ROUTE_SURFACE" \
632
+ --argjson outboundRouteSameBlock "$OUTBOUND_ROUTE_SAME_BLOCK" \
633
+ --argjson outboundRouteMessagingSurface "$OUTBOUND_ROUTE_MESSAGING_SURFACE" \
634
+ --arg expectedFlowName "$FLOW_DEV_NAME" \
635
+ --arg flowActiveVersionId "$FLOW_ACTIVE_ID" \
636
+ --arg queueId "$QUEUE_ID" \
637
+ --argjson queueSobjectPresent "$QUEUE_SOBJECT_PRESENT" \
638
+ --argjson queueHasActiveDirectUserMember "$QUEUE_HAS_ACTIVE_DIRECT_USER_MEMBER" \
639
+ --argjson queueMemberCount "$MEMBER_COUNT" \
640
+ --argjson queueRoutingConfigPresent "$QUEUE_ROUTING_CONFIG_PRESENT" \
641
+ --argjson queueRoutingConfigBound "$QUEUE_ROUTING_CONFIG_BOUND" \
642
+ --argjson agentActive "$AGENT_ACTIVE" \
643
+ --argjson thresholdAuthored "$THRESHOLD_FLAG" \
644
+ --argjson defaultFailureThreshold "$DEFAULT_FAILURE_THRESHOLD" \
645
+ --arg authoringModel "$AUTHORING_MODEL" \
646
+ --argjson ngaEscalationSurfacePresent "$NGA_SURFACE_PRESENT" \
647
+ --arg ngaAgentType "$NGA_AGENT_TYPE" \
648
+ '{canEscalate:$canEscalate, outboundRouteName:$outboundRouteName, outboundRouteType:$outboundRouteType,
649
+ outboundRouteSurface:$outboundRouteSurface, outboundRouteSameBlock:$outboundRouteSameBlock,
650
+ outboundRouteMessagingSurface:$outboundRouteMessagingSurface, expectedFlowName:$expectedFlowName,
651
+ flowActiveVersionId:$flowActiveVersionId, queueId:$queueId, queueSobjectPresent:$queueSobjectPresent,
652
+ queueHasActiveDirectUserMember:$queueHasActiveDirectUserMember, queueMemberCount:$queueMemberCount,
653
+ queueRoutingConfigPresent:$queueRoutingConfigPresent, queueRoutingConfigBound:$queueRoutingConfigBound,
654
+ agentActive:$agentActive, thresholdAuthored:$thresholdAuthored,
655
+ defaultFailureThreshold:$defaultFailureThreshold, authoringModel:$authoringModel,
656
+ ngaEscalationSurfacePresent:$ngaEscalationSurfacePresent, ngaAgentType:$ngaAgentType}' > "$EVID"
657
+
658
+ CONFIG_VERDICT=$(node "$SCRIPT_DIR/verify-escalation-config.mjs" "$EVID")
659
+ # Status mirrors the deterministic three-state verdict: CONFIGURED (routing + surface + policy),
660
+ # ROUTING_CONFIGURED_POLICY_PENDING (routing/surface done but failure-threshold directive missing),
661
+ # or INCOMPLETE. A missing policy can no longer masquerade as fully CONFIGURED.
662
+ STATUS=$(echo "$CONFIG_VERDICT" | jq -r '.verdict')
663
+ case "$STATUS" in
664
+ CONFIGURED|ROUTING_CONFIGURED_POLICY_PENDING|INCOMPLETE) ;;
665
+ *) STATUS="INCOMPLETE" ;;
666
+ esac
667
+
668
+ jq -n \
669
+ --arg skill "$SKILL_NAME" \
670
+ --arg status "$STATUS" \
671
+ --arg agent "$AGENT_DN" \
672
+ --arg queue "$QUEUE_DN" \
673
+ --arg qid "$QUEUE_ID" \
674
+ --argjson qmembers "$MEMBER_COUNT" \
675
+ --arg flow "$FLOW_DEV_NAME" \
676
+ --arg flowActive "$FLOW_ACTIVE_ID" \
677
+ --arg topic "$ESCALATION_TOPIC" \
678
+ --arg bundle "$PLANNER_BUNDLE" \
679
+ --argjson config "$CONFIG_VERDICT" \
680
+ --argjson thresholdValue "$DEFAULT_FAILURE_THRESHOLD" \
681
+ --argjson thresholdAuthored "$THRESHOLD_FLAG" \
682
+ --argjson directiveRendered "$DIRECTIVE_RENDERED" \
683
+ --arg authoringModel "$AUTHORING_MODEL" \
684
+ --argjson ngaSurface "$NGA_SURFACE_PRESENT" \
685
+ --arg ngaAgentType "$NGA_AGENT_TYPE" \
686
+ '{
687
+ skill: $skill,
688
+ status: $status,
689
+ authoring_model: $authoringModel,
690
+ agent: ({developer_name:$agent, active:true} + (if $authoringModel=="nga" then {type:$ngaAgentType, escalation_surface_present:$ngaSurface} else {} end)),
691
+ queue: {developer_name:$queue, id:$qid, active_direct_user_member_count:$qmembers},
692
+ outbound_flow: {api_name:$flow, active_version_id:(if $flowActive=="" then null else $flowActive end)},
693
+ escalation_topic: (if $topic=="" then null else $topic end),
694
+ planner_bundle: (if $bundle=="" then null else $bundle end),
695
+ threshold: {default_failures:$thresholdValue, authored:$thresholdAuthored, directive_rendered:$directiveRendered},
696
+ config_verification: $config,
697
+ next_steps: (if $status=="CONFIGURED" then []
698
+ elif $status=="ROUTING_CONFIGURED_POLICY_PENDING" then
699
+ (["Routing and the agent escalation surface are verified, but the failure-threshold escalation policy (default two-failure / password-reset override) is not yet authored or confirmed. Author the threshold directive (AUTHOR_SURFACES=1) or confirm it in the topic instructions, then re-run to reach CONFIGURED. See references/failure-policy.md."])
700
+ elif $authoringModel=="nga" then
701
+ (["Deterministic routing infra is verified. Author the NGA escalation surface with agentforce-generate: for a Service agent add a reachable {!@utils.escalate} action (needs connection messaging:); for an Employee agent add a create-record (Case/Incident) action that Omni-routes to the queue. See references/nga-escalation.md, then re-run to reach CONFIGURED."]) else
702
+ (["Author the doc-driven surfaces listed in config_verification.missing (see references/escalation-mechanism.md and failure-policy.md), then re-run to reach CONFIGURED."]) end),
703
+ manual_actions: [],
704
+ blocking_issue: null
705
+ }'