@salesforce/afv-skills 1.47.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 (160) hide show
  1. package/package.json +1 -1
  2. package/skills/education-cloud-domain-configure/SKILL.md +234 -0
  3. package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
  4. package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
  5. package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
  6. package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
  7. package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
  8. package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
  9. package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
  10. package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
  11. package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
  12. package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
  13. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
  14. package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
  15. package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
  16. package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
  17. package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
  18. package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
  19. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +4 -3
  20. package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
  21. package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
  22. package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
  23. package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
  24. package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
  25. package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
  26. package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
  27. package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
  28. package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
  29. package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
  30. package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
  31. package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
  32. package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
  33. package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
  34. package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
  35. package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
  36. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
  37. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
  38. package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
  39. package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
  40. package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
  41. package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
  42. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
  43. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
  44. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
  45. package/skills/service-omni-agent-users-create/SKILL.md +96 -0
  46. package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
  47. package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
  48. package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
  49. package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
  50. package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
  51. package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
  52. package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
  53. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
  54. package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
  55. package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
  56. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
  57. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
  58. package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
  59. package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
  60. package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
  61. package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
  62. package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
  63. package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
  64. package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
  65. package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
  66. package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
  67. package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
  68. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
  69. package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
  70. package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
  71. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
  72. package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
  73. package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
  74. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
  75. package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
  76. package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
  77. package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
  78. package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
  79. package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
  80. package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
  81. package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
  82. package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
  83. package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
  84. package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
  85. package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
  86. package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
  87. package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
  88. package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
  89. package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
  90. package/skills/service-omni-queue-deploy/SKILL.md +91 -0
  91. package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
  92. package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
  93. package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
  94. package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
  95. package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
  96. package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
  97. package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
  98. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
  99. package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
  100. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
  101. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
  102. package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
  103. package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
  104. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
  105. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
  106. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
  107. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
  108. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
  109. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
  110. package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
  111. package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
  112. package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
  113. package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
  114. package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
  115. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
  116. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
  117. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
  118. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
  119. package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
  120. package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
  121. package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
  122. package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
  123. package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
  124. package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
  125. package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
  126. package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
  127. package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
  128. package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
  129. package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
  130. package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
  131. package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
  132. package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
  133. package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
  134. package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
  135. package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
  136. package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
  137. package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
  138. package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
  139. package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
  140. package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
  141. package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
  142. package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
  143. package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
  144. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
  145. package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
  146. package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
  147. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
  148. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
  149. package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
  150. package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
  151. package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
  152. package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
  153. package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
  154. package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
  155. package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
  156. package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
  157. package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
  158. package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
  159. package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
  160. package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
@@ -0,0 +1,298 @@
1
+ #!/usr/bin/env bash
2
+ # run-create.sh - INTERNAL (invoke via detect-and-create.sh, which enforces the safe_to_write guard): render Apex tokens, execute, parse created users; passwords set via separate System.setPassword, never extracted from logs. Contract + security: SKILL.md + references/apex-template-notes.md.
3
+
4
+ set -euo pipefail
5
+
6
+ if [ $# -lt 4 ]; then
7
+ echo '{"error":"Usage: bash run-create.sh <org-alias> <count> <profile-id> <org-suffix>"}' >&2
8
+ exit 1
9
+ fi
10
+
11
+ ORG="$1"
12
+ COUNT="$2"
13
+ PROFILE_ID="$3"
14
+ SUFFIX="$4"
15
+
16
+ # Validate every arg BEFORE it is sed-substituted into anonymous Apex. These tokens are interpolated
17
+ # into executable Apex, so anything outside a strict grammar (Apex/sed metacharacters, code) is
18
+ # rejected up front rather than executed.
19
+ if ! [[ "$COUNT" =~ ^[1-9][0-9]{0,2}$ ]]; then
20
+ echo "{\"error\":\"Invalid count '$COUNT' (expected a positive integer 1-999).\"}" >&2; exit 1
21
+ fi
22
+ if ! [[ "$PROFILE_ID" =~ ^[A-Za-z0-9]{15}([A-Za-z0-9]{3})?$ ]]; then
23
+ echo "{\"error\":\"Invalid profile-id '$PROFILE_ID' (expected a 15- or 18-char Salesforce Id).\"}" >&2; exit 1
24
+ fi
25
+ if ! [[ "$SUFFIX" =~ ^[A-Za-z0-9]{1,18}$ ]]; then
26
+ echo "{\"error\":\"Invalid org-suffix '$SUFFIX' (expected 1-18 alphanumeric chars).\"}" >&2; exit 1
27
+ fi
28
+
29
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
30
+ TEMPLATE="$SCRIPT_DIR/../assets/create-users.apex.template"
31
+
32
+ if [ ! -f "$TEMPLATE" ]; then
33
+ echo "{\"error\":\"Apex template not found: $TEMPLATE\"}" >&2
34
+ exit 1
35
+ fi
36
+
37
+ WORK="$(mktemp -d)"
38
+ trap 'rm -rf "$WORK"' EXIT
39
+ RESOLVED="$WORK/create-users.resolved.apex"
40
+
41
+ sed -e "s|__COUNT__|${COUNT}|g" \
42
+ -e "s|__PROFILE_ID__|${PROFILE_ID}|g" \
43
+ -e "s|__SUFFIX__|${SUFFIX}|g" \
44
+ "$TEMPLATE" > "$RESOLVED"
45
+
46
+ # Execute Anonymous Apex. sf apex run --json shapes differ for compile-fail / runtime-fail /
47
+ # success / CLI-fail; each is handled below.
48
+ APEX_JSON=$(sf apex run --target-org "$ORG" --file "$RESOLVED" --json 2>/dev/null || true)
49
+
50
+ # Strip C0 control chars: sf embeds an unescaped debug log in .result.logs, which jq>=1.7 rejects.
51
+ # This merges the log to one line; the grep below is anchored to tolerate that.
52
+ APEX_JSON=$(printf '%s' "$APEX_JSON" | tr -d '\000-\037')
53
+
54
+ CLI_STATUS=$(echo "$APEX_JSON" | jq -r '.status // 0' 2>/dev/null || echo "0")
55
+ COMPILE_SUCCESS=$(echo "$APEX_JSON" | jq -r '.result.compiled // false' 2>/dev/null || echo "false")
56
+ EXEC_SUCCESS=$(echo "$APEX_JSON" | jq -r '.result.success // false' 2>/dev/null || echo "false")
57
+
58
+ translate_error() {
59
+ local raw="$1"
60
+ case "$raw" in
61
+ *LICENSE_LIMIT_EXCEEDED*)
62
+ echo "Org has reached its user license limit. Free up a license slot (deactivate unused users in Setup > Users) or request a larger CDO from OrgFarm."
63
+ ;;
64
+ *DUPLICATE_USERNAME*)
65
+ echo "Username collision with another Salesforce org (globally unique constraint). Rare - retry the skill; if it persists, another agent may be concurrently running against the same suffix."
66
+ ;;
67
+ *INVALID_EMAIL_ADDRESS*)
68
+ echo "Salesforce rejected the @example.com email. This should never happen - likely a bug in the skill's Apex template. Escalate."
69
+ ;;
70
+ *INSUFFICIENT_ACCESS*|*NoAccessException*)
71
+ echo "Executing user lacks required permission. Ensure the sf-CLI-authenticated user has System Administrator profile or equivalent."
72
+ ;;
73
+ *INVALID_LOGIN*|*INVALID_SESSION_ID*)
74
+ echo "Salesforce session expired. Re-authenticate: sf org login web -a $ORG -r <my-domain-url>"
75
+ ;;
76
+ *)
77
+ echo "$raw"
78
+ ;;
79
+ esac
80
+ }
81
+
82
+ if [ "$CLI_STATUS" != "0" ]; then
83
+ CLI_ERR=$(echo "$APEX_JSON" | jq -r '.message // "Unknown CLI error"' 2>/dev/null)
84
+ FRIENDLY=$(translate_error "$CLI_ERR")
85
+ echo "{\"error\":\"$FRIENDLY\",\"raw\":$(echo "$CLI_ERR" | jq -R .)}" >&2
86
+ exit 1
87
+ fi
88
+
89
+ if [ "$COMPILE_SUCCESS" != "true" ]; then
90
+ COMPILE_ERR=$(echo "$APEX_JSON" | jq -r '.result.compileProblem // "Unknown compile error"' 2>/dev/null)
91
+ COMPILE_LINE=$(echo "$APEX_JSON" | jq -r '.result.line // "?"' 2>/dev/null)
92
+ echo "{\"error\":\"Apex compile failed at line $COMPILE_LINE: $COMPILE_ERR\"}" >&2
93
+ exit 1
94
+ fi
95
+
96
+ if [ "$EXEC_SUCCESS" != "true" ]; then
97
+ EXEC_ERR=$(echo "$APEX_JSON" | jq -r '.result.exceptionMessage // "Unknown execution error"' 2>/dev/null)
98
+ if [ -z "$EXEC_ERR" ] || [ "$EXEC_ERR" = "Unknown execution error" ] || [ "$EXEC_ERR" = "null" ]; then
99
+ EXEC_ERR=$(echo "$APEX_JSON" | jq -r '.message // "Unknown execution error"' 2>/dev/null)
100
+ fi
101
+ FRIENDLY=$(translate_error "$EXEC_ERR")
102
+ echo "{\"error\":\"$FRIENDLY\",\"raw\":$(echo "$EXEC_ERR" | jq -R .)}" >&2
103
+ exit 1
104
+ fi
105
+
106
+ # Parse AGENT_USER_CREATED|<userId>|<username>|<email> from the log; decode the HTML-encoded pipe
107
+ # (&#124;) that sf emits, else the grep misses every user.
108
+ LOG=$(echo "$APEX_JSON" | jq -r '.result.logs // ""' 2>/dev/null | sed 's/&#124;/|/g')
109
+
110
+ # Anchor on a real 005 Id + @example.com domain so the match ignores the Apex source echoed in the merged log.
111
+ CREATED_RAW=$(echo "$LOG" | grep -oE 'AGENT_USER_CREATED\|005[a-zA-Z0-9]{15}\|[^|]+@example\.com\|[^|]+@example\.com' || true)
112
+
113
+ if [ -z "$CREATED_RAW" ]; then
114
+ CREATED_USERS_JSON='[]'
115
+ else
116
+ CREATED_USERS_JSON=$(echo "$CREATED_RAW" \
117
+ | awk -F'|' '
118
+ BEGIN { print "[" }
119
+ NR > 1 { printf "," }
120
+ {
121
+ printf "{\"id\":\"%s\",\"username\":\"%s\",\"email\":\"%s\"}", $2, $3, $4
122
+ }
123
+ END { print "]" }
124
+ ')
125
+ if ! echo "$CREATED_USERS_JSON" | jq -c '.' >/dev/null 2>&1; then
126
+ echo "{\"error\":\"Internal parse error: AGENT_USER_CREATED lines produced invalid JSON.\",\"raw_lines\":$(echo "$CREATED_RAW" | jq -Rs .)}" >&2
127
+ exit 1
128
+ fi
129
+ CREATED_USERS_JSON=$(echo "$CREATED_USERS_JSON" | jq -c '.')
130
+ fi
131
+
132
+ # Set each User's password via System.setPassword (the only working path for Apex-inserted users).
133
+ # Fail-closed on the TraceFlag gate below; on failure the User stays active + reset_required.
134
+ # Full security rationale: references/apex-template-notes.md.
135
+ ENRICHED_USERS='[]'
136
+ NEEDS_RESET='[]'
137
+ ROLLED_BACK='[]'
138
+
139
+ # Generated passwords are written to a restricted (0600) file and NEVER returned in stdout, so
140
+ # plaintext cannot enter an AI/tool transcript when this leaf is invoked directly. The coordinator
141
+ # passes OMNI_CREDENTIALS_FILE/OMNI_CREDENTIALS_KEY to escrow into its own file; standalone runs
142
+ # default to ./CREDENTIALS.json. File format: { "<key>": [ {username, password}, ... ] }.
143
+ # Secrets never touch argv/env (both visible via process inspection): each {username,password} pair is
144
+ # appended as one JSON line to this 0600 temp file (inside $WORK, mode 0700) and fed to jq only via
145
+ # stdin / --slurpfile. $WORK is removed by the EXIT trap, so the plaintext file never persists.
146
+ CRED_PAIRS_FILE="$WORK/cred-pairs.jsonl"
147
+ : > "$CRED_PAIRS_FILE"; chmod 600 "$CRED_PAIRS_FILE" 2>/dev/null || true
148
+ CRED_COUNT=0
149
+ CRED_FILE="${OMNI_CREDENTIALS_FILE:-$PWD/CREDENTIALS.json}"
150
+ CRED_KEY="${OMNI_CREDENTIALS_KEY:-service-omni-agent-users-create}"
151
+ CRED_WRITTEN="false"
152
+ write_credentials() {
153
+ # $1 = path to a JSONL file of {username,password} objects. Secret-bearing pairs are read via
154
+ # --slurpfile (a file, never argv); the existing credentials file is piped in via stdin.
155
+ local pairs_file="$1" existing='{}' tmp
156
+ [ -f "$CRED_FILE" ] && existing=$(cat "$CRED_FILE" 2>/dev/null || echo '{}')
157
+ tmp=$(mktemp) || return 1
158
+ if ! printf '%s' "$existing" | jq --arg k "$CRED_KEY" --slurpfile c "$pairs_file" '. + {($k): $c}' > "$tmp" 2>/dev/null || [ ! -s "$tmp" ]; then
159
+ rm -f "$tmp"; return 1
160
+ fi
161
+ if ! mv "$tmp" "$CRED_FILE" 2>/dev/null; then rm -f "$tmp"; return 1; fi
162
+ chmod 600 "$CRED_FILE" 2>/dev/null || true
163
+ return 0
164
+ }
165
+
166
+ set_password_via_apex() {
167
+ # $1=user_id $2=plaintext_password. Echoes "ok" on success, else the exception text.
168
+ local uid="$1" pw="$2" apex_file res
169
+ apex_file="$WORK/setpw.apex"
170
+ # uid (18-char Id) and pw ([A-Za-z0-9]) can't break out of the Apex string literal.
171
+ printf "System.setPassword('%s', '%s');\n" "$uid" "$pw" > "$apex_file"
172
+ res=$(sf apex run --target-org "$ORG" --file "$apex_file" --json 2>/dev/null | tr -d '\000-\037' || true)
173
+ rm -f "$apex_file"
174
+ if [ "$(echo "$res" | jq -r '.result.success // false' 2>/dev/null)" = "true" ]; then
175
+ echo "ok"
176
+ else
177
+ echo "$res" | jq -r '.result.exceptionMessage // .message // "setPassword failed"' 2>/dev/null
178
+ fi
179
+ }
180
+
181
+ # TraceFlag gate (fail-closed): prove no active TraceFlag exists for the running user BEFORE any
182
+ # System.setPassword, else a password literal could persist to a queryable ApexLog. Cannot prove safe
183
+ # => no password set, users left active + reset_required. See references/apex-template-notes.md.
184
+ TRACEFLAG_SAFE=false
185
+ TRACEFLAG_REASON="TraceFlag state could not be determined"
186
+ ME_UNAME=$(sf org display --target-org "$ORG" --json 2>/dev/null | jq -r '.result.username // empty' 2>/dev/null || true)
187
+ if [ -z "${ME_UNAME:-}" ]; then
188
+ TRACEFLAG_REASON="could not resolve the running user (sf org display failed)"
189
+ else
190
+ ME_UID=$(sf data query --target-org "$ORG" \
191
+ --query "SELECT Id FROM User WHERE Username='$ME_UNAME' LIMIT 1" \
192
+ --json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || true)
193
+ if [ -z "${ME_UID:-}" ]; then
194
+ TRACEFLAG_REASON="could not resolve the running user's Id"
195
+ else
196
+ NOW_UTC=$(date -u +%Y-%m-%dT%H:%M:%SZ)
197
+ if TF_JSON=$(sf data query --target-org "$ORG" --use-tooling-api \
198
+ --query "SELECT Id FROM TraceFlag WHERE TracedEntityId='$ME_UID' AND ExpirationDate > $NOW_UTC" \
199
+ --json 2>/dev/null) && [ -n "$TF_JSON" ]; then
200
+ TF_STATUS=$(echo "$TF_JSON" | jq -r '.status // 1' 2>/dev/null || echo 1)
201
+ if [ "$TF_STATUS" = "0" ]; then
202
+ TF_COUNT=$(echo "$TF_JSON" | jq -r 'if (.result.totalSize != null) then .result.totalSize elif (.result.records != null) then (.result.records | length) else "unknown" end' 2>/dev/null || echo "unknown")
203
+ if [ "$TF_COUNT" = "0" ]; then
204
+ TRACEFLAG_SAFE=true
205
+ TRACEFLAG_REASON=""
206
+ elif [ "$TF_COUNT" = "unknown" ]; then
207
+ TRACEFLAG_REASON="the TraceFlag query returned an unparseable result"
208
+ else
209
+ TRACEFLAG_REASON="an active Apex debug TraceFlag exists for the running user ($TF_COUNT found)"
210
+ fi
211
+ else
212
+ TRACEFLAG_REASON="the TraceFlag Tooling API query returned an error status"
213
+ fi
214
+ else
215
+ TRACEFLAG_REASON="the TraceFlag Tooling API query failed"
216
+ fi
217
+ fi
218
+ fi
219
+
220
+ if [ "$(echo "$CREATED_USERS_JSON" | jq -r 'length')" -gt 0 ]; then
221
+ while IFS= read -r row; do
222
+ USER_ID=$(echo "$row" | jq -r '.id')
223
+ UNAME=$(echo "$row" | jq -r '.username')
224
+ EMAIL=$(echo "$row" | jq -r '.email')
225
+
226
+ if [ "$TRACEFLAG_SAFE" != "true" ]; then
227
+ # Fail closed: skip setPassword, keep the User active + flag for manual reset.
228
+ NEEDS_RESET=$(echo "$NEEDS_RESET" | jq -c --arg u "$UNAME" '. + [$u]')
229
+ ENRICHED_USERS=$(echo "$ENRICHED_USERS" | jq -c \
230
+ --arg id "$USER_ID" --arg u "$UNAME" --arg e "$EMAIL" \
231
+ '. + [{id: $id, username: $u, email: $e, password: null, password_status: "reset_required", password_error: "skipped: TraceFlag safety could not be proven (fail-closed)"}]')
232
+ continue
233
+ fi
234
+
235
+ # Strong [A-Za-z0-9] password + fixed mixed-class suffix (Salesforce complexity). The || true
236
+ # swallows tr's SIGPIPE (head closes the pipe) so pipefail+set -e don't abort mid-insert.
237
+ RAND="$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 14 || true)"
238
+ PW="${RAND}aB3"
239
+ SETPW_RESULT=$(set_password_via_apex "$USER_ID" "$PW")
240
+
241
+ if [ "$SETPW_RESULT" = "ok" ]; then
242
+ # Feed the SECRET via stdin (never argv/env); username is not secret so --arg is fine.
243
+ printf '%s' "$PW" | jq -Rsc --arg u "$UNAME" '{username: $u, password: .}' >> "$CRED_PAIRS_FILE"
244
+ CRED_COUNT=$((CRED_COUNT + 1))
245
+ ENRICHED_USERS=$(echo "$ENRICHED_USERS" | jq -c \
246
+ --arg id "$USER_ID" --arg u "$UNAME" --arg e "$EMAIL" \
247
+ '. + [{id: $id, username: $u, email: $e, password: null, password_status: "generated"}]')
248
+ else
249
+ # Non-destructive: keep the active User, flag it for a manual password reset.
250
+ NEEDS_RESET=$(echo "$NEEDS_RESET" | jq -c --arg u "$UNAME" '. + [$u]')
251
+ ENRICHED_USERS=$(echo "$ENRICHED_USERS" | jq -c \
252
+ --arg id "$USER_ID" --arg u "$UNAME" --arg e "$EMAIL" --arg r "$SETPW_RESULT" \
253
+ '. + [{id: $id, username: $u, email: $e, password: null, password_status: "reset_required", password_error: $r}]')
254
+ fi
255
+ done < <(echo "$CREATED_USERS_JSON" | jq -c '.[]')
256
+ fi
257
+
258
+ # Escrow generated passwords to the restricted file; never emit plaintext in stdout. Fail-closed:
259
+ # if escrow fails, the passwords were set on-org but cannot be handed back, so flag those users for a
260
+ # manual reset rather than leaking the plaintext.
261
+ CRED_WARNING=""
262
+ if [ "$CRED_COUNT" -gt 0 ]; then
263
+ if write_credentials "$CRED_PAIRS_FILE"; then
264
+ CRED_WRITTEN="true"
265
+ else
266
+ CRED_WARNING="Passwords were set on the created users but could NOT be escrowed to the credentials file ($CRED_FILE); plaintext was deliberately NOT emitted to stdout. Reset these users' passwords in Setup > Users."
267
+ # Escrow failed → the plaintext is unrecoverable, so these users genuinely need a manual reset:
268
+ # add them to users_needing_password_reset (not just the per-user status) before flipping status.
269
+ ESCROW_FAILED_UNAMES=$(echo "$ENRICHED_USERS" | jq -c '[ .[] | select(.password_status=="generated") | .username ]')
270
+ NEEDS_RESET=$(jq -c -n --argjson a "$NEEDS_RESET" --argjson b "$ESCROW_FAILED_UNAMES" '$a + $b | unique')
271
+ ENRICHED_USERS=$(echo "$ENRICHED_USERS" | jq -c '[ .[] | if .password_status=="generated" then (.password_status="reset_required" | .password_error="escrow_failed") else . end ]')
272
+ fi
273
+ fi
274
+
275
+ # Surface why password generation was skipped when the TraceFlag gate failed.
276
+ SECURITY_WARNING=""
277
+ if [ "$TRACEFLAG_SAFE" != "true" ]; then
278
+ SECURITY_WARNING="Password generation was SKIPPED (fail-closed) because ${TRACEFLAG_REASON}. No System.setPassword was executed, so no plaintext could enter an ApexLog. The created users are ACTIVE and flagged password_status:\"reset_required\" - set their passwords in Setup > Users after confirming no debug TraceFlag is active, or disable the TraceFlag and re-run."
279
+ fi
280
+ [ -n "$CRED_WARNING" ] && SECURITY_WARNING="${SECURITY_WARNING:+$SECURITY_WARNING }$CRED_WARNING"
281
+
282
+ jq -n \
283
+ --argjson created "$ENRICHED_USERS" \
284
+ --argjson needs_reset "$NEEDS_RESET" \
285
+ --argjson rolled_back "$ROLLED_BACK" \
286
+ --arg security_warning "$SECURITY_WARNING" \
287
+ --arg cred_written "$CRED_WRITTEN" \
288
+ --arg cred_file "$CRED_FILE" \
289
+ '{
290
+ created_users: $created,
291
+ created_count: ($created | length),
292
+ users_needing_password_reset: $needs_reset,
293
+ rolled_back_users: $rolled_back,
294
+ credentials_written: ($cred_written == "true"),
295
+ credentials_file: (if $cred_written == "true" then $cred_file else null end),
296
+ security_warning: (if $security_warning == "" then null else $security_warning end),
297
+ security_note: "Passwords are set via anonymous Apex System.setPassword. The plaintext appears in the inline executeAnonymous debug log and, ONLY when a debug-log TraceFlag is active for the running user, in a queryable ApexLog row. To avoid ever persisting plaintext to a log, this script FAILS CLOSED BEFORE the first System.setPassword: it proves via a SOQL-filtered Tooling API query (ExpirationDate > now) that no active TraceFlag exists for the running user. If safety cannot be positively proven - the running user cannot be resolved, the Tooling API query fails or is unparseable, or any active TraceFlag exists - NO password is generated at all; the created users are left ACTIVE and flagged password_status:\"reset_required\", and security_warning explains why. Generated passwords are NEVER returned in this output; they are written to a restricted 0600 credentials_file (default ./CREDENTIALS.json, or OMNI_CREDENTIALS_FILE) so plaintext cannot enter a tool/AI transcript. Read the file once, distribute securely, then delete it. Set passwords for reset_required users in Setup > Users after ensuring no debug TraceFlag is active (or disable the TraceFlag and re-run)."
298
+ }'
@@ -0,0 +1,171 @@
1
+ """Org-free security tests proving the TraceFlag fail-closed contract for both run-create.sh scripts: setPassword runs only when no active TraceFlag exists (else users stay reset_required; a failing TraceFlag query also fails closed), proven via a fake-sf marker file."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import tempfile
8
+ import unittest
9
+ from pathlib import Path
10
+
11
+ SKILLS_ROOT = Path(__file__).resolve().parents[3] # the skills/ directory
12
+ AGENT_SCRIPT = SKILLS_ROOT / "service-omni-agent-users-create/scripts/run-create.sh"
13
+ SUPERVISOR_SCRIPT = SKILLS_ROOT / "service-omni-supervisor-users-create/scripts/run-create.sh"
14
+
15
+ FAKE_SF = r"""#!/usr/bin/env bash
16
+ # Minimal fake `sf` for TraceFlag fail-closed tests. Differentiates the create-users Apex from the
17
+ # System.setPassword Apex by reading the --file contents, and records setPassword submissions.
18
+ cmd="$1"; sub="$2"; shift 2 || true
19
+ all="$*"
20
+ file=""
21
+ prev=""
22
+ for a in "$@"; do
23
+ if [ "$prev" = "--file" ]; then file="$a"; fi
24
+ prev="$a"
25
+ done
26
+ case "$cmd $sub" in
27
+ "apex run")
28
+ # The setPassword Apex is EXACTLY `System.setPassword('id','pw');` on its first line; the
29
+ # create-users template may merely mention setPassword in a comment, so match the first line only.
30
+ if [ -n "$file" ] && head -1 "$file" 2>/dev/null | grep -q '^System.setPassword'; then
31
+ : > "$FAKE_SETPW_MARKER"
32
+ echo '{"status":0,"result":{"compiled":true,"success":true,"logs":""}}'
33
+ else
34
+ echo '{"status":0,"result":{"compiled":true,"success":true,"logs":"AGENT_USER_CREATED|005000000000001AAA|agent1.sfx@example.com|agent1.sfx@example.com SUPERVISOR_CREATED|005000000000002AAA|supervisor1.sfx@example.com|supervisor1.sfx@example.com"}}'
35
+ fi
36
+ ;;
37
+ "org display")
38
+ echo '{"status":0,"result":{"username":"admin@example.com"}}'
39
+ ;;
40
+ "data query")
41
+ if printf '%s' "$all" | grep -q "TraceFlag"; then
42
+ case "${FAKE_TF:-none}" in
43
+ active) echo '{"status":0,"result":{"totalSize":1,"records":[{"Id":"7tf000000000001AAA","ExpirationDate":"2999-01-01T12:00:00.000+0000"}]}}' ;;
44
+ fail) exit 1 ;;
45
+ *) echo '{"status":0,"result":{"totalSize":0,"records":[]}}' ;;
46
+ esac
47
+ else
48
+ echo '{"status":0,"result":{"records":[{"Id":"005000000000009AAA"}]}}'
49
+ fi
50
+ ;;
51
+ *)
52
+ echo '{"status":0,"result":{}}'
53
+ ;;
54
+ esac
55
+ """
56
+
57
+
58
+ def _run(script: Path, tf_mode: str):
59
+ """Run a user-create script with the fake sf; return (rc, parsed_json, setpw_called)."""
60
+ with tempfile.TemporaryDirectory() as tmp:
61
+ bindir = Path(tmp) / "bin"
62
+ bindir.mkdir()
63
+ (bindir / "sf").write_text(FAKE_SF)
64
+ (bindir / "sf").chmod(0o755)
65
+ marker = Path(tmp) / "setpw_called"
66
+
67
+ cred_file = Path(tmp) / "CREDENTIALS.json"
68
+ env = dict(os.environ)
69
+ env["PATH"] = f"{bindir}:{env['PATH']}"
70
+ env["FAKE_TF"] = tf_mode
71
+ env["FAKE_SETPW_MARKER"] = str(marker)
72
+ # Isolate the credentials escrow file inside the temp dir so a run never writes to the repo.
73
+ env["OMNI_CREDENTIALS_FILE"] = str(cred_file)
74
+
75
+ proc = subprocess.run(
76
+ ["bash", str(script), "myorg", "1", "00e000000000001AAA", "sfx"],
77
+ capture_output=True, text=True, env=env, cwd=tmp,
78
+ )
79
+ out = None
80
+ try:
81
+ out = json.loads(proc.stdout)
82
+ except json.JSONDecodeError:
83
+ pass
84
+ cred_text = cred_file.read_text() if cred_file.exists() else ""
85
+ return proc.returncode, out, marker.exists(), proc.stdout + proc.stderr, cred_text
86
+
87
+
88
+ class TraceFlagFailClosedTests(unittest.TestCase):
89
+ SCRIPTS = (("agent", AGENT_SCRIPT), ("supervisor", SUPERVISOR_SCRIPT))
90
+
91
+ def test_no_traceflag_sets_password(self):
92
+ for label, script in self.SCRIPTS:
93
+ with self.subTest(script=label):
94
+ rc, out, setpw_called, raw, cred_text = _run(script, "none")
95
+ self.assertEqual(rc, 0, raw)
96
+ self.assertIsNotNone(out, raw)
97
+ self.assertTrue(setpw_called, "System.setPassword must run when no TraceFlag is active")
98
+ self.assertIsNone(out.get("security_warning"), raw)
99
+ self.assertEqual(out["created_users"][0]["password_status"], "generated", raw)
100
+ # P0: plaintext must NEVER be in stdout - it is escrowed to the 0600 credentials file.
101
+ self.assertIsNone(out["created_users"][0]["password"], raw)
102
+ self.assertTrue(out.get("credentials_written"), raw)
103
+ self.assertTrue(str(out.get("credentials_file", "")).endswith("CREDENTIALS.json"), raw)
104
+ pw = json.loads(cred_text)
105
+ pairs = next(iter(pw.values()))
106
+ self.assertTrue(pairs and pairs[0].get("password"), "escrow file must hold the plaintext")
107
+ self.assertNotIn(pairs[0]["password"], raw, "generated password leaked into stdout/stderr")
108
+
109
+ def test_active_traceflag_never_calls_setpassword(self):
110
+ for label, script in self.SCRIPTS:
111
+ with self.subTest(script=label):
112
+ rc, out, setpw_called, raw, cred_text = _run(script, "active")
113
+ self.assertEqual(rc, 0, raw)
114
+ self.assertIsNotNone(out, raw)
115
+ self.assertFalse(setpw_called,
116
+ "PROOF FAILED: System.setPassword was submitted while a TraceFlag was active")
117
+ self.assertIsNotNone(out.get("security_warning"), raw)
118
+ self.assertEqual(out["created_users"][0]["password_status"], "reset_required", raw)
119
+ self.assertIsNone(out["created_users"][0]["password"], raw)
120
+
121
+ def test_traceflag_query_failure_fails_closed(self):
122
+ for label, script in self.SCRIPTS:
123
+ with self.subTest(script=label):
124
+ rc, out, setpw_called, raw, cred_text = _run(script, "fail")
125
+ self.assertEqual(rc, 0, raw)
126
+ self.assertIsNotNone(out, raw)
127
+ self.assertFalse(setpw_called,
128
+ "A failed TraceFlag query must fail closed (no setPassword)")
129
+ self.assertIsNotNone(out.get("security_warning"), raw)
130
+ self.assertEqual(out["created_users"][0]["password_status"], "reset_required", raw)
131
+
132
+
133
+ class SourceContractTests(unittest.TestCase):
134
+ """Guard the exact fail-closed shape so a refactor cannot silently regress it."""
135
+
136
+ def test_setpassword_is_gated_on_traceflag_safe(self):
137
+ for script in (AGENT_SCRIPT, SUPERVISOR_SCRIPT):
138
+ src = script.read_text()
139
+ self.assertIn('if [ "$TRACEFLAG_SAFE" != "true" ]; then', src, str(script))
140
+ # The active-TraceFlag path must `continue` before reaching set_password_via_apex.
141
+ gate = src.index('if [ "$TRACEFLAG_SAFE" != "true" ]; then')
142
+ setpw = src.index("SETPW_RESULT=$(set_password_via_apex")
143
+ self.assertLess(gate, setpw, f"{script}: TRACEFLAG_SAFE gate must precede setPassword")
144
+
145
+ def test_traceflag_query_uses_soql_expiration_filter(self):
146
+ for script in (AGENT_SCRIPT, SUPERVISOR_SCRIPT):
147
+ src = script.read_text()
148
+ self.assertIn("ExpirationDate > $NOW_UTC", src, str(script))
149
+ self.assertIn("TRACEFLAG_SAFE=false", src, str(script))
150
+
151
+ def test_password_never_passed_via_jq_argv(self):
152
+ # The generated password must not reach jq (or any command) through argv/env, which are visible
153
+ # via process inspection. It is fed to jq over stdin and escrowed via --slurpfile (a file).
154
+ for script in (AGENT_SCRIPT, SUPERVISOR_SCRIPT):
155
+ src = script.read_text()
156
+ self.assertNotIn('--arg p "$PW"', src, f"{script}: password must not be a jq --arg")
157
+ self.assertNotIn('--argjson c "$pairs"', src, f"{script}: pairs (with secrets) must not be jq argv")
158
+ self.assertIn('printf \'%s\' "$PW" | jq -Rsc', src, f"{script}: password must be fed via stdin")
159
+ self.assertIn('--slurpfile c "$pairs_file"', src, f"{script}: escrow must read pairs from a file")
160
+
161
+ def test_escrow_failure_marks_users_needing_reset(self):
162
+ # When escrow fails the plaintext is unrecoverable, so the affected users must be listed in
163
+ # users_needing_password_reset (not only flipped in per-user status).
164
+ for script in (AGENT_SCRIPT, SUPERVISOR_SCRIPT):
165
+ src = script.read_text()
166
+ self.assertIn("ESCROW_FAILED_UNAMES=", src, str(script))
167
+ self.assertIn("NEEDS_RESET=$(jq -c -n --argjson a \"$NEEDS_RESET\" --argjson b \"$ESCROW_FAILED_UNAMES\"", src, str(script))
168
+
169
+
170
+ if __name__ == "__main__":
171
+ unittest.main(verbosity=2)
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: service-omni-agent-work-sharing-configure
3
+ description: "Use to configure or verify org-wide AgentWork visibility for supervisors after explicit confirmation. Triggers: let supervisors read AgentWork, configure AgentWork sharing, verify AgentWork OWD, make AgentWork visible to supervisors, change AgentWork internal access. Do not use to filter which reps appear in Command Center; use service-omni-supervisor-config-deploy for that."
4
+ allowed-tools: Bash Read Write Edit Glob Grep
5
+ metadata:
6
+ version: "0.1"
7
+ minApiVersion: "66.0"
8
+ relatedSkills:
9
+ - "service-omni-supervisor-config-deploy"
10
+ accessCheck:
11
+ - type: license
12
+ value: ServiceCloud
13
+ cliTools:
14
+ - tool: ["jq"]
15
+ semver: ">=1.6"
16
+ - tool: ["python3"]
17
+ semver: ">=3.8"
18
+ - tool: ["sf"]
19
+ semver: ">=2.139.6"
20
+ ---
21
+
22
+ # service-omni-agent-work-sharing-configure
23
+
24
+ Detect and optionally configure the standard `AgentWork` object's internal organization-wide default. The supported headless contract is `CustomObject:AgentWork` metadata: retrieve the current metadata, preserve `externalSharingModel`, deploy internal `sharingModel=Read`, and retrieve it again for proof.
25
+
26
+ This setting grants read visibility broadly to internal users who already have object access. It is not a substitute for an `OmniSupervisorConfig`: that configuration filters which reps, queues, skills, actions, and tabs a supervisor sees, but does not grant record access.
27
+
28
+ ## Inputs
29
+
30
+ ```bash
31
+ bash scripts/configure-and-report.sh <org-alias> --plan
32
+ bash scripts/configure-and-report.sh <org-alias> --confirm-org-wide-visibility
33
+ ```
34
+
35
+ - `org-alias` is required and must already be authenticated with `sf`.
36
+ - `--plan` retrieves and reports the current and desired models without deploying.
37
+ - `--confirm-org-wide-visibility` is required only when a run must change `Private` to `Read`. Omitting it blocks before deployment.
38
+
39
+ ## Safety contract
40
+
41
+ - A run refuses production customer orgs. Plan mode remains read-only.
42
+ - `externalSharingModel` is preserved exactly.
43
+ - `Read` and `ReadWrite` already provide internal read visibility and are reused. The skill never narrows `ReadWrite` to `Read`.
44
+ - The only supported mutation is `Private` to `Read`; the skill never grants internal edit access.
45
+ - Failed classification, retrieve, deploy, or read-back blocks. It never reports success from an inconclusive response.
46
+ - Use a dedicated sandbox or test org because changing an OWD can initiate sharing recalculation.
47
+
48
+ ## Workflow
49
+
50
+ 1. Classify the target org and enforce the production-write guard.
51
+ 2. Retrieve `CustomObject:AgentWork` through Metadata API.
52
+ 3. Read internal and external sharing models.
53
+ 4. Reuse `Read` or `ReadWrite` with no deploy.
54
+ 5. In plan mode, report the proposed `Private` to `Read` change and exit yellow.
55
+ 6. In run mode, require explicit blast-radius confirmation.
56
+ 7. Deploy only the standard-object metadata containing preserved external sharing and internal `Read`.
57
+ 8. Retrieve again and require internal `Read` plus the unchanged external model.
58
+
59
+ ## Output
60
+
61
+ A single JSON object containing `status`, `previous_sharing_model`, `sharing_model`, `external_sharing_model`, `changed`, `dry_run`, `deploy_id`, and `blocking_issue`.
62
+
63
+ - `configured`: a confirmed `Private` to `Read` deployment succeeded and read-back matched.
64
+ - `reused`: current `Read` or `ReadWrite` already provides visibility.
65
+ - `action_needed`: plan mode found `Private`; no write occurred.
66
+ - `blocked`: a safety guard or supported-API verification failed.
67
+
68
+ ## Limitations
69
+
70
+ - This skill changes visibility, not supervisor scope. Use the supervisor configuration skill for filtering.
71
+ - It does not create sharing rules or per-record `AgentWorkShare` rows.
72
+ - It cannot prove a particular supervisor's complete UI access; validate the end-to-end Command Center journey with the intended supervisor user after configuration.
73
+
74
+ ## References
75
+
76
+ | File | When to read |
77
+ |---|---|
78
+ | `references/api-notes.md` | Before approving the org-wide change or diagnosing a retrieve/deploy failure |
@@ -0,0 +1,31 @@
1
+ # AgentWork sharing API notes
2
+
3
+ ## Supported contract
4
+
5
+ `AgentWork` is a standard object whose organization-wide defaults round-trip through the `CustomObject` Metadata API representation:
6
+
7
+ ```xml
8
+ <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
9
+ <externalSharingModel>Private</externalSharingModel>
10
+ <sharingModel>Read</sharingModel>
11
+ </CustomObject>
12
+ ```
13
+
14
+ Retrieve with `CustomObject:AgentWork`. A deployment must preserve the retrieved `externalSharingModel`; only the internal `sharingModel` is changed.
15
+
16
+ ## Access semantics
17
+
18
+ - `Private`: object permissions alone do not make every AgentWork record readable.
19
+ - `Read`: Public Read Only for internal access.
20
+ - `ReadWrite`: already includes read access, so this skill leaves it unchanged instead of narrowing access.
21
+
22
+ Object permissions, supervisor permissions, and supervisor configuration filters still apply. An OWD change does not grant object permission and does not select which reps appear in Command Center.
23
+
24
+ ## Verification
25
+
26
+ A successful deploy response is insufficient. Retrieve `CustomObject:AgentWork` again and require:
27
+
28
+ - `sharingModel=Read` after a change.
29
+ - `externalSharingModel` equal to its pre-deploy value.
30
+
31
+ After metadata verification, sign in as the intended supervisor and validate that the expected work is visible and unrelated supervisor scopes remain filtered correctly.