@salesforce/afv-skills 1.31.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/agentforce-bot-upgrade/SKILL.md +217 -0
- package/skills/agentforce-bot-upgrade/references/extraction-blueprint.md +242 -0
- package/skills/agentforce-bot-upgrade/references/generate-agent-spec.md +128 -0
- package/skills/agentforce-bot-upgrade/references/handoff-output-format.md +208 -0
- package/skills/agentforce-bot-upgrade/references/input-contract.md +101 -0
- package/skills/agentforce-bot-upgrade/references/mapping-rules.md +113 -0
- package/skills/agentforce-bot-upgrade/references/planner-workflow-reference.md +78 -0
- package/skills/agentforce-bot-upgrade/references/post-conversion-enhancements-reference.md +67 -0
- package/skills/agentforce-bot-upgrade/references/quality-checklist.md +33 -0
- package/skills/agentforce-bot-upgrade/references/sf-cli-bot-reference.md +116 -0
- package/skills/dx-devops-work-item-manage/SKILL.md +270 -0
- package/skills/dx-devops-work-item-manage/examples/common-workflows.md +479 -0
- package/skills/dx-devops-work-item-manage/references/cli-commands.md +476 -0
- package/skills/dx-org-manage/SKILL.md +8 -5
- package/skills/dx-org-trial-expiration-check/SKILL.md +239 -0
- package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +588 -0
- package/skills/experience-ui-bundle-app-coordinate/SKILL.md +12 -9
- package/skills/experience-ui-bundle-custom-app-generate/SKILL.md +29 -5
- package/skills/experience-ui-bundle-custom-app-generate/references/configure-metadata-custom-application.md +1 -1
- package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.sh +30 -0
- package/skills/experience-ui-bundle-features-generate/SKILL.md +86 -11
- package/skills/experience-ui-bundle-features-generate/references/conflict-resolution-schema.json +20 -0
- package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +14 -0
- package/skills/experience-ui-bundle-frontend-generate/SKILL.md +6 -1
- package/skills/experience-ui-bundle-frontend-generate/references/component.md +34 -1
- package/skills/experience-ui-bundle-frontend-generate/references/page.md +1 -1
- package/skills/experience-ui-bundle-project-generate/SKILL.md +111 -0
- package/skills/experience-ui-bundle-project-generate/scripts/flatten-project.mjs +33 -0
- package/skills/platform-custom-lightning-type-generate/SKILL.md +9 -14
- package/skills/platform-custom-lightning-type-generate/references/widget-rendition.md +27 -111
- package/skills/platform-lightning-type-widget-coordinate/SKILL.md +214 -0
- package/skills/platform-lightning-type-widget-coordinate/examples/existing-lightning-type-with-widget-prompt.md +80 -0
- package/skills/platform-lightning-type-widget-coordinate/examples/new-lightning-type-with-widget-prompt.md +75 -0
- package/skills/platform-lightning-type-widget-coordinate/references/build-plan-format.md +67 -0
- package/skills/platform-lightning-type-widget-coordinate/references/lightning-type-discovery.md +124 -0
- package/skills/platform-lightning-type-widget-coordinate/references/validation-gates.md +130 -0
- package/skills/platform-sharing-owd-configure/SKILL.md +142 -0
- package/skills/platform-sharing-owd-configure/examples/get_owd_output.md +38 -0
- package/skills/platform-sharing-owd-configure/examples/update_owd_output.md +44 -0
- package/skills/platform-sharing-owd-configure/references/access_levels.md +49 -0
- package/skills/platform-sharing-owd-configure/references/metadata_api_approach.md +69 -0
- package/skills/platform-sharing-rules-generate/SKILL.md +89 -19
- package/skills/platform-sharing-rules-generate/examples/test-cases.md +381 -0
- package/skills/platform-widget-generate/SKILL.md +241 -0
- package/skills/platform-widget-generate/examples/conditional.json +73 -0
- package/skills/platform-widget-generate/examples/list-with-foreach.json +66 -0
- package/skills/platform-widget-generate/examples/single-object.json +64 -0
- package/skills/platform-widget-generate/references/schema-from-lightning-type.md +76 -0
- package/skills/platform-widget-generate/references/widget-bundle-layout.md +116 -0
- package/skills/platform-widget-generate/references/widget-meta-directives.md +130 -0
- package/skills/sales-agentforce-pipeline-management-configure/SKILL.md +268 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/field-completion-template.genAiPromptTemplate-meta.xml +141 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/pipeline_management_flow.flow-meta.xml +548 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/sales_management_agent.agent +2938 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/sales_management_agent.bundle-meta.xml +4 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/sfdx-project.json +12 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/stage-descriptions/bant.txt +11 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/stage-descriptions/meddic.txt +11 -0
- package/skills/sales-agentforce-pipeline-management-configure/assets/stage-descriptions/spiced.txt +11 -0
- package/skills/sales-agentforce-pipeline-management-configure/examples/custom-prompt-instructions.md +317 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/admin-communication.md +355 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/agent-creation.md +383 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/auth-and-cli.md +146 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/automation-matrix.md +23 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/autonomous-updates.md +215 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/canonical-agent-path.md +75 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/data-sources.md +155 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/field-completion-prompt-template.md +120 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/field-completion-prompts.md +149 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/flags.md +13 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/flow-clone-from-template.md +447 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/metadata-inventory.md +330 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/opportunity-stages.md +246 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/repair-diagnostics.md +265 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/setup-order.md +263 -0
- package/skills/sales-agentforce-pipeline-management-configure/references/soap-api-enablement.md +528 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/CHANGES.md +180 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/README.md +56 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/add-field-suggestion.sh +679 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/create-agent.sh +302 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/create-flow.sh +250 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/define-agent-access.sh +430 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/deploy-settings.sh +77 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/enable-deal-agent.sh +241 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/enable-prerequisites.sh +538 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/flow-debug-and-verify.sh +385 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/retrieve-settings.sh +131 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/setup-all.sh +2791 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/agent-bundle-publish.sh +189 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/agent-detection.sh +239 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/flow-builder.sh +238 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/psg.sh +48 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/soap.sh +140 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/stage-descriptions.sh +448 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/shared/test-opp.sh +166 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/verify-all.sh +636 -0
- package/skills/sales-agentforce-pipeline-management-configure/scripts/verify-prompt-generation.sh +346 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Define Agent Access for the Pipeline Management Sales Agent (W-23242378)
|
|
4
|
+
#
|
|
5
|
+
# PROBLEM this solves:
|
|
6
|
+
# When the Pipeline Management Sales Agent (a BotDefinition matched by
|
|
7
|
+
# AgentTemplate — SalesMgmt__NGASalesAgent / SalesMgmt__SalesAgent) is created,
|
|
8
|
+
# it is NOT added to the "Agent Access" section of any permission set that
|
|
9
|
+
# human users hold. Users assigned the managed PSG `SalesManagementUserPsg`
|
|
10
|
+
# therefore cannot launch or chat with the agent, even though the agent is
|
|
11
|
+
# active and the PSG is assigned.
|
|
12
|
+
#
|
|
13
|
+
# WHY it can't be done directly on the PSG:
|
|
14
|
+
# `SalesManagementUserPsg` is a MANAGED permission set group — its Agent Access
|
|
15
|
+
# cannot be edited. Agent Access (SetupEntityAccess with SetupEntityType=
|
|
16
|
+
# 'BotDefinition') can only live on a CUSTOM permission set (License=None).
|
|
17
|
+
#
|
|
18
|
+
# THE FIX (mirrors the manual Setup UI flow):
|
|
19
|
+
# 1. Create a CUSTOM permission set `Sales_Agent_Access` (License=None).
|
|
20
|
+
# 2. Add the agent to its Agent Access: insert SetupEntityAccess
|
|
21
|
+
# (ParentId=<permset Id>, SetupEntityId=<BotDefinition Id>). Standard Data API.
|
|
22
|
+
# SetupEntityType is derived — do NOT set it on insert. Requires API v64.0+.
|
|
23
|
+
# 3. Add the custom permset to `SalesManagementUserPsg` via
|
|
24
|
+
# PermissionSetGroupComponent (Tooling API object — needs --use-tooling-api).
|
|
25
|
+
# 4. Confirm the PSG recalculates (the component insert triggers async recalc;
|
|
26
|
+
# poll Status until 'Updated'). => Agent becomes available to all users
|
|
27
|
+
# holding SalesManagementUserPsg.
|
|
28
|
+
#
|
|
29
|
+
# The BotDefinition is detected by AgentTemplate (via shared/agent-detection.sh),
|
|
30
|
+
# NOT by a hardcoded DeveloperName, so it matches whatever local name the agent
|
|
31
|
+
# was published with (SalesAgent, Sale_Agent, etc.) — the same logic the
|
|
32
|
+
# scheduled flow uses.
|
|
33
|
+
#
|
|
34
|
+
# This script is idempotent (query-then-skip; SetupEntityAccess has no Update)
|
|
35
|
+
# and safe to re-run. It must run AFTER the agent (BotDefinition) exists.
|
|
36
|
+
#
|
|
37
|
+
# Usage: ./define-agent-access.sh <org-alias>
|
|
38
|
+
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
# sf CLI can emit ANSI color codes inside --json stdout, breaking jq parsing.
|
|
42
|
+
# 2>/dev/null only strips stderr; these env vars are the reliable fix (see PM notes).
|
|
43
|
+
export NO_COLOR=1
|
|
44
|
+
export FORCE_COLOR=0
|
|
45
|
+
|
|
46
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
47
|
+
|
|
48
|
+
# Shared PM bot detection (AgentTemplate-based). Provides PM_AGENT_TEMPLATES_SOQL
|
|
49
|
+
# and pm_bot_developer_name().
|
|
50
|
+
source "$SCRIPT_DIR/shared/agent-detection.sh"
|
|
51
|
+
# Shared PSG recalculation status reader (psg_status).
|
|
52
|
+
source "$SCRIPT_DIR/shared/psg.sh"
|
|
53
|
+
|
|
54
|
+
ORG_ALIAS="${1:-}"
|
|
55
|
+
|
|
56
|
+
# --- Helpers (mirror setup-all.sh conventions) ---
|
|
57
|
+
log_pass() { echo " [PASS] $1"; }
|
|
58
|
+
log_warn() { echo " [WARN] $1"; }
|
|
59
|
+
log_fail() { echo " [FAIL] $1"; }
|
|
60
|
+
log_info() { echo " [....] $1"; }
|
|
61
|
+
log_try() { echo " [TRY ] $1"; }
|
|
62
|
+
|
|
63
|
+
if [[ -z "$ORG_ALIAS" ]]; then
|
|
64
|
+
echo "Error: Missing org alias"
|
|
65
|
+
echo "Usage: $0 <org-alias>"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Validate org alias (prevent shell injection via metacharacters), matching
|
|
70
|
+
# the pattern used by setup-all.sh and verify-all.sh.
|
|
71
|
+
if [[ ! "$ORG_ALIAS" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
|
|
72
|
+
echo "ERROR: Invalid org alias. Only alphanumeric, dots, hyphens, and underscores allowed (must start with alphanumeric)."
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Preflight: this script parses every CLI response with jq and calls the sf CLI,
|
|
77
|
+
# and SetupEntityAccess for BotDefinition needs the sf CLI's default API (v64.0+,
|
|
78
|
+
# shipped by sf CLI v2.x). Fail fast with an actionable message instead of a
|
|
79
|
+
# cryptic mid-run error.
|
|
80
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
81
|
+
echo "ERROR: 'jq' is required but not installed. Install it (e.g. 'brew install jq') and re-run."
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
if ! command -v sf >/dev/null 2>&1; then
|
|
85
|
+
echo "ERROR: Salesforce CLI ('sf') is required but not installed. Install the v2.x 'sf' CLI and re-run."
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
SF_MAJOR=$(sf --version 2>/dev/null | grep -oE '@salesforce/cli/[0-9]+' | grep -oE '[0-9]+$' || echo "")
|
|
89
|
+
if [[ -n "$SF_MAJOR" && "$SF_MAJOR" -lt 2 ]]; then
|
|
90
|
+
echo "ERROR: Salesforce CLI v2.x or newer is required (found major version ${SF_MAJOR})."
|
|
91
|
+
echo " Agent Access (SetupEntityAccess for BotDefinition) needs API v64.0+, which ships with sf CLI v2.x."
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Configurable identifiers
|
|
96
|
+
ACCESS_PS_NAME="Sales_Agent_Access"
|
|
97
|
+
ACCESS_PS_LABEL="Sales Agent Access"
|
|
98
|
+
USER_PSG_NAME="SalesManagementUserPsg"
|
|
99
|
+
AGENT_PSG_NAME="SalesManagementAgentUserPsg"
|
|
100
|
+
|
|
101
|
+
echo "=== Define Agent Access for Pipeline Management Sales Agent ==="
|
|
102
|
+
echo ""
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------
|
|
105
|
+
# Step 0: The agent must exist first (BotDefinition Id is runtime-only).
|
|
106
|
+
# Detect it by AgentTemplate, matching the scheduled flow's own lookup — not by
|
|
107
|
+
# a hardcoded DeveloperName. Uses a TWO-STEP query: first find the BotDefinition
|
|
108
|
+
# by AgentTemplate (no subquery), then separately confirm it has an Active
|
|
109
|
+
# BotVersion. The single-query `Id IN (SELECT ... FROM BotVersion WHERE Status =
|
|
110
|
+
# 'Active')` form was removed in de77d520 because that correlated subquery hangs
|
|
111
|
+
# 60+ seconds on some orgs.
|
|
112
|
+
# ------------------------------------------------------------
|
|
113
|
+
BOT_DEF=$(sf data query \
|
|
114
|
+
--query "SELECT Id, DeveloperName FROM BotDefinition WHERE AgentTemplate IN (${PM_AGENT_TEMPLATES_SOQL}) ORDER BY LastModifiedDate DESC LIMIT 1" \
|
|
115
|
+
--target-org "$ORG_ALIAS" \
|
|
116
|
+
--json 2>/dev/null | jq -r '.result.records[0] // empty' 2>/dev/null || echo "")
|
|
117
|
+
|
|
118
|
+
BOT_DEF_ID=$(echo "$BOT_DEF" | jq -r '.Id // empty' 2>/dev/null || echo "")
|
|
119
|
+
BOT_DEF_NAME=$(echo "$BOT_DEF" | jq -r '.DeveloperName // empty' 2>/dev/null || echo "")
|
|
120
|
+
|
|
121
|
+
# BOT_DEF_ID is interpolated into later SOQL; reject anything that is not a
|
|
122
|
+
# valid Salesforce record Id (15 or 18 char alphanumeric).
|
|
123
|
+
if [[ -n "$BOT_DEF_ID" && ! "$BOT_DEF_ID" =~ ^[a-zA-Z0-9]{15,18}$ ]]; then
|
|
124
|
+
log_fail "Invalid BotDefinition ID format: $BOT_DEF_ID"
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if [[ -z "$BOT_DEF_ID" ]]; then
|
|
129
|
+
log_warn "No Pipeline Management BotDefinition found — cannot define Agent Access yet."
|
|
130
|
+
echo " Agent Access can only be defined after the agent exists and has an Active version."
|
|
131
|
+
echo " Create the agent first (./create-agent.sh $ORG_ALIAS), then re-run this script."
|
|
132
|
+
exit 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Separately confirm the bot has an Active BotVersion (flat query on BotVersion,
|
|
136
|
+
# no correlated subquery). Agent Access on an inactive bot has no runtime effect.
|
|
137
|
+
ACTIVE_VERSION_COUNT=$(sf data query \
|
|
138
|
+
--query "SELECT COUNT() FROM BotVersion WHERE BotDefinitionId = '${BOT_DEF_ID}' AND Status = 'Active'" \
|
|
139
|
+
--target-org "$ORG_ALIAS" \
|
|
140
|
+
--json 2>/dev/null | jq -r '.result.totalSize // 0' 2>/dev/null | head -1 | tr -cd '0-9' || echo "0")
|
|
141
|
+
ACTIVE_VERSION_COUNT="${ACTIVE_VERSION_COUNT:-0}"
|
|
142
|
+
|
|
143
|
+
if [[ "$ACTIVE_VERSION_COUNT" -lt 1 ]]; then
|
|
144
|
+
log_warn "Agent BotDefinition '${BOT_DEF_NAME:-<unknown>}' has no Active BotVersion — cannot define Agent Access yet."
|
|
145
|
+
echo " Activate the agent version first, then re-run this script."
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
log_pass "Found active agent BotDefinition: ${BOT_DEF_NAME:-<unknown>} ($BOT_DEF_ID)"
|
|
149
|
+
|
|
150
|
+
# ------------------------------------------------------------
|
|
151
|
+
# Step 1: Create the custom permission set (idempotent)
|
|
152
|
+
# PermissionSet is createable via the STANDARD Data API (NOT Tooling).
|
|
153
|
+
# Omitting License leaves LicenseId null (= "None"), which is required so the
|
|
154
|
+
# permset can be added as a component of the managed PSG.
|
|
155
|
+
# ------------------------------------------------------------
|
|
156
|
+
PS_ID=$(sf data query \
|
|
157
|
+
--query "SELECT Id FROM PermissionSet WHERE Name = '${ACCESS_PS_NAME}'" \
|
|
158
|
+
--target-org "$ORG_ALIAS" \
|
|
159
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
160
|
+
|
|
161
|
+
if [[ -n "$PS_ID" ]]; then
|
|
162
|
+
log_pass "Custom permission set '${ACCESS_PS_NAME}' already exists ($PS_ID)"
|
|
163
|
+
else
|
|
164
|
+
log_try "Creating custom permission set '${ACCESS_PS_NAME}' (License=None)..."
|
|
165
|
+
CREATE_PS=$(sf data create record \
|
|
166
|
+
--sobject PermissionSet \
|
|
167
|
+
--values "Name='${ACCESS_PS_NAME}' Label='${ACCESS_PS_LABEL}'" \
|
|
168
|
+
--target-org "$ORG_ALIAS" \
|
|
169
|
+
--json 2>/dev/null || echo '{"status":1}')
|
|
170
|
+
|
|
171
|
+
PS_ID=$(echo "$CREATE_PS" | jq -r '.result.id // empty' 2>/dev/null || echo "")
|
|
172
|
+
if [[ -z "$PS_ID" ]]; then
|
|
173
|
+
# Re-query in case of a race / partial output. A concurrent create or a
|
|
174
|
+
# not-yet-visible write can make the row lag briefly, so retry with a short
|
|
175
|
+
# backoff rather than giving up after a single lookup.
|
|
176
|
+
for _ in $(seq 1 5); do
|
|
177
|
+
PS_ID=$(sf data query \
|
|
178
|
+
--query "SELECT Id FROM PermissionSet WHERE Name = '${ACCESS_PS_NAME}'" \
|
|
179
|
+
--target-org "$ORG_ALIAS" \
|
|
180
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
181
|
+
[[ -n "$PS_ID" ]] && break
|
|
182
|
+
sleep 2
|
|
183
|
+
done
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if [[ -z "$PS_ID" ]]; then
|
|
187
|
+
log_fail "Could not create permission set '${ACCESS_PS_NAME}'"
|
|
188
|
+
echo "$CREATE_PS" | jq -r '.message // .result.message // empty' 2>/dev/null || true
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
log_pass "Created custom permission set '${ACCESS_PS_NAME}' ($PS_ID)"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# ------------------------------------------------------------
|
|
195
|
+
# Step 2: Add the agent to Agent Access on that permission set
|
|
196
|
+
# SetupEntityAccess: Create/Delete/Query only (NO Update). SetupEntityType is
|
|
197
|
+
# derived from SetupEntityId — do NOT set it on insert. BotDefinition is a valid
|
|
198
|
+
# SetupEntityType only at API v64.0+ (sf CLI default is well above this).
|
|
199
|
+
# ------------------------------------------------------------
|
|
200
|
+
EXISTING_ACCESS=$(sf data query \
|
|
201
|
+
--query "SELECT Id FROM SetupEntityAccess WHERE ParentId = '${PS_ID}' AND SetupEntityId = '${BOT_DEF_ID}'" \
|
|
202
|
+
--target-org "$ORG_ALIAS" \
|
|
203
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
204
|
+
|
|
205
|
+
if [[ -n "$EXISTING_ACCESS" ]]; then
|
|
206
|
+
log_pass "Agent Access already granted to '${ACCESS_PS_NAME}' ($EXISTING_ACCESS)"
|
|
207
|
+
else
|
|
208
|
+
log_try "Granting Agent Access (SetupEntityAccess) to '${ACCESS_PS_NAME}'..."
|
|
209
|
+
CREATE_ACCESS=$(sf data create record \
|
|
210
|
+
--sobject SetupEntityAccess \
|
|
211
|
+
--values "ParentId='${PS_ID}' SetupEntityId='${BOT_DEF_ID}'" \
|
|
212
|
+
--target-org "$ORG_ALIAS" \
|
|
213
|
+
--json 2>/dev/null || echo '{"status":1}')
|
|
214
|
+
|
|
215
|
+
# The query-then-create above is non-atomic: a concurrent run can insert the
|
|
216
|
+
# same grant between our SELECT and INSERT, so tolerate DUPLICATE_VALUE — the
|
|
217
|
+
# grant exists either way, which is the outcome we want (idempotent).
|
|
218
|
+
if echo "$CREATE_ACCESS" | jq -e '.result.id // empty' >/dev/null 2>&1; then
|
|
219
|
+
log_pass "Agent Access granted (agent added to '${ACCESS_PS_NAME}')"
|
|
220
|
+
elif echo "$CREATE_ACCESS" | jq -r '.message // .result.message // ""' 2>/dev/null | grep -qi "DUPLICATE_VALUE"; then
|
|
221
|
+
log_pass "Agent Access already granted (duplicate insert ignored)"
|
|
222
|
+
else
|
|
223
|
+
ACCESS_ERR=$(echo "$CREATE_ACCESS" | jq -r '.message // .result.message // "unknown"' 2>/dev/null || echo "unknown")
|
|
224
|
+
log_fail "Could not grant Agent Access: $ACCESS_ERR"
|
|
225
|
+
echo " SetupEntityAccess requires a CUSTOM permission set and API v64.0+."
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# ------------------------------------------------------------
|
|
231
|
+
# Step 3: Add the custom permset as a component of the managed PSG
|
|
232
|
+
# PermissionSetGroupComponent is a TOOLING API object — must use --use-tooling-api.
|
|
233
|
+
# ------------------------------------------------------------
|
|
234
|
+
PSG_ID=$(sf data query \
|
|
235
|
+
--query "SELECT Id FROM PermissionSetGroup WHERE DeveloperName = '${USER_PSG_NAME}'" \
|
|
236
|
+
--target-org "$ORG_ALIAS" \
|
|
237
|
+
--use-tooling-api \
|
|
238
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
239
|
+
|
|
240
|
+
COMPONENT_LINKED=0
|
|
241
|
+
if [[ -z "$PSG_ID" ]]; then
|
|
242
|
+
log_warn "PSG '${USER_PSG_NAME}' not found — skipping component link + recalc."
|
|
243
|
+
echo " The custom permission set '${ACCESS_PS_NAME}' still carries Agent Access;"
|
|
244
|
+
echo " it can be assigned directly to users as a fallback (see below)."
|
|
245
|
+
else
|
|
246
|
+
EXISTING_COMPONENT=$(sf data query \
|
|
247
|
+
--query "SELECT Id FROM PermissionSetGroupComponent WHERE PermissionSetGroupId = '${PSG_ID}' AND PermissionSetId = '${PS_ID}'" \
|
|
248
|
+
--target-org "$ORG_ALIAS" \
|
|
249
|
+
--use-tooling-api \
|
|
250
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
251
|
+
|
|
252
|
+
if [[ -n "$EXISTING_COMPONENT" ]]; then
|
|
253
|
+
log_pass "'${ACCESS_PS_NAME}' is already a component of '${USER_PSG_NAME}'"
|
|
254
|
+
COMPONENT_LINKED=1
|
|
255
|
+
else
|
|
256
|
+
log_try "Adding '${ACCESS_PS_NAME}' as a component of '${USER_PSG_NAME}'..."
|
|
257
|
+
CREATE_COMPONENT=$(sf data create record \
|
|
258
|
+
--sobject PermissionSetGroupComponent \
|
|
259
|
+
--values "PermissionSetGroupId='${PSG_ID}' PermissionSetId='${PS_ID}'" \
|
|
260
|
+
--target-org "$ORG_ALIAS" \
|
|
261
|
+
--use-tooling-api \
|
|
262
|
+
--json 2>/dev/null || echo '{"status":1}')
|
|
263
|
+
|
|
264
|
+
if echo "$CREATE_COMPONENT" | jq -e '.result.id // empty' >/dev/null 2>&1; then
|
|
265
|
+
log_pass "Linked '${ACCESS_PS_NAME}' into '${USER_PSG_NAME}'"
|
|
266
|
+
COMPONENT_LINKED=1
|
|
267
|
+
elif echo "$CREATE_COMPONENT" | jq -r '.message // .result.message // ""' 2>/dev/null | grep -qi "DUPLICATE_VALUE"; then
|
|
268
|
+
log_pass "'${ACCESS_PS_NAME}' already a component of '${USER_PSG_NAME}' (duplicate insert ignored)"
|
|
269
|
+
COMPONENT_LINKED=1
|
|
270
|
+
else
|
|
271
|
+
COMPONENT_ERR=$(echo "$CREATE_COMPONENT" | jq -r '.message // .result.message // "unknown"' 2>/dev/null || echo "unknown")
|
|
272
|
+
log_warn "Could not link custom permset into managed PSG: $COMPONENT_ERR"
|
|
273
|
+
echo " Falling back to direct assignment of '${ACCESS_PS_NAME}' to PSG users."
|
|
274
|
+
fi
|
|
275
|
+
fi
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
# ------------------------------------------------------------
|
|
279
|
+
# Step 3b: Add the custom permset as a component of the agent-user PSG too.
|
|
280
|
+
# The autonomous agent user runs the suggestion flow AS the agent, so it also
|
|
281
|
+
# needs Agent Access — wire the same custom permset into SalesManagementAgentUserPsg.
|
|
282
|
+
# Mirrors Step 3 (Tooling API object; idempotent query-then-skip). No direct-
|
|
283
|
+
# assignment fallback: the agent user is a single system user, so the component
|
|
284
|
+
# link is the right (and only) path.
|
|
285
|
+
# ------------------------------------------------------------
|
|
286
|
+
AGENT_COMPONENT_LINKED=0
|
|
287
|
+
AGENT_PSG_ID=$(sf data query \
|
|
288
|
+
--query "SELECT Id FROM PermissionSetGroup WHERE DeveloperName = '${AGENT_PSG_NAME}'" \
|
|
289
|
+
--target-org "$ORG_ALIAS" \
|
|
290
|
+
--use-tooling-api \
|
|
291
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
292
|
+
|
|
293
|
+
if [[ -z "$AGENT_PSG_ID" ]]; then
|
|
294
|
+
log_warn "PSG '${AGENT_PSG_NAME}' not found — skipping agent component link + recalc."
|
|
295
|
+
else
|
|
296
|
+
EXISTING_AGENT_COMPONENT=$(sf data query \
|
|
297
|
+
--query "SELECT Id FROM PermissionSetGroupComponent WHERE PermissionSetGroupId = '${AGENT_PSG_ID}' AND PermissionSetId = '${PS_ID}'" \
|
|
298
|
+
--target-org "$ORG_ALIAS" \
|
|
299
|
+
--use-tooling-api \
|
|
300
|
+
--json 2>/dev/null | jq -r '.result.records[0].Id // empty' 2>/dev/null || echo "")
|
|
301
|
+
|
|
302
|
+
if [[ -n "$EXISTING_AGENT_COMPONENT" ]]; then
|
|
303
|
+
log_pass "'${ACCESS_PS_NAME}' is already a component of '${AGENT_PSG_NAME}'"
|
|
304
|
+
AGENT_COMPONENT_LINKED=1
|
|
305
|
+
else
|
|
306
|
+
log_try "Adding '${ACCESS_PS_NAME}' as a component of '${AGENT_PSG_NAME}'..."
|
|
307
|
+
CREATE_AGENT_COMPONENT=$(sf data create record \
|
|
308
|
+
--sobject PermissionSetGroupComponent \
|
|
309
|
+
--values "PermissionSetGroupId='${AGENT_PSG_ID}' PermissionSetId='${PS_ID}'" \
|
|
310
|
+
--target-org "$ORG_ALIAS" \
|
|
311
|
+
--use-tooling-api \
|
|
312
|
+
--json 2>/dev/null || echo '{"status":1}')
|
|
313
|
+
|
|
314
|
+
if echo "$CREATE_AGENT_COMPONENT" | jq -e '.result.id // empty' >/dev/null 2>&1; then
|
|
315
|
+
log_pass "Linked '${ACCESS_PS_NAME}' into '${AGENT_PSG_NAME}'"
|
|
316
|
+
AGENT_COMPONENT_LINKED=1
|
|
317
|
+
elif echo "$CREATE_AGENT_COMPONENT" | jq -r '.message // .result.message // ""' 2>/dev/null | grep -qi "DUPLICATE_VALUE"; then
|
|
318
|
+
log_pass "'${ACCESS_PS_NAME}' already a component of '${AGENT_PSG_NAME}' (duplicate insert ignored)"
|
|
319
|
+
AGENT_COMPONENT_LINKED=1
|
|
320
|
+
else
|
|
321
|
+
AGENT_COMPONENT_ERR=$(echo "$CREATE_AGENT_COMPONENT" | jq -r '.message // .result.message // "unknown"' 2>/dev/null || echo "unknown")
|
|
322
|
+
log_warn "Could not link custom permset into agent PSG: $AGENT_COMPONENT_ERR"
|
|
323
|
+
fi
|
|
324
|
+
fi
|
|
325
|
+
fi
|
|
326
|
+
|
|
327
|
+
# ------------------------------------------------------------
|
|
328
|
+
# Step 4: Confirm the PSG recalculates so the added component takes effect.
|
|
329
|
+
# The PermissionSetGroupComponent insert in Step 3 is what actually TRIGGERS the
|
|
330
|
+
# async recalculation — PermissionSetGroup.Status is a system-computed field
|
|
331
|
+
# (Updated/Updating/Outdated/Failed), NOT client-writable. There is no CLI recalc
|
|
332
|
+
# verb, so we simply POLL Status until it returns to 'Updated'. A Failed status
|
|
333
|
+
# means Agent Access is NOT live and must be surfaced as a hard error.
|
|
334
|
+
# ------------------------------------------------------------
|
|
335
|
+
# The PermissionSetGroupComponent insert in Step 3 IS the recalc trigger — the
|
|
336
|
+
# async recalculation is already under way and the org converges on its own; we
|
|
337
|
+
# deliberately do NOT block waiting for it (recalc completion only matters when a
|
|
338
|
+
# user later launches the agent). We read Status once, for reporting.
|
|
339
|
+
if [[ "$COMPONENT_LINKED" -eq 1 && -n "$PSG_ID" ]]; then
|
|
340
|
+
log_try "Recalculation triggered for '${USER_PSG_NAME}' (component linked)."
|
|
341
|
+
PSG_STATUS=$(psg_status "$ORG_ALIAS" "$USER_PSG_NAME")
|
|
342
|
+
case "$PSG_STATUS" in
|
|
343
|
+
Updated)
|
|
344
|
+
log_pass "PSG '${USER_PSG_NAME}' Status: Updated" ;;
|
|
345
|
+
Failed)
|
|
346
|
+
log_fail "PSG '${USER_PSG_NAME}' recalculation Status: Failed — Agent Access not active"
|
|
347
|
+
exit 1 ;;
|
|
348
|
+
*)
|
|
349
|
+
log_info "PSG '${USER_PSG_NAME}' Status: ${PSG_STATUS} — recalculation is async and will complete on its own."
|
|
350
|
+
echo " Agent Access takes effect once Status reaches 'Updated' (verify-all.sh reports it)." ;;
|
|
351
|
+
esac
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
# ------------------------------------------------------------
|
|
355
|
+
# Step 4b: Report the agent-user PSG recalc, mirroring Step 4 (read Status once,
|
|
356
|
+
# never sleep-poll — the Step 3b component insert already triggered the async
|
|
357
|
+
# recalculation).
|
|
358
|
+
# ------------------------------------------------------------
|
|
359
|
+
if [[ "$AGENT_COMPONENT_LINKED" -eq 1 && -n "$AGENT_PSG_ID" ]]; then
|
|
360
|
+
log_try "Recalculation triggered for '${AGENT_PSG_NAME}' (component linked)."
|
|
361
|
+
AGENT_PSG_STATUS=$(psg_status "$ORG_ALIAS" "$AGENT_PSG_NAME")
|
|
362
|
+
case "$AGENT_PSG_STATUS" in
|
|
363
|
+
Updated)
|
|
364
|
+
log_pass "PSG '${AGENT_PSG_NAME}' Status: Updated" ;;
|
|
365
|
+
Failed)
|
|
366
|
+
log_fail "PSG '${AGENT_PSG_NAME}' recalculation Status: Failed — Agent Access not active"
|
|
367
|
+
exit 1 ;;
|
|
368
|
+
*)
|
|
369
|
+
log_info "PSG '${AGENT_PSG_NAME}' Status: ${AGENT_PSG_STATUS} — recalculation is async and will complete on its own."
|
|
370
|
+
echo " Agent Access takes effect once Status reaches 'Updated' (verify-all.sh reports it)." ;;
|
|
371
|
+
esac
|
|
372
|
+
fi
|
|
373
|
+
|
|
374
|
+
# ------------------------------------------------------------
|
|
375
|
+
# Fallback: if the component link failed, assign the custom permset directly to
|
|
376
|
+
# the users who hold the managed PSG so they still get Agent Access.
|
|
377
|
+
# ------------------------------------------------------------
|
|
378
|
+
if [[ "$COMPONENT_LINKED" -eq 0 && -n "$PSG_ID" ]]; then
|
|
379
|
+
log_info "Fallback: assigning '${ACCESS_PS_NAME}' directly to users of '${USER_PSG_NAME}'..."
|
|
380
|
+
# LIMIT 200 caps the fan-out: each user below costs one sequential API call, so
|
|
381
|
+
# an unbounded query on a large org could issue hundreds of calls. The linked
|
|
382
|
+
# component (Step 3) is the scalable path; this direct-assignment fallback is a
|
|
383
|
+
# best-effort stopgap. If the PSG has >200 users, prefer fixing the component
|
|
384
|
+
# link so recalculation reaches everyone at once.
|
|
385
|
+
ASSIGNEE_IDS=$(sf data query \
|
|
386
|
+
--query "SELECT AssigneeId FROM PermissionSetAssignment WHERE PermissionSetGroup.DeveloperName = '${USER_PSG_NAME}' LIMIT 200" \
|
|
387
|
+
--target-org "$ORG_ALIAS" \
|
|
388
|
+
--json 2>/dev/null | jq -r '.result.records[].AssigneeId' 2>/dev/null || echo "")
|
|
389
|
+
|
|
390
|
+
if [[ -z "$ASSIGNEE_IDS" ]]; then
|
|
391
|
+
log_warn "No users currently hold '${USER_PSG_NAME}' — nothing to assign."
|
|
392
|
+
else
|
|
393
|
+
# NOTE: do not name this loop var UID — UID is a readonly special var in bash.
|
|
394
|
+
while IFS= read -r ASSIGNEE_ID; do
|
|
395
|
+
[[ -z "$ASSIGNEE_ID" ]] && continue
|
|
396
|
+
CREATE_RESULT=$(sf data create record \
|
|
397
|
+
--sobject PermissionSetAssignment \
|
|
398
|
+
--values "AssigneeId='${ASSIGNEE_ID}' PermissionSetId='${PS_ID}'" \
|
|
399
|
+
--target-org "$ORG_ALIAS" \
|
|
400
|
+
--json 2>/dev/null || echo '{"status":1}')
|
|
401
|
+
|
|
402
|
+
# Distinguish "already assigned" (benign) from real failures like a
|
|
403
|
+
# deactivated user or license conflict — don't paper over the latter.
|
|
404
|
+
RESULT_MSG=$(echo "$CREATE_RESULT" | jq -r '.message // .result.message // ""' 2>/dev/null || echo "")
|
|
405
|
+
if echo "$CREATE_RESULT" | jq -e '.result.id // empty' >/dev/null 2>&1; then
|
|
406
|
+
log_pass "Assigned '${ACCESS_PS_NAME}' to user $ASSIGNEE_ID"
|
|
407
|
+
elif echo "$RESULT_MSG" | grep -qi "DUPLICATE_VALUE"; then
|
|
408
|
+
log_info "Already assigned: $ASSIGNEE_ID"
|
|
409
|
+
elif echo "$RESULT_MSG" | grep -qi "FIELD_INTEGRITY_EXCEPTION"; then
|
|
410
|
+
log_warn "License conflict for $ASSIGNEE_ID (FIELD_INTEGRITY_EXCEPTION) — user may need a license upgrade"
|
|
411
|
+
elif echo "$RESULT_MSG" | grep -qi "INVALID_CROSS_REFERENCE_KEY"; then
|
|
412
|
+
log_warn "Assignment for $ASSIGNEE_ID rejected (INVALID_CROSS_REFERENCE_KEY) — the PSG has not finished recalculating (Status not yet 'Updated'). Re-run after recalculation completes."
|
|
413
|
+
else
|
|
414
|
+
ASSIGN_ERR="${RESULT_MSG:-unknown}"
|
|
415
|
+
log_warn "Failed to assign to $ASSIGNEE_ID: $ASSIGN_ERR"
|
|
416
|
+
fi
|
|
417
|
+
done <<< "$ASSIGNEE_IDS"
|
|
418
|
+
fi
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
echo ""
|
|
422
|
+
echo "=== Agent Access definition complete ==="
|
|
423
|
+
echo ""
|
|
424
|
+
echo " Custom permission set : ${ACCESS_PS_NAME} ($PS_ID)"
|
|
425
|
+
echo " Agent : ${BOT_DEF_NAME:-<unknown>} ($BOT_DEF_ID)"
|
|
426
|
+
echo " Target PSG : ${USER_PSG_NAME}"
|
|
427
|
+
echo " Agent PSG : ${AGENT_PSG_NAME}"
|
|
428
|
+
echo ""
|
|
429
|
+
echo " Users holding '${USER_PSG_NAME}' can launch the Sales Agent once the PSG"
|
|
430
|
+
echo " recalculation completes (Status 'Updated'). The recalc runs asynchronously."
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Deploy Pipeline Management settings from local metadata files
|
|
4
|
+
# Usage: ./deploy-settings.sh <org-alias>
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# sf CLI can emit ANSI color codes inside --json stdout, breaking jq parsing.
|
|
9
|
+
# 2>/dev/null only strips stderr; these env vars are the reliable fix (see PM notes).
|
|
10
|
+
export NO_COLOR=1
|
|
11
|
+
export FORCE_COLOR=0
|
|
12
|
+
|
|
13
|
+
ORG_ALIAS="${1:-}"
|
|
14
|
+
|
|
15
|
+
if [[ -z "$ORG_ALIAS" ]]; then
|
|
16
|
+
echo "❌ Error: Missing org alias"
|
|
17
|
+
echo "Usage: $0 <org-alias>"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ ! -f "sfdx-project.json" ]]; then
|
|
22
|
+
echo "❌ Error: sfdx-project.json not found in current directory"
|
|
23
|
+
echo "Copy from ../assets/sfdx-project.json first"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
SETTINGS_DIR="force-app/main/default/settings"
|
|
28
|
+
|
|
29
|
+
if [[ ! -d "$SETTINGS_DIR" ]]; then
|
|
30
|
+
echo "❌ Error: Settings directory not found: $SETTINGS_DIR"
|
|
31
|
+
echo "Run ./retrieve-settings.sh first to retrieve settings"
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
echo "📤 Deploying Pipeline Management settings to org: $ORG_ALIAS"
|
|
36
|
+
echo ""
|
|
37
|
+
|
|
38
|
+
# Count available settings files
|
|
39
|
+
SETTINGS_COUNT=$(find "$SETTINGS_DIR" -name "*.settings-meta.xml" -type f | wc -l | tr -d ' ')
|
|
40
|
+
|
|
41
|
+
if [[ "$SETTINGS_COUNT" -eq 0 ]]; then
|
|
42
|
+
echo "❌ No settings files found in $SETTINGS_DIR"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
echo "Found $SETTINGS_COUNT settings file(s) to deploy"
|
|
47
|
+
echo ""
|
|
48
|
+
|
|
49
|
+
# Deploy all settings
|
|
50
|
+
echo "Deploying settings..."
|
|
51
|
+
|
|
52
|
+
if sf project deploy start \
|
|
53
|
+
--source-dir "$SETTINGS_DIR" \
|
|
54
|
+
--target-org "$ORG_ALIAS" \
|
|
55
|
+
--json 2>/dev/null | jq -e '.status == 0' >/dev/null 2>&1; then
|
|
56
|
+
echo "✅ Settings deployed successfully"
|
|
57
|
+
else
|
|
58
|
+
echo "❌ Deployment failed"
|
|
59
|
+
echo ""
|
|
60
|
+
echo "Retrying with verbose output..."
|
|
61
|
+
sf project deploy start \
|
|
62
|
+
--source-dir "$SETTINGS_DIR" \
|
|
63
|
+
--target-org "$ORG_ALIAS"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo ""
|
|
68
|
+
|
|
69
|
+
# List deployed files
|
|
70
|
+
echo "Deployed settings:"
|
|
71
|
+
find "$SETTINGS_DIR" -name "*.settings-meta.xml" -type f | while read -r FILE; do
|
|
72
|
+
BASENAME=$(basename "$FILE" .settings-meta.xml)
|
|
73
|
+
echo " ✅ $BASENAME"
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
echo ""
|
|
77
|
+
echo "✅ Deployment complete"
|