@salesforce/afv-skills 1.44.0 → 1.45.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 (97) hide show
  1. package/package.json +1 -1
  2. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +72 -0
  3. package/skills/consumer-goods-rtr-datacloud-export-configure/references/inputs-and-namespace.md +38 -0
  4. package/skills/consumer-goods-rtr-datacloud-export-configure/references/procedure.md +158 -0
  5. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/detect-namespace.js +86 -0
  6. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/render-apex.js +64 -0
  7. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/resolve-id-by-name.js +47 -0
  8. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/sf-rest.js +171 -0
  9. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/soql-escape.js +26 -0
  10. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex +51 -0
  11. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex +21 -0
  12. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +74 -0
  13. package/skills/consumer-goods-tpe-dashboard-configure/references/phases-1-6.md +112 -0
  14. package/skills/consumer-goods-tpe-dashboard-configure/references/phases-7-12.md +157 -0
  15. package/skills/consumer-goods-tpe-dashboard-configure/scripts/find-failure-reason.js +132 -0
  16. package/skills/consumer-goods-tpe-dashboard-configure/scripts/poll-status.js +116 -0
  17. package/skills/consumer-goods-tpe-dashboard-configure/scripts/render-apex.js +64 -0
  18. package/skills/consumer-goods-tpe-dashboard-configure/scripts/run-data-transform.js +121 -0
  19. package/skills/consumer-goods-tpe-dashboard-configure/scripts/schedule-business-period-export.apex +27 -0
  20. package/skills/consumer-goods-tpe-dashboard-configure/scripts/sf-rest.js +171 -0
  21. package/skills/consumer-goods-tpe-dashboard-configure/scripts/soql-escape.js +25 -0
  22. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +141 -0
  23. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/payload-shapes.md +447 -0
  24. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/procedure.md +263 -0
  25. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/clone-tpe-dashboards.js +537 -0
  26. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/sf-rest.js +195 -0
  27. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +157 -0
  28. package/skills/consumer-goods-tpe-datakit-deploy/scripts/detect-namespace.js +86 -0
  29. package/skills/consumer-goods-tpe-datakit-deploy/scripts/download-static-resource.js +151 -0
  30. package/skills/consumer-goods-tpe-datakit-deploy/scripts/extract-crm-field-permissions.js +115 -0
  31. package/skills/consumer-goods-tpe-datakit-deploy/scripts/sf-rest.js +109 -0
  32. package/skills/consumer-goods-tpe-datakit-deploy/scripts/update-field-permissions.js +433 -0
  33. package/skills/service-catalog-template-coordinate/SKILL.md +263 -0
  34. package/skills/service-catalog-template-coordinate/examples/output-templates.md +44 -0
  35. package/skills/service-catalog-template-coordinate/references/mcp-invocation.md +183 -0
  36. package/skills/service-catalog-template-coordinate/references/operations.md +230 -0
  37. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +243 -0
  38. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/cli-invocation.md +205 -0
  39. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/helper-contracts.md +236 -0
  40. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/permset-topology.md +132 -0
  41. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-activated-agents.mjs +106 -0
  42. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-agent-access-state.mjs +113 -0
  43. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-assignment-state.mjs +99 -0
  44. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-platform-permset-availability.mjs +155 -0
  45. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/gate-unified-catalog-tiers.mjs +100 -0
  46. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/rank-candidate-users.mjs +95 -0
  47. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/resolve-target-user.mjs +86 -0
  48. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +44 -23
  49. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +33 -9
  50. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -18
  51. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +3 -1
  52. package/skills/service-itsm-agentic-setup-configure/SKILL.md +20 -12
  53. package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +73 -5
  54. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -7
  55. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +45 -33
  56. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +8 -6
  57. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +10 -10
  58. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +114 -56
  59. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +33 -17
  60. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +9 -3
  61. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -7
  62. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +43 -32
  63. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +6 -4
  64. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +10 -10
  65. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +106 -55
  66. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +27 -13
  67. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +9 -3
  68. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +159 -161
  69. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +51 -0
  70. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +1 -1
  71. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +120 -0
  72. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +28 -5
  73. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +19 -1
  74. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +350 -30
  75. package/skills/service-itsm-channels-coordinate/SKILL.md +80 -213
  76. package/skills/service-itsm-slack-itservice-configure/SKILL.md +363 -0
  77. package/skills/service-itsm-slack-itservice-configure/references/connect-agentforce-to-slack.md +159 -0
  78. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +88 -0
  79. package/skills/service-itsm-slack-itservice-configure/references/manage-user-access.md +117 -0
  80. package/skills/service-itsm-slack-itservice-configure/references/record-visibility.md +78 -0
  81. package/skills/service-itsm-slack-itservice-configure/references/site-membership-verification.md +126 -0
  82. package/skills/service-itsm-slack-itservice-configure/scripts/classify-user-access.mjs +167 -0
  83. package/skills/service-itsm-teams-configure/SKILL.md +50 -47
  84. package/skills/service-itsm-teams-configure/references/azure-credential-population.md +42 -28
  85. package/skills/service-itsm-teams-configure/references/gotchas.md +1 -2
  86. package/skills/service-itsm-teams-coordinate/SKILL.md +22 -18
  87. package/skills/service-itsm-teams-coordinate/examples/output-templates.md +12 -9
  88. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +60 -44
  89. package/skills/service-itsm-teams-itservice-configure/SKILL.md +56 -70
  90. package/skills/service-catalog-template-deploy/SKILL.md +0 -310
  91. package/skills/service-catalog-template-deploy/references/cli-invocation.md +0 -258
  92. package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +0 -164
  93. package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +0 -94
  94. package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +0 -331
  95. package/skills/service-catalog-template-search/SKILL.md +0 -212
  96. package/skills/service-catalog-template-search/references/cli-invocation.md +0 -128
  97. package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +0 -205
@@ -3,51 +3,67 @@
3
3
  //
4
4
  // Idempotency for this flow is keyed PRIMARILY on the TEMPLATE's own
5
5
  // `botDefinitionId` — the platform's authoritative link from an `agent-templates`
6
- // row to the live `BotDefinition` it was instantiated into — and FALLS BACK to a
7
- // `DeveloperName`-keyed read when that row carries no `botDefinitionId`.
6
+ // row to the live `BotDefinition` it was instantiated into — and FALLS BACK
7
+ // first to the BotDefinition's own `AgentTemplate` field (the OOTB, namespaced
8
+ // template API name it was instantiated from, e.g.
9
+ // `svc_itsm_intelligence__ITSrvcMgmtFulfiller`), then to a `DeveloperName`-keyed
10
+ // read.
8
11
  // If the Fulfiller template was already instantiated under a DeveloperName that
9
12
  // differs from this skill's default collected `IT_Service_Fulfiller_Agent`, a
10
13
  // DeveloperName-only read false-negatives (exists:false) and the create then
11
- // collides on `apiName` with `DUPLICATE_VALUE` — hence botDefinitionId is the
12
- // primary key. But botDefinitionId is back-filled onto the template row ONLY for
13
- // platform-pre-provisioned agents (the ITSM create path never stamps
14
- // `templateName`, so the platform never joins the agent-templates row to its
15
- // BotDefinition), and the Fulfiller is never pre-provisioned — so its template
16
- // `botDefinitionId` is null on the first run and every run after, and the
17
- // DeveloperName-keyed read is the guard that actually protects this path, used as
18
- // the FALLBACK below. (Preflight emits `template.botDefinitionId`; the workflow
19
- // passes it as the primary key and the collected developerName as the fallback key.)
14
+ // collides on `apiName` with `DUPLICATE_VALUE`. `AgentTemplate` closes that gap
15
+ // far more reliably than a display name would: it is the platform-stamped source
16
+ // template on the live BotDefinition, so it survives any DeveloperName rename and
17
+ // carries the namespace prefix the skill already knows (preflight `template.id`).
18
+ // The template-row `botDefinitionId` is back-filled ONLY for platform-
19
+ // pre-provisioned agents (the ITSM create path never stamps `templateName`, so
20
+ // the platform never joins the agent-templates row to its BotDefinition), and the
21
+ // Fulfiller is never pre-provisioned — so its template `botDefinitionId` is null
22
+ // on the first run and every run after. Self-created Fulfiller agents also carry
23
+ // a NULL `AgentTemplate` (the create path doesn't stamp the source template), so
24
+ // for the Fulfiller the DeveloperName-keyed read is the guard that actually
25
+ // protects this path; `AgentTemplate` is a defensive key that would catch a
26
+ // hypothetical pre-provisioned Fulfiller and keeps this classifier identical to
27
+ // the Employee one (where `AgentTemplate` IS the load-bearing catcher). (Preflight
28
+ // emits `template.botDefinitionId`, `template.id`, and the collected
29
+ // developerName; the workflow passes the botDefinitionId as the primary key,
30
+ // template.id as the AgentTemplate key, and the developerName as the fallback.)
20
31
  //
21
32
  // This classifier consumes the raw JSON of a `sf data query ... --json` read of
22
- // BotDefinition (BY Id primarily, BY DeveloperName on the fallback) and decides
33
+ // BotDefinition (BY Id primarily, BY AgentTemplate then DeveloperName on the
34
+ // fallback) and decides
23
35
  // whether the agent already exists — and if so, whether its latest BotVersion is
24
36
  // Inactive (so the workflow can offer to activate it instead of creating a
25
37
  // duplicate). This is the deterministic decision that gates whether the write is
26
38
  // skipped — it MUST NOT be interpreted by the model in prose (authoring standard A9).
27
39
  //
28
40
  // Usage:
29
- // node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName]
41
+ // node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName] [agentTemplate]
30
42
  //
31
43
  // When <botDefinitionId> is empty, `-`, or the literal `null`/`undefined` (the
32
44
  // matched template carries no botDefinitionId), the classifier FALLS BACK to
33
- // keying on <developerName>, and <bot-query.json> must be a DeveloperName-keyed
34
- // BotDefinition read: self-created agents are never back-filled with a
35
- // botDefinitionId (the ITSM create path doesn't stamp `templateName`, so the
36
- // platform never joins the agent-templates row to its BotDefinition), so
37
- // DeveloperName is the only idempotency guard left for them. Only when BOTH
38
- // <botDefinitionId> and <developerName> are absent is exists:false emitted
39
- // without reading a query file. Otherwise <bot-query.json> is a FILE PATH to the
40
- // stdout captured from:
41
- // sf data query -q "SELECT Id,DeveloperName,MasterLabel,
45
+ // keying on <agentTemplate> (the source template API name), and then to
46
+ // <developerName> when the AgentTemplate misses too. <bot-query.json> must
47
+ // therefore be a read that ORs all three keys together: self-created agents are
48
+ // never back-filled with a botDefinitionId AND carry a null AgentTemplate (the
49
+ // ITSM create path doesn't stamp `templateName`), so DeveloperName is their only
50
+ // idempotency guard; a pre-provisioned agent whose live DeveloperName differs from
51
+ // this skill's collected guess is caught by AgentTemplate. Only when ALL of
52
+ // <botDefinitionId>, <agentTemplate>, and <developerName> are absent is
53
+ // exists:false emitted without reading a query file. Otherwise <bot-query.json> is
54
+ // a FILE PATH to the stdout captured from:
55
+ // sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,
42
56
  // (SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1)
43
- // FROM BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'" \
57
+ // FROM BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>'
58
+ // OR DeveloperName='<developerName>'" \
44
59
  // --target-org <org> --json > file.json
45
- // (The present-id read ORs in DeveloperName so a DANGLING template→BotDefinition
46
- // link — a botDefinitionId whose target BotDefinition was since deleted — still
47
- // surfaces the live same-name agent: the classifier tries the Id first, then
48
- // falls back to the name on the SAME records. The pure FALLBACK read, taken when
49
- // the template row carries no botDefinitionId at all, keys on
50
- // `DeveloperName='<developerName>'` only; everything else is identical.
60
+ // (The present-id read ORs in AgentTemplate AND DeveloperName so a DANGLING
61
+ // template→BotDefinition link — a botDefinitionId whose target BotDefinition was
62
+ // since deleted — still surfaces the live agent: the classifier tries the Id
63
+ // first, then the AgentTemplate, then the DeveloperName on the SAME records. The
64
+ // pure FALLBACK read, taken when the template row carries no botDefinitionId at
65
+ // all, keys on `AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'`;
66
+ // everything else is identical.
51
67
  // `sf data query --json` wraps results in a `.result.records[]` envelope. The
52
68
  // BotVersions subquery is required so this classifier can see the latest
53
69
  // version's Status — a query without it leaves latestVersionStatus null and
@@ -62,16 +78,17 @@
62
78
  // child row was returned), and needsActivation is true only when exists is true
63
79
  // AND latestVersionStatus is "Inactive" — the signal to offer activating the
64
80
  // existing version instead of creating a new agent. `matchedBy` is
65
- // "botDefinitionId" | "developerName" | null, recording which key hit. Exit code
81
+ // "botDefinitionId" | "agentTemplate" | "developerName" | null, recording which
82
+ // key hit. Exit code
66
83
  // is always 0 on a parseable body (or when neither key is supplied); the verdict
67
84
  // is carried in the payload. On an unparseable/failed query it exits 3 so the
68
85
  // workflow surfaces the raw error rather than assuming NOT-EXISTS.
69
86
 
70
87
  import { readFileSync } from 'node:fs';
71
88
 
72
- const [queryPath, rawBotDefinitionId, rawDeveloperName] = process.argv.slice(2);
89
+ const [queryPath, rawBotDefinitionId, rawDeveloperName, rawAgentTemplate] = process.argv.slice(2);
73
90
  if (!queryPath) {
74
- process.stderr.write('usage: node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName]\n');
91
+ process.stderr.write('usage: node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName] [agentTemplate]\n');
75
92
  process.exit(2);
76
93
  }
77
94
 
@@ -89,8 +106,10 @@ function notSet(v) {
89
106
 
90
107
  const botDefinitionId = String(rawBotDefinitionId ?? '').trim();
91
108
  const developerName = String(rawDeveloperName ?? '').trim();
109
+ const agentTemplate = String(rawAgentTemplate ?? '').trim();
92
110
  const noId = notSet(botDefinitionId);
93
111
  const noName = notSet(developerName);
112
+ const noTemplate = notSet(agentTemplate);
94
113
 
95
114
  const NOT_EXISTS = {
96
115
  exists: false,
@@ -104,11 +123,12 @@ const NOT_EXISTS = {
104
123
  needsActivation: false,
105
124
  };
106
125
 
107
- // Nothing to key on — no botDefinitionId AND no developerName fallback. There is
108
- // no way to detect an existing agent, so this is the create path. (The workflow
109
- // should always pass the collected developerName so the fallback below can run;
110
- // this bare branch only fires when neither key is supplied.)
111
- if (noId && noName) {
126
+ // Nothing to key on — no botDefinitionId AND no AgentTemplate fallback AND no
127
+ // DeveloperName fallback. There is no way to detect an existing agent, so this is
128
+ // the create path. (The workflow should always pass the collected developerName
129
+ // and the template's id/AgentTemplate so the fallbacks below can run; this bare
130
+ // branch only fires when none of the three keys is supplied.)
131
+ if (noId && noTemplate && noName) {
112
132
  process.stdout.write(JSON.stringify(NOT_EXISTS, null, 2) + '\n');
113
133
  process.exit(0);
114
134
  }
@@ -139,20 +159,22 @@ const records = Array.isArray(data.result?.records) ? data.result.records : [];
139
159
  // `templateName`), so in practice the DeveloperName fallback below is what fires.
140
160
  // The matching LOGIC here is kept identical to the Employee classifier on
141
161
  // purpose — one shared matching contract, so the two can't drift and a future
142
- // platform change that DOES populate the Fulfiller's botDefinitionId is handled
143
- // correctly rather than silently mismatched; see that script's header for the
144
- // pre-provisioned rationale it exercises.
145
- // FALLBACK: the collected DeveloperName. Self-created agents are NOT
146
- // back-filled — the ITSM create path never stamps `templateName`, so the
147
- // platform never joins the agent-templates row to its BotDefinition and
148
- // `botDefinitionId` stays null on every later read — so DeveloperName is their
149
- // only guard. We ALSO fall back to DeveloperName when a PRESENT botDefinitionId
162
+ // platform change that DOES populate the Fulfiller's botDefinitionId/AgentTemplate
163
+ // is handled correctly rather than silently mismatched; see that script's header
164
+ // for the pre-provisioned rationale it exercises.
165
+ // FALLBACK: the BotDefinition's own `AgentTemplate`, then the collected
166
+ // DeveloperName. `AgentTemplate` is the OOTB source-template API name the platform
167
+ // stamps on a template-instantiated agent — the reliable "an agent from THIS
168
+ // template already exists" signal for pre-provisioned agents, independent of any
169
+ // DeveloperName rename. Self-created agents are NOT stamped (the ITSM create path
170
+ // never sets `templateName`), so their `AgentTemplate` is null and DeveloperName
171
+ // is their guard. We ALSO fall back this way when a PRESENT botDefinitionId
150
172
  // matches nothing (a dangling link whose target BotDefinition was deleted): the
151
- // present-id Phase-2 SOQL reads `WHERE Id=... OR DeveloperName=...`, so a stale
152
- // link still surfaces the live same-name agent instead of slipping through to the
153
- // create path and colliding with DUPLICATE_VALUE. Try the Id first, then the
154
- // name; each filter is self-checking on top of the SOQL WHERE so a wrong-keyed
155
- // read can't false-positive.
173
+ // present-id Phase-2 SOQL reads `WHERE Id=... OR AgentTemplate=... OR
174
+ // DeveloperName=...`, so a stale link still surfaces the live agent instead of
175
+ // slipping through to the create path and colliding with DUPLICATE_VALUE. Try the
176
+ // Id first, then the template, then the name; each filter is self-checking on top
177
+ // of the SOQL WHERE so a wrong-keyed read can't false-positive.
156
178
  let matches = [];
157
179
  let matchedBy = null;
158
180
  if (!noId) {
@@ -160,12 +182,38 @@ if (!noId) {
160
182
  matches = records.filter((r) => idKey(r?.Id) === wantedIdKey);
161
183
  if (matches.length) matchedBy = 'botDefinitionId';
162
184
  }
185
+ // FIRST FALLBACK: the BotDefinition's `AgentTemplate` (source template API name).
186
+ // When a live agent was instantiated from this template under a DeveloperName
187
+ // that differs from this skill's collected guess AND its template row carries no
188
+ // botDefinitionId, the Id and DeveloperName keys both miss and the create then
189
+ // collides with DUPLICATE_VALUE. `AgentTemplate` is the platform-stamped source
190
+ // template, so matching on it recovers that live agent reliably. SOQL text
191
+ // comparison is case-insensitive, so compare the same way. `AgentTemplate` is NOT
192
+ // unique (a template can be instantiated more than once), so if more than one
193
+ // record shares it we keep them all but surface an Active version first (see
194
+ // below) — the report should treat the already-active one as the real match.
195
+ if (!matches.length && !noTemplate) {
196
+ const wantedTemplate = agentTemplate.toLowerCase();
197
+ matches = records.filter((r) => String(r?.AgentTemplate ?? '').trim().toLowerCase() === wantedTemplate);
198
+ if (matches.length) matchedBy = 'agentTemplate';
199
+ }
200
+ // SECOND FALLBACK: the collected DeveloperName — the guard for self-created agents
201
+ // (null AgentTemplate). DeveloperName uniqueness is case-insensitive; compare
202
+ // accordingly.
163
203
  if (!matches.length && !noName) {
164
- // DeveloperName uniqueness is case-insensitive; compare accordingly.
165
204
  const wantedName = developerName.toLowerCase();
166
205
  matches = records.filter((r) => String(r?.DeveloperName ?? '').trim().toLowerCase() === wantedName);
167
206
  if (matches.length) matchedBy = 'developerName';
168
207
  }
208
+ if (matchedBy === 'agentTemplate' && matches.length > 1) {
209
+ // Prefer a record whose latest BotVersion is Active so `matches[0]` (used for
210
+ // latestVersion/agentId below) reflects the live agent rather than a stale draft.
211
+ matches = [...matches].sort((a, b) => {
212
+ const aActive = a?.BotVersions?.records?.[0]?.Status === 'Active' ? 0 : 1;
213
+ const bActive = b?.BotVersions?.records?.[0]?.Status === 'Active' ? 0 : 1;
214
+ return aActive - bActive;
215
+ });
216
+ }
169
217
  const exists = matches.length > 0;
170
218
 
171
219
  // The BotVersions child subquery (if present in the SOQL) surfaces the latest
@@ -180,12 +228,15 @@ const needsActivation = exists && latestVersionStatus === 'Inactive';
180
228
  process.stdout.write(JSON.stringify({
181
229
  exists,
182
230
  count: matches.length,
183
- // Which key matched — `botDefinitionId` (primary) or `developerName` (the
184
- // fallback for self-created agents). null when nothing matched.
231
+ // Which key matched — `botDefinitionId` (primary), `agentTemplate` (the
232
+ // reliable catcher for a pre-provisioned agent whose DeveloperName differs from
233
+ // the collected guess), or `developerName` (the fallback for self-created
234
+ // agents). null when nothing matched.
185
235
  matchedBy: exists ? matchedBy : null,
186
236
  agentId: exists ? (matches[0].Id ?? null) : null,
187
- // On a DeveloperName-fallback hit this surfaces the live BotDefinition Id the
188
- // template row was missing; on the Id path it echoes the queried id.
237
+ // On an AgentTemplate- or DeveloperName-fallback hit this surfaces the live
238
+ // BotDefinition Id the template row was missing; on the Id path it echoes the
239
+ // queried id.
189
240
  botDefinitionId: exists ? (matches[0].Id ?? null) : (noId ? null : botDefinitionId),
190
241
  developerName: exists ? (matches[0].DeveloperName ?? null) : null,
191
242
  latestVersionId: latestVersion?.Id ?? null,
@@ -20,12 +20,17 @@
20
20
  //
21
21
  // Emits a single JSON object to stdout:
22
22
  // { studio: { hasAccess, signal, reason },
23
- // template: { present, id, hasAgentScript, botDefinitionId, signal, reason },
23
+ // template: { present, id, hasAgentScript, botDefinitionId, masterLabel, signal, reason },
24
24
  // verdict, reasons: [...] }
25
25
  // `botDefinitionId` is copied from the matched agent-templates row and passed to
26
26
  // the Phase-2 idempotency read as the PRIMARY key (the platform's authoritative
27
- // template→BotDefinition link); the collected developerName is the FALLBACK key
28
- // there. See classify-agent-existence.mjs for why. (The template row's
27
+ // template→BotDefinition link); `id` (the template's OOTB namespaced API name) is
28
+ // passed as the AgentTemplate fallback key — it matches `BotDefinition.AgentTemplate`
29
+ // on a template-instantiated agent regardless of its DeveloperName — and the
30
+ // collected developerName is the last fallback (the guard for self-created agents,
31
+ // which carry a null AgentTemplate). `masterLabel` is echoed only for the report's
32
+ // display label; it is NOT an idempotency key (a display name is renamable and
33
+ // non-unique). See classify-agent-existence.mjs for why. (The template row's
29
34
  // `isInstalled`/`isActivated` flags are NOT emitted: they ride the same
30
35
  // AgentTemplate join as botDefinitionId, so for agents this skill creates — which
31
36
  // never stamp `templateName` — they read false even after the agent exists, and
@@ -107,16 +112,18 @@ function classifyTemplate(res, masterLabel) {
107
112
  }
108
113
  // Idempotency identity carried by the matched row — the platform's own link
109
114
  // from this template to the BotDefinition it was instantiated into. A populated
110
- // botDefinitionId is the reliable idempotency signal for PRE-PROVISIONED agents
111
- // (see classify-agent-existence.mjs): it means the agent already exists
112
- // regardless of what DeveloperName this skill would have guessed. Agents this
113
- // skill creates never stamp `templateName`, so their row's botDefinitionId stays
114
- // null — the Phase-2 read falls back to a DeveloperName key for that case.
115
+ // botDefinitionId is a direct link for PRE-PROVISIONED agents; when it is null,
116
+ // the template's own `id` (matched against `BotDefinition.AgentTemplate` in
117
+ // Phase 2) still catches a pre-provisioned agent regardless of what DeveloperName
118
+ // this skill would have guessed (see classify-agent-existence.mjs). Agents this
119
+ // skill creates never stamp `templateName`, so they carry BOTH a null row
120
+ // botDefinitionId AND a null AgentTemplate — the Phase-2 read falls back to the
121
+ // collected DeveloperName key for that case.
115
122
  const botDefinitionId = typeof match.botDefinitionId === 'string' && match.botDefinitionId.trim() ? match.botDefinitionId : null;
116
123
  if (!match.agentScript || typeof match.agentScript !== 'string' || !match.agentScript.trim()) {
117
124
  return { present: true, id: match.id ?? null, hasAgentScript: false, botDefinitionId, signal: 'CANNOT-CONFIRM', reason: `Matched "${masterLabel}" (id=${match.id}) but it had no agentScript field — the NGA bundle create needs the template's Agent Script content.` };
118
125
  }
119
- return { present: true, id: match.id ?? null, hasAgentScript: true, botDefinitionId, signal: 'PASS', reason: `Template "${masterLabel}" present (id=${match.id}) with a non-empty agentScript${botDefinitionId ? `; already instantiated (botDefinitionId=${botDefinitionId})` : '; not yet instantiated (no botDefinitionId — the Phase-2 read falls back to DeveloperName)'} — feed it to scripts/build-create-body.mjs for the NGA bundle create.` };
126
+ return { present: true, id: match.id ?? null, hasAgentScript: true, botDefinitionId, signal: 'PASS', reason: `Template "${masterLabel}" present (id=${match.id}) with a non-empty agentScript${botDefinitionId ? `; already instantiated (botDefinitionId=${botDefinitionId})` : '; not yet instantiated (no botDefinitionId — the Phase-2 read keys on AgentTemplate=id, then DeveloperName)'} — feed it to scripts/build-create-body.mjs for the NGA bundle create.` };
120
127
  }
121
128
 
122
129
  const [studioPath, templatePath, rawLabel] = process.argv.slice(2);
@@ -145,11 +152,18 @@ process.stdout.write(JSON.stringify({
145
152
  id: template.id,
146
153
  hasAgentScript: template.hasAgentScript,
147
154
  // Idempotency identity (from the matched agent-templates row): the Phase-2
148
- // read uses botDefinitionId as the PRIMARY key and the collected developerName
149
- // as the FALLBACK. A populated botDefinitionId is the load-bearing "agent
150
- // already exists" signal for pre-provisioned agents; null just means this
151
- // template row was never joined (e.g. a self-created agent) — not "no agent".
155
+ // read uses botDefinitionId as the PRIMARY key, `id` (the template's namespaced
156
+ // API name) as the AgentTemplate key, and the collected developerName as the
157
+ // last fallback. A populated botDefinitionId is a direct "agent already exists"
158
+ // link; null just means this template row was never joined (e.g. a self-created
159
+ // agent) — not "no agent". `id` is the reliable catcher for a pre-provisioned
160
+ // agent whose DeveloperName differs from this skill's collected guess: it
161
+ // matches `BotDefinition.AgentTemplate`, the platform-stamped source template.
162
+ // `masterLabel` is echoed only for the report's display label (NOT an
163
+ // idempotency key). The workflow passes botDefinitionId, `id`, and the collected
164
+ // developerName to classify-agent-existence.mjs from this single source.
152
165
  botDefinitionId: template.botDefinitionId ?? null,
166
+ masterLabel,
153
167
  signal: template.signal,
154
168
  reason: template.reason,
155
169
  },
@@ -144,13 +144,19 @@ if (
144
144
 
145
145
  const reason = String(state.reason ?? '').trim();
146
146
 
147
+ // The "set up access" next step below is the handoff to the
148
+ // `service-itsm-agentic-setup-agent-runtime-access-assign` skill (declared in
149
+ // this skill's metadata.relatedSkills). We deliberately surface it in plain
150
+ // admin language ("grant this agent's runtime access") rather than by raw skill
151
+ // name — the routing metadata carries the linkage, so the user-facing report
152
+ // stays friendly while the relationship remains discoverable.
147
153
  const NEXT_STEPS = {
148
154
  'CREATED':
149
- 'The IT Service Fulfiller agent is live as an NGA-native agent (no external-link icon in Agentforce Studio). No new agent was provisioned beyond the one just created. Verify end-to-end in Agentforce Studio\'s Agents list.',
155
+ 'The IT Service Fulfiller agent is live as an NGA-native agent (no external-link icon in Agentforce Studio). No new agent was provisioned beyond the one just created. Verify end-to-end in Agentforce Studio\'s Agents list. **Next — set up access:** before anyone can use the agent, the user needs the runtime permissions the agent\'s actions use (Prompt Templates, Data Cloud, or Unified Catalog, depending on what is provisioned). Just ask to **grant this agent\'s runtime access** and those feature permissions — plus an "Agent Access" permission set covering the agent — will be set up for the user(s) you choose.',
150
156
  'ALREADY-CREATED':
151
- 'The IT Service Fulfiller agent was already present and active — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list.',
157
+ 'The IT Service Fulfiller agent was already present and active — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list. **Next — set up access:** before anyone can use the agent, the user needs the runtime permissions the agent\'s actions use (Prompt Templates, Data Cloud, or Unified Catalog, depending on what is provisioned). Just ask to **grant this agent\'s runtime access** and those feature permissions — plus an "Agent Access" permission set covering the agent — will be set up for the user(s) you choose.',
152
158
  'ACTIVATED':
153
- 'The existing IT Service Fulfiller agent was found inactive and has been activated — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list.',
159
+ 'The existing IT Service Fulfiller agent was found inactive and has been activated — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list. **Next — set up access:** before anyone can use the agent, the user needs the runtime permissions the agent\'s actions use (Prompt Templates, Data Cloud, or Unified Catalog, depending on what is provisioned). Just ask to **grant this agent\'s runtime access** and those feature permissions — plus an "Agent Access" permission set covering the agent — will be set up for the user(s) you choose.',
154
160
  'PENDING CONFIRMATION':
155
161
  'The run is paused at the confirm-to-write gate. In this skill, create + publish + activate happen as one atomic sequence gated by a single confirmation — there is no supported create-only or publish-without-activate mode, and no partial "create-only" mode of any kind. Re-run and reply "yes" when you are ready for the agent to go live.',
156
162
  'DECLINED':