@salesforce/afv-skills 1.44.0 → 1.46.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 (109) hide show
  1. package/package.json +1 -1
  2. package/skills/automation-flow-generate/SKILL.md +11 -5
  3. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +72 -0
  4. package/skills/consumer-goods-rtr-datacloud-export-configure/references/inputs-and-namespace.md +38 -0
  5. package/skills/consumer-goods-rtr-datacloud-export-configure/references/procedure.md +158 -0
  6. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/detect-namespace.js +86 -0
  7. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/render-apex.js +64 -0
  8. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/resolve-id-by-name.js +47 -0
  9. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/sf-rest.js +171 -0
  10. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/soql-escape.js +26 -0
  11. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex +51 -0
  12. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex +21 -0
  13. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +74 -0
  14. package/skills/consumer-goods-tpe-dashboard-configure/references/phases-1-6.md +112 -0
  15. package/skills/consumer-goods-tpe-dashboard-configure/references/phases-7-12.md +157 -0
  16. package/skills/consumer-goods-tpe-dashboard-configure/scripts/find-failure-reason.js +132 -0
  17. package/skills/consumer-goods-tpe-dashboard-configure/scripts/poll-status.js +116 -0
  18. package/skills/consumer-goods-tpe-dashboard-configure/scripts/render-apex.js +64 -0
  19. package/skills/consumer-goods-tpe-dashboard-configure/scripts/run-data-transform.js +121 -0
  20. package/skills/consumer-goods-tpe-dashboard-configure/scripts/schedule-business-period-export.apex +27 -0
  21. package/skills/consumer-goods-tpe-dashboard-configure/scripts/sf-rest.js +171 -0
  22. package/skills/consumer-goods-tpe-dashboard-configure/scripts/soql-escape.js +25 -0
  23. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +141 -0
  24. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/payload-shapes.md +447 -0
  25. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/procedure.md +263 -0
  26. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/clone-tpe-dashboards.js +537 -0
  27. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/sf-rest.js +195 -0
  28. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +157 -0
  29. package/skills/consumer-goods-tpe-datakit-deploy/scripts/detect-namespace.js +86 -0
  30. package/skills/consumer-goods-tpe-datakit-deploy/scripts/download-static-resource.js +151 -0
  31. package/skills/consumer-goods-tpe-datakit-deploy/scripts/extract-crm-field-permissions.js +115 -0
  32. package/skills/consumer-goods-tpe-datakit-deploy/scripts/sf-rest.js +109 -0
  33. package/skills/consumer-goods-tpe-datakit-deploy/scripts/update-field-permissions.js +433 -0
  34. package/skills/dx-code-analyzer-configure/scripts/validate-config.sh +14 -10
  35. package/skills/dx-code-analyzer-run/scripts/apply-fixes.js +45 -4
  36. package/skills/dx-code-analyzer-run/scripts/describe-rule.js +52 -32
  37. package/skills/platform-apex-logs-debug/SKILL.md +7 -7
  38. package/skills/platform-custom-application-generate/SKILL.md +4 -4
  39. package/skills/platform-custom-object-generate/SKILL.md +7 -7
  40. package/skills/platform-custom-tab-generate/SKILL.md +1 -1
  41. package/skills/platform-flexipage-generate/SKILL.md +4 -0
  42. package/skills/platform-list-view-generate/SKILL.md +1 -0
  43. package/skills/platform-soql-query/SKILL.md +8 -8
  44. package/skills/platform-value-set-generate/SKILL.md +2 -2
  45. package/skills/service-catalog-template-coordinate/SKILL.md +263 -0
  46. package/skills/service-catalog-template-coordinate/examples/output-templates.md +44 -0
  47. package/skills/service-catalog-template-coordinate/references/mcp-invocation.md +183 -0
  48. package/skills/service-catalog-template-coordinate/references/operations.md +230 -0
  49. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +243 -0
  50. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/cli-invocation.md +205 -0
  51. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/helper-contracts.md +236 -0
  52. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/permset-topology.md +132 -0
  53. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-activated-agents.mjs +106 -0
  54. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-agent-access-state.mjs +113 -0
  55. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-assignment-state.mjs +99 -0
  56. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-platform-permset-availability.mjs +155 -0
  57. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/gate-unified-catalog-tiers.mjs +100 -0
  58. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/rank-candidate-users.mjs +95 -0
  59. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/resolve-target-user.mjs +86 -0
  60. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +44 -23
  61. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +33 -9
  62. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -18
  63. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +3 -1
  64. package/skills/service-itsm-agentic-setup-configure/SKILL.md +20 -12
  65. package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +73 -5
  66. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -7
  67. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +45 -33
  68. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +8 -6
  69. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +10 -10
  70. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +114 -56
  71. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +33 -17
  72. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +9 -3
  73. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -7
  74. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +43 -32
  75. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +6 -4
  76. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +10 -10
  77. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +106 -55
  78. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +27 -13
  79. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +9 -3
  80. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +159 -161
  81. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +51 -0
  82. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +1 -1
  83. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +120 -0
  84. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +28 -5
  85. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +19 -1
  86. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +350 -30
  87. package/skills/service-itsm-channels-coordinate/SKILL.md +80 -213
  88. package/skills/service-itsm-slack-itservice-configure/SKILL.md +363 -0
  89. package/skills/service-itsm-slack-itservice-configure/references/connect-agentforce-to-slack.md +159 -0
  90. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +88 -0
  91. package/skills/service-itsm-slack-itservice-configure/references/manage-user-access.md +117 -0
  92. package/skills/service-itsm-slack-itservice-configure/references/record-visibility.md +78 -0
  93. package/skills/service-itsm-slack-itservice-configure/references/site-membership-verification.md +126 -0
  94. package/skills/service-itsm-slack-itservice-configure/scripts/classify-user-access.mjs +167 -0
  95. package/skills/service-itsm-teams-configure/SKILL.md +50 -47
  96. package/skills/service-itsm-teams-configure/references/azure-credential-population.md +42 -28
  97. package/skills/service-itsm-teams-configure/references/gotchas.md +1 -2
  98. package/skills/service-itsm-teams-coordinate/SKILL.md +22 -18
  99. package/skills/service-itsm-teams-coordinate/examples/output-templates.md +12 -9
  100. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +60 -44
  101. package/skills/service-itsm-teams-itservice-configure/SKILL.md +56 -70
  102. package/skills/service-catalog-template-deploy/SKILL.md +0 -310
  103. package/skills/service-catalog-template-deploy/references/cli-invocation.md +0 -258
  104. package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +0 -164
  105. package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +0 -94
  106. package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +0 -331
  107. package/skills/service-catalog-template-search/SKILL.md +0 -212
  108. package/skills/service-catalog-template-search/references/cli-invocation.md +0 -128
  109. package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +0 -205
@@ -20,16 +20,21 @@
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
29
- // `isInstalled`/`isActivated` flags are NOT emitted: they ride the same
30
- // AgentTemplate join as botDefinitionId, so for agents this skill creates — which
31
- // never stamp `templateName` — they read false even after the agent exists, and
32
- // nothing downstream consumes them.)
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 the pre-provisioned broad `IT_Service_Employee` agent regardless of its
30
+ // DeveloperName, so it is THE key that catches it — and the collected developerName
31
+ // is the last fallback (the guard for self-created agents, which carry a null
32
+ // AgentTemplate). `masterLabel` is echoed only for the report's display label; it
33
+ // is NOT an idempotency key (a display name is renamable and non-unique). See
34
+ // classify-agent-existence.mjs for why. (The template row's `isInstalled`/`isActivated`
35
+ // flags are NOT emitted: they ride the same AgentTemplate join as botDefinitionId,
36
+ // so for agents this skill creates — which never stamp `templateName` — they read
37
+ // false even after the agent exists, and nothing downstream consumes them.)
33
38
  // where signal is PASS | FAIL | CANNOT-CONFIRM | ERROR and verdict is
34
39
  // READY | NOT-READY | CANNOT-CONFIRM | ERROR. Exit is always 0 on usable args.
35
40
  // ERROR (studio only) means the Studio-access read returned a parseable but
@@ -117,16 +122,19 @@ function classifyTemplate(res, masterLabel) {
117
122
  }
118
123
  // Idempotency identity carried by the matched row — the platform's own link
119
124
  // from this template to the BotDefinition it was instantiated into. A populated
120
- // botDefinitionId is the reliable idempotency signal for PRE-PROVISIONED agents
121
- // (see classify-agent-existence.mjs): it means the agent already exists
122
- // regardless of what DeveloperName this skill would have guessed. Agents this
123
- // skill creates never stamp `templateName`, so their row's botDefinitionId stays
124
- // null — the Phase-2 read falls back to a DeveloperName key for that case.
125
+ // botDefinitionId is a direct link for PRE-PROVISIONED agents; for the broad
126
+ // Employee agent it is null, so the template's own `id` (matched against
127
+ // `BotDefinition.AgentTemplate` in Phase 2) is what catches the pre-provisioned
128
+ // `IT_Service_Employee` regardless of what DeveloperName this skill would have
129
+ // guessed (see classify-agent-existence.mjs). Agents this skill creates never
130
+ // stamp `templateName`, so they carry BOTH a null row botDefinitionId AND a null
131
+ // AgentTemplate — the Phase-2 read falls back to the collected DeveloperName key
132
+ // for that case.
125
133
  const botDefinitionId = typeof match.botDefinitionId === 'string' && match.botDefinitionId.trim() ? match.botDefinitionId : null;
126
134
  if (!match.agentScript || typeof match.agentScript !== 'string' || !match.agentScript.trim()) {
127
135
  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.` };
128
136
  }
129
- 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.` };
137
+ 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.` };
130
138
  }
131
139
 
132
140
  const [studioPath, templatePath, rawLabel] = process.argv.slice(2);
@@ -155,11 +163,19 @@ process.stdout.write(JSON.stringify({
155
163
  id: template.id,
156
164
  hasAgentScript: template.hasAgentScript,
157
165
  // Idempotency identity (from the matched agent-templates row): the Phase-2
158
- // read uses botDefinitionId as the PRIMARY key and the collected developerName
159
- // as the FALLBACK. A populated botDefinitionId is the load-bearing "agent
160
- // already exists" signal for pre-provisioned agents; null just means this
161
- // template row was never joined (e.g. a self-created agent) — not "no agent".
166
+ // read uses botDefinitionId as the PRIMARY key, `id` (the template's namespaced
167
+ // API name) as the AgentTemplate key, and the collected developerName as the
168
+ // last fallback. A populated botDefinitionId is a direct "agent already exists"
169
+ // link; null just means this template row was never joined (e.g. a self-created
170
+ // agent) — not "no agent". `id` is the load-bearing catcher for the
171
+ // pre-provisioned broad `IT_Service_Employee`, whose DeveloperName differs from
172
+ // this skill's collected guess: it matches `BotDefinition.AgentTemplate`, the
173
+ // platform-stamped source template. `masterLabel` is echoed only for the
174
+ // report's display label (NOT an idempotency key). The workflow passes
175
+ // botDefinitionId, `id`, and the collected developerName to
176
+ // classify-agent-existence.mjs from this single source.
162
177
  botDefinitionId: template.botDefinitionId ?? null,
178
+ masterLabel,
163
179
  signal: template.signal,
164
180
  reason: template.reason,
165
181
  },
@@ -148,13 +148,19 @@ if (
148
148
 
149
149
  const reason = String(state.reason ?? '').trim();
150
150
 
151
+ // The "set up access" next step below is the handoff to the
152
+ // `service-itsm-agentic-setup-agent-runtime-access-assign` skill (declared in
153
+ // this skill's metadata.relatedSkills). We deliberately surface it in plain
154
+ // admin language ("grant this agent's runtime access") rather than by raw skill
155
+ // name — the routing metadata carries the linkage, so the user-facing report
156
+ // stays friendly while the relationship remains discoverable.
151
157
  const NEXT_STEPS = {
152
158
  'CREATED':
153
- 'The IT Service Employee 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.',
159
+ 'The IT Service Employee 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.',
154
160
  'ALREADY-CREATED':
155
- 'The IT Service Employee agent was already present and active — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list.',
161
+ 'The IT Service Employee 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.',
156
162
  'ACTIVATED':
157
- 'The existing IT Service Employee agent was found inactive and has been activated — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list.',
163
+ 'The existing IT Service Employee 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.',
158
164
  'PENDING CONFIRMATION':
159
165
  '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.',
160
166
  'DECLINED':
@@ -2,10 +2,11 @@
2
2
  name: service-itsm-agentic-setup-fulfiller-agent-configure
3
3
  description: "Create and activate the IT Service Fulfiller agent as a Next-Gen Authoring (NGA) native agent from the shipped ITSM Fulfiller template's Agent Script, using the Salesforce CLI (sf): read the template, check idempotency, create the NGA bundle then publish and activate it, then verify it is live. Idempotent per developer name. The Fulfiller agent is the IT-technician-facing assistant for incident triage, case summarization, field updates, and related-record automations. Use when asked to create the Fulfiller agent, set up the IT Service Fulfiller agent, provision the fulfiller assistant, or activate the Fulfiller agent. Triggers: create fulfiller agent, set up fulfiller agent, provision fulfiller agent, activate fulfiller agent. DO NOT TRIGGER: checking org prerequisites (service-itsm-agentic-setup-agentforce-studio-validate), or CMDB CRUD."
4
4
  metadata:
5
- version: "3.5"
5
+ version: "3.7"
6
6
  domains: ["Service", "Agentforce"]
7
7
  minApiVersion: "67.0"
8
8
  relatedSkills:
9
+ - "service-itsm-agentic-setup-agent-runtime-access-assign"
9
10
  - "service-itsm-agentic-setup-agentforce-studio-configure"
10
11
  - "service-itsm-agentic-setup-agentforce-studio-validate"
11
12
  - "service-itsm-agentic-setup-employee-agent-configure"
@@ -63,7 +64,7 @@ If any of these are unmet, `sf` surfaces an auth error or a `401`/`403`/`404`; *
63
64
  |---------|---------|-------|
64
65
  | Studio access (precondition read) | `sf api request rest "/services/data/v67.0/agentforce-studio/access/Agents" --method GET -o <alias>` | `hasAccess=false` ⇒ prerequisite hand-off |
65
66
  | List agent templates + Agent Script (read) | `sf api request rest "/services/data/v67.0/connect/service-itsm/agent-templates?agentType=AgentforceEmployeeAgent" --method GET -o <alias>` | `agentType=AgentforceEmployeeAgent` required; confirms Fulfiller template + non-empty `agentScript` |
66
- | Enumerate the existing agent + latest version status (read) | `sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'" -o <alias> --json` | Keyed PRIMARILY on the template's `botDefinitionId` (Phase-1 row); the `OR DeveloperName=` clause is both the null-`botDefinitionId` fallback (the normal Fulfiller case) AND the guard for a dangling Id link (deleted target). Classified by `scripts/classify-agent-existence.mjs`; Active latest ⇒ ALREADY-CREATED; Inactive latest ⇒ offer reactivation |
67
+ | Enumerate the existing agent + latest version status (read) | `sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'" -o <alias> --json` | Keyed PRIMARILY on the template's `botDefinitionId` (Phase-1 row); `OR AgentTemplate=` is a defensive fallback that would catch a live agent instantiated from the OOTB source template (`svc_itsm_intelligence__ITSrvcMgmtFulfiller` = Phase-1 `template.id`) regardless of its DeveloperName; `OR DeveloperName=` is the real guard here — the normal Fulfiller case (never pre-provisioned; null `AgentTemplate`) and the guard for a dangling Id link (deleted target). Classified by `scripts/classify-agent-existence.mjs`; Active latest ⇒ ALREADY-CREATED; Inactive latest ⇒ offer reactivation |
67
68
  | **Create the NGA bundle** (write) | `sf api request rest "/services/data/v67.0/nextgen-authoring/bundles" --method POST --body @<body-file> -o <alias>` | Body built by `scripts/build-create-body.mjs`; response `id` = the bundle **version** Id |
68
69
  | **Publish the bundle version** (write) | `sf api request rest "/services/data/v67.0/nextgen-authoring/bundle-versions/<bundleVersionId>/publish" --method POST --body '{}' -o <alias>` | Returns `publishedBotId`/`publishedBotVersionId` — creates the underlying `BotDefinition`/`BotVersion` |
69
70
  | **Activate the bundle version** (write) | `sf api request rest "/services/data/v67.0/nextgen-authoring/bundle-versions/<bundleVersionId>/activate" --method POST --body '{}' -o <alias>` | Empty response on success; agent is now live and NGA-native |
@@ -102,7 +103,7 @@ Full command shapes and the ITSM Connect API reference live in `references/cli-i
102
103
  | Activate | POST `.../bundle-versions/<id>/activate` (create path) OR POST `.../connect/bot-versions/<latestVersionId>/activation` with `{"status":"Active"}` (reactivation path — skips create/publish) | `Bash` (`sf api request rest`) |
103
104
  | Verify | SOQL-read `BotDefinition`/`BotVersions` and confirm the agent exists with an Active latest version | `Bash` (`sf data query`) |
104
105
 
105
- **Idempotency**: keyed PRIMARILY on the **template's `botDefinitionId`** (Phase-1 `agent-templates` row — the platform's authoritative template→`BotDefinition` link) and FALLING BACK to the collected `<developerName>`. The Phase-2 read is `BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'` (the `OR` half is both the null-`botDefinitionId` fallback — the normal Fulfiller case — AND the guard for a **dangling** Id link whose target `BotDefinition` was deleted, so a stale link can't slip through to create), + latest `BotVersion.Status` (classified by the helper script). Outcomes: no match on either key ⇒ `exists:false` ⇒ create; `latestVersionStatus:"Active"` ⇒ **ALREADY-CREATED** (skip the write, fall through to Phase 7 verification); `needsActivation:true` (latest version `Inactive`) ⇒ **offer to activate the existing version instead of creating a new agent** (Phase 2b) rather than silently skipping or duplicating. **Why the fallback matters:** the Fulfiller is never pre-provisioned and this skill's create path never stamps `templateName`, so the template's `botDefinitionId` is always `null` — the `<developerName>`-keyed fallback is the guard that actually catches a repeat run; short-circuiting straight to create on a null `botDefinitionId` would re-create and collide with `DUPLICATE_VALUE`. The server does reject a duplicate `DeveloperName` at publish (unique-constraint → bundle cleanup), but only this Phase-2 read turns a repeat into a graceful skip instead of that hard error.
106
+ **Idempotency**: keyed PRIMARILY on the **template's `botDefinitionId`** (Phase-1 `agent-templates` row — the platform's authoritative template→`BotDefinition` link), FALLING BACK first to the **BotDefinition's `AgentTemplate`** (the OOTB namespaced source template = Phase-1 `template.id`) and then to the collected `<developerName>`. The Phase-2 read is `BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'` (the `OR AgentTemplate=` half is a defensive key that would catch a live agent instantiated from the source template regardless of its `DeveloperName`; the `OR DeveloperName=` half is both the null-`botDefinitionId` fallback — the normal Fulfiller case — AND the guard for a **dangling** Id link whose target `BotDefinition` was deleted), + latest `BotVersion.Status` (classified by the helper script). Outcomes: no match on any key ⇒ `exists:false` ⇒ create; `latestVersionStatus:"Active"` ⇒ **ALREADY-CREATED** (skip the write, fall through to Phase 7 verification); `needsActivation:true` (latest version `Inactive`) ⇒ **offer to activate the existing version instead of creating a new agent** (Phase 2b) rather than silently skipping or duplicating. **Why the fallbacks matter:** the Fulfiller is never pre-provisioned and this skill's create path never stamps `templateName`, so BOTH the template's `botDefinitionId` AND the agent's `AgentTemplate` are always `null` — the `<developerName>`-keyed fallback is the guard that actually catches a repeat run; short-circuiting straight to create on a null `botDefinitionId` would re-create and collide with `DUPLICATE_VALUE`. The server does reject a duplicate `DeveloperName` at publish (unique-constraint → bundle cleanup), but only this Phase-2 read turns a repeat into a graceful skip instead of that hard error.
106
107
 
107
108
  ---
108
109
 
@@ -117,7 +118,7 @@ Collect from the user (ask only what is not already in conversation context):
117
118
  | Label | User-facing label for the agent | `IT Service Fulfiller Agent` |
118
119
  | Confirm the write | Explicit confirmation before the create/publish/activate sequence | **REQUIRED** — present the developerName + label and require "yes" via `AskUserQuestion` |
119
120
 
120
- The **idempotency read keys PRIMARILY on the template's `botDefinitionId`** (Phase-1 row) and FALLS BACK to the collected `<developerName>` when that is null; the verify read keys on the publish response's `publishedBotId` (create path) or the template's `botDefinitionId` (ALREADY-CREATED / reactivation). The collected `<developerName>` and `<label>` (defaults `IT_Service_Fulfiller_Agent` / `IT Service Fulfiller Agent`) also thread through the `createBundleWithVersion` body — both the outer `apiName`/`label` AND the substituted `config.developer_name`/`config.agent_label` inside the Agent Script. Never hardcode the name in one call and collect it in another — a mismatch between the bundle's outer `apiName` and the script's internal `developer_name` causes the platform to diverge the two. Creating an agent provisions a live, activated agent on the org; the user must explicitly approve the write.
121
+ The **idempotency read keys PRIMARILY on the template's `botDefinitionId`** (Phase-1 row) and FALLS BACK to the BotDefinition's `AgentTemplate` (defensive — null on the never-pre-provisioned Fulfiller) and then to the collected `<developerName>`, the guard that actually catches a repeat here, when that is null; the verify read keys on the publish response's `publishedBotId` (create path) or the template's `botDefinitionId` (ALREADY-CREATED / reactivation). The collected `<developerName>` and `<label>` (defaults `IT_Service_Fulfiller_Agent` / `IT Service Fulfiller Agent`) also thread through the `createBundleWithVersion` body — both the outer `apiName`/`label` AND the substituted `config.developer_name`/`config.agent_label` inside the Agent Script. Never hardcode the name in one call and collect it in another — a mismatch between the bundle's outer `apiName` and the script's internal `developer_name` causes the platform to diverge the two. Creating an agent provisions a live, activated agent on the org; the user must explicitly approve the write.
121
122
 
122
123
  ---
123
124
 
@@ -126,8 +127,8 @@ The **idempotency read keys PRIMARILY on the template's `botDefinitionId`** (Pha
126
127
  Substitute `<alias>` with the collected target org and `<developerName>` / `<label>` with the collected values. Full command shapes + per-phase verdict-branch handling live in `references/workflow-detail.md` — the phase summary below names each step and its load-bearing rule; the reference file holds the exact `sf` / `node` invocations to copy.
127
128
 
128
129
  0. **Phase 0 — Establish `${SCRATCH_DIR}`.** Invoke the deterministic helper (path is skill-root-qualified so it resolves regardless of the shell's CWD): `SCRATCH_DIR="$(node "<skill_dir>/scripts/create-scratch-dir.mjs" "${outputDir:-}")"`. Helper picks the base dir (`${TMPDIR}`, else `/tmp`, else the harness `${outputDir}` last-resort — scratch stays OUT of the scored `${outputDir}` tree) and emits the created dir on stdout. All transient JSON lands under `${SCRATCH_DIR}`; the durable `${outputDir}/report.md` stays under the harness dir.
129
- 1. **Phase 1 — Preflight.** Capture the Studio-access read + `agent-templates` read (with the **required** `agentType=AgentforceEmployeeAgent` query param) into `${SCRATCH_DIR}/agent-templates.json`, then classify via `scripts/classify-preflight.mjs "IT Service Fulfiller"`. The classifier also emits `template.botDefinitionId` from the matched row — **capture it; it is the primary Phase-2 idempotency key (the collected `<developerName>` is the fallback key).** Branch on `verdict`: `READY` ⇒ Phase 2; `NOT-READY` ⇒ prerequisite hand-off via `AskUserQuestion` (delegate to `service-itsm-agentic-setup-agentforce-studio-validate` on "yes"); `ERROR` ⇒ surface + stop; `studio.signal="CANNOT-CONFIRM"` (confirmed 404) does not block.
130
- 2. **Phase 2 — Idempotency (primary key `botDefinitionId`, fallback key `<developerName>`).** Take `template.botDefinitionId` from Phase 1. **Present** ⇒ SOQL `BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'` with the `BotVersions` subquery (subquery is required — otherwise `needsActivation` is permanently false; the `OR` clause makes a **dangling** Id link — deleted target — fall back to the live same-name agent instead of a false `exists:false` → duplicate create). **Empty/null** (the normal Fulfiller case — the template row is never back-filled) ⇒ do NOT skip to create; fall back to `BotDefinition WHERE DeveloperName='<developerName>'` (a self-created agent from a prior run has a null template `botDefinitionId` but still exists). Either way classify via `scripts/classify-agent-existence.mjs ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId-or-empty>" "<developerName>"`. Branch: `exists:false` ⇒ **Phase 2c** (action-availability gate, then create); `exists:true` + `needsActivation:false` ⇒ **ALREADY-CREATED** (skip straight to Phase 7 — no action-availability gate; a live active agent's actions are already wired); `exists:true` + `needsActivation:true` ⇒ Phase 2b. Non-zero exit ⇒ surface CLI error; never assume absent. **Why the fallback:** the Fulfiller is never pre-provisioned, so `botDefinitionId` is always null — a missing developerName check would re-create and hit `DUPLICATE_VALUE`.
130
+ 1. **Phase 1 — Preflight.** Capture the Studio-access read into `${SCRATCH_DIR}/studio-access.json` and the `agent-templates` read (with the **required** `agentType=AgentforceEmployeeAgent` query param) into `${SCRATCH_DIR}/agent-templates.json`, then classify by passing **both file paths, then the label** (that arg order): `node "<skill_dir>/scripts/classify-preflight.mjs" ${SCRATCH_DIR}/studio-access.json ${SCRATCH_DIR}/agent-templates.json "IT Service Fulfiller"`. The classifier emits `template.botDefinitionId`, `template.id`, and `template.masterLabel` from the matched row — **capture all three; `botDefinitionId` is the primary Phase-2 idempotency key, `template.id` (the BotDefinition's `AgentTemplate`) the first fallback, and `<developerName>` the last fallback; `masterLabel` is the report's display label, not a key.** Branch on `verdict`: `READY` ⇒ Phase 2; `NOT-READY` ⇒ prerequisite hand-off via `AskUserQuestion` (delegate to `service-itsm-agentic-setup-agentforce-studio-validate` on "yes"); `ERROR` ⇒ surface + stop; `studio.signal="CANNOT-CONFIRM"` (confirmed 404) does not block.
131
+ 2. **Phase 2 — Idempotency (primary key `botDefinitionId`, fallbacks `AgentTemplate` then `<developerName>`).** Take `template.botDefinitionId` and `template.id` from Phase 1. **Present `botDefinitionId`** ⇒ SOQL `BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'` with the `BotVersions` subquery (subquery is required — otherwise `needsActivation` is permanently false; the `OR` clauses make a **dangling** Id link — deleted target — fall back to the live agent instead of a false `exists:false` → duplicate create). **Empty/null `botDefinitionId`** (the normal Fulfiller case — the template row is never back-filled) ⇒ do NOT skip to create; read `BotDefinition WHERE AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'` (a self-created agent from a prior run carries a null `AgentTemplate`, so `DeveloperName` is what recovers it; `AgentTemplate` would catch a hypothetical instantiated-from-template agent under any DeveloperName). `<agentTemplate>` is Phase-1 `template.id`. Either way classify via `node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId-or-empty>" "<developerName>" "<agentTemplate>"`. Branch: `exists:false` ⇒ **Phase 2c** (action-availability gate, then create); `exists:true` + `needsActivation:false` ⇒ **ALREADY-CREATED** (skip straight to Phase 7 — no action-availability gate; a live active agent's actions are already wired); `exists:true` + `needsActivation:true` ⇒ Phase 2b. Non-zero exit ⇒ surface CLI error; never assume absent. **Why the fallbacks:** the Fulfiller is never pre-provisioned, so `botDefinitionId` and `AgentTemplate` are always null — a missing developerName check would re-create and hit `DUPLICATE_VALUE`.
131
132
  3. **Phase 2b — Reactivation offer.** `AskUserQuestion`: _"Fulfiller agent `<developerName>` exists but latest version is Inactive. Activate it?"_. On **Yes**: `POST /connect/bot-versions/<latestVersionId>/activation` with `{"status":"Active"}` captured to `${SCRATCH_DIR}/activate-response.json`, then `node "<skill_dir>/scripts/classify-activate-result.mjs" ${SCRATCH_DIR}/activate-response.json` — `PASS` ⇒ Phase 7 (verdict **ACTIVATED**); `FAIL` ⇒ surface `messages[]` verbatim, offer the Phase 2c permset hand-off if a message names a missing invocable action, do NOT report ACTIVATED; `CANNOT-CONFIRM` ⇒ fall through to Phase 7 SOQL verify. On **No**: stop, no writes.
132
133
  4. **Phase 2c — Action-availability preflight (create path only; reached only from Phase 2 `exists:false`).** Capture `sf api request rest "/services/data/v67.0/actions/custom/generatePromptResponse" --method GET` to `${SCRATCH_DIR}/generate-prompt-response.json`, then `node "<skill_dir>/scripts/classify-action-availability.mjs" ${SCRATCH_DIR}/agent-templates.json "IT Service Fulfiller" ${SCRATCH_DIR}/generate-prompt-response.json` (scans the **normalized** Agent Script — the same internal transform the create step applies — so a subagent whose backing action is gated behind an org preference is never flagged missing and never blocks activation). Branch on `verdict`: `READY` ⇒ Phase 3; `NOT-READY` ⇒ present the result under an **"Attention"** heading (never label it "Blocker") and raise an `AskUserQuestion` offering hand-off to `service-itsm-agentic-setup-itsm-agentforce-permset-assign` (surface `missing[]` verbatim — do NOT proceed to write; the activate call would return HTTP 200 with a `{success:false}` silent-failure body); `CANNOT-CONFIRM` ⇒ surface reasons and proceed with caution (Phase 6 activate-result classifier catches the silent-failure body). Full contract in `references/action-availability.md`.
133
134
  5. **Phase 3 — Confirm-to-Write (REQUIRED, create path only).** If `${outputDir}` was provided, first render the checkpoint file via `render-report.mjs` with `verdict:"PENDING CONFIRMATION"` (skip for interactive runs). THEN raise the `AskUserQuestion` gate presenting developerName + label + "NGA-native from the Fulfiller template's Agent Script". Proceed **only** on explicit "yes"; on "no", re-render with `verdict:"DECLINED"`.
@@ -158,7 +159,7 @@ Substitute `<alias>` with the collected target org and `<developerName>` / `<lab
158
159
  ## Verification Checklist
159
160
 
160
161
  - [ ] Preflight classified by `classify-preflight.mjs` (PASS or documented CANNOT-CONFIRM); hand-off offered on FAIL; raw error surfaced on ERROR.
161
- - [ ] Idempotency keyed on the template's `botDefinitionId` (Phase-1 row) with the collected developerName as fallback; `BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'` (the `OR` covers both a null and a dangling `botDefinitionId`) + latest `BotVersion.Status` (subquery present) read + classified before any write.
162
+ - [ ] Idempotency keyed on the template's `botDefinitionId` (Phase-1 row) with the BotDefinition's `AgentTemplate` (Phase-1 `template.id`) as a defensive first fallback and the collected developerName as the real guard; `BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'` (the `OR`s cover a null/dangling `botDefinitionId` and — for the never-pre-provisioned Fulfiller with a null `AgentTemplate` — a self-created repeat by DeveloperName) + latest `BotVersion.Status` (subquery present) read + classified before any write.
162
163
  - [ ] If `needsActivation:true`, Phase-2b reactivation offer presented — no silent skip, no duplicate create.
163
164
  - [ ] Explicit user confirmation at Phase 3 (create) or Phase 2b (reactivation) before any write.
164
165
  - [ ] Bundle body built by `build-create-body.mjs`, POSTed via `--body @<file>` with the collected `developerName`/`label`; or write correctly skipped.
@@ -135,13 +135,13 @@ Classifier output:
135
135
  ```json
136
136
  {
137
137
  "studio": { "hasAccess": true, "signal": "PASS|FAIL|CANNOT-CONFIRM|ERROR", "reason": "..." },
138
- "template": { "present": true, "id": "svc_itsm_intelligence__ITSrvcMgmtFulfiller", "hasAgentScript": true, "botDefinitionId": "0Xx...", "signal": "PASS|FAIL|CANNOT-CONFIRM", "reason": "..." },
138
+ "template": { "present": true, "id": "svc_itsm_intelligence__ITSrvcMgmtFulfiller", "hasAgentScript": true, "botDefinitionId": "0Xx...", "masterLabel": "IT Service Fulfiller", "signal": "PASS|FAIL|CANNOT-CONFIRM", "reason": "..." },
139
139
  "verdict": "READY | NOT-READY | CANNOT-CONFIRM | ERROR",
140
140
  "reasons": ["..."]
141
141
  }
142
142
  ```
143
143
 
144
- `template.botDefinitionId` is copied from the matched `agent-templates` row. **`botDefinitionId` is the PRIMARY Phase-2 idempotency key**, but the Fulfiller is never pre-provisioned and this skill's create path never stamps `templateName`, so its template `botDefinitionId` is `null` on every run — the collected `<developerName>` is therefore the guard that actually protects repeat runs. Carry `<developerName>` into the Phase-2 read as the fallback key. (`isInstalled`/`isActivated` are no longer emitted: they ride the same AgentTemplate join as `botDefinitionId`, so they read false for self-created agents and nothing consumes them.)
144
+ `template.botDefinitionId`, `template.id`, and `template.masterLabel` are copied from the matched `agent-templates` row. **`botDefinitionId` is the PRIMARY Phase-2 idempotency key**, but the Fulfiller is never pre-provisioned and this skill's create path never stamps `templateName`, so BOTH its template `botDefinitionId` AND the agent's `AgentTemplate` are `null` on every run — the collected `<developerName>` (the last fallback) is therefore the guard that actually protects repeat runs; `template.id` (the BotDefinition's `AgentTemplate`) is carried as a defensive first fallback. Carry `template.id` and `<developerName>` into the Phase-2 read as the fallback keys; `masterLabel` is the report's display label, not an idempotency key. (`isInstalled`/`isActivated` are no longer emitted: they ride the same AgentTemplate join as `botDefinitionId`, so they read false for self-created agents and nothing consumes them.)
145
145
 
146
146
  `verdict=ERROR` (`studio.signal="ERROR"` — a parseable non-404 Studio-access
147
147
  error, e.g. `401`/`403`) ⇒ surface the raw error and stop; takes priority over
@@ -149,55 +149,66 @@ template state so a present template cannot outrun a failed prerequisite read.
149
149
  `verdict=NOT-READY` (studio FAIL or template FAIL) ⇒ hand off / stop.
150
150
  `verdict=READY` ⇒ proceed to Phase 2. It exits `0` on usable args.
151
151
 
152
- ## Enumerate the existing agent — SOQL on `BotDefinition` BY Id (falling back to DeveloperName)
152
+ ## Enumerate the existing agent — SOQL on `BotDefinition` BY Id (falling back to AgentTemplate, then DeveloperName)
153
153
 
154
154
  Idempotency is keyed **PRIMARILY** on the template's `botDefinitionId` (from the
155
- Phase-1 row) and **FALLS BACK** to the collected `<developerName>`. If the
156
- Fulfiller agent already exists under a `DeveloperName` that differs from this
157
- skill's default guess `IT_Service_Fulfiller_Agent`, a name-only read
158
- false-negatives (`exists:false`) and the create then collides on `apiName` with
159
- `DUPLICATE_VALUE`. `botDefinitionId` is the platform's authoritative link from
160
- the template to the `BotDefinition` it was instantiated into — but it is
161
- back-filled onto the template row **only** for pre-provisioned agents, and the
162
- Fulfiller is never pre-provisioned: this skill's create path never stamps
163
- `templateName`, so its template `botDefinitionId` stays `null` on the first run
164
- and every run after. The `DeveloperName`-keyed fallback is therefore the guard
165
- that actually protects repeat runs here — never short-circuit straight to create
166
- when `botDefinitionId` is absent.
155
+ Phase-1 row), **FALLS BACK** first to the BotDefinition's `AgentTemplate` (the
156
+ OOTB namespaced source template = Phase-1 `template.id`), and then to the
157
+ collected `<developerName>`. If the Fulfiller agent already exists under a
158
+ `DeveloperName` that differs from this skill's default guess
159
+ `IT_Service_Fulfiller_Agent`, a name-only read false-negatives (`exists:false`)
160
+ and the create then collides on `apiName` with `DUPLICATE_VALUE` — the
161
+ `DeveloperName`-keyed fallback recovers that live agent. `botDefinitionId` is the
162
+ platform's authoritative link from the template to the `BotDefinition` it was
163
+ instantiated into — but it is back-filled onto the template row **only** for
164
+ pre-provisioned agents, and the Fulfiller is never pre-provisioned: this skill's
165
+ create path never stamps `templateName`, so BOTH its template `botDefinitionId`
166
+ AND the agent's `AgentTemplate` stay `null` on the first run and every run after.
167
+ The `DeveloperName`-keyed fallback is therefore the guard that actually protects
168
+ repeat runs here; the `AgentTemplate` key — which would catch a live agent
169
+ instantiated from the source template regardless of its `DeveloperName`, far more
170
+ reliably than a display name — is carried as a defensive/symmetric key that keeps
171
+ this classifier identical to the Employee one (where `AgentTemplate` IS the
172
+ load-bearing catcher). Never short-circuit straight to create when
173
+ `botDefinitionId` is absent.
167
174
 
168
175
  - **`botDefinitionId` empty/null** (the normal Fulfiller case — template row
169
- never joined) ⇒ fall back to a DeveloperName-keyed read and let the classifier
170
- match on it:
176
+ never joined) ⇒ fall back to an `AgentTemplate` **`OR` `DeveloperName`** read and
177
+ let the classifier match on either:
171
178
  ```bash
172
- sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE DeveloperName='<developerName>'" \
179
+ sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'" \
173
180
  --target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
174
- node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>"
181
+ node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>" "<agentTemplate>"
175
182
  ```
176
183
  - **`botDefinitionId` present** (a pre-provisioned instantiation, if any — never
177
184
  the normal Fulfiller case) ⇒ read the `BotDefinition` by Id **`OR` by the
178
- collected `<developerName>`** in one query, then classify. The `OR DeveloperName=`
179
- clause catches a **dangling** link — a `botDefinitionId` whose target row was
180
- since deleted: the by-Id half returns nothing, the live same-name agent still
181
- surfaces, and the classifier falls back to it (`matchedBy:"developerName"`)
182
- rather than concluding `exists:false` and colliding with `DUPLICATE_VALUE`:
185
+ template's `AgentTemplate` `OR` by the collected `<developerName>`** in one
186
+ query, then classify. The `OR AgentTemplate=` clause would catch a live agent by
187
+ its platform-stamped source template (`matchedBy:"agentTemplate"`) regardless of
188
+ its DeveloperName. The `OR DeveloperName=` clause catches a **dangling**
189
+ link — a `botDefinitionId` whose target row was since deleted: the by-Id half
190
+ returns nothing, the live same-name agent still surfaces, and the classifier
191
+ falls back to it (`matchedBy:"developerName"`) rather than concluding
192
+ `exists:false` and colliding with `DUPLICATE_VALUE`:
183
193
  ```bash
184
- sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'" \
194
+ sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'" \
185
195
  --target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
186
- node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>"
196
+ node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>" "<agentTemplate>"
187
197
  ```
188
198
 
189
- Only when **both** `botDefinitionId` and `<developerName>` are absent does the
190
- classifier return `exists:false` without reading a query file — in practice the
191
- collected `<developerName>` is always present, so the fallback read always runs.
199
+ Only when **all** of `botDefinitionId`, `<agentTemplate>`, and `<developerName>`
200
+ are absent does the classifier return `exists:false` without reading a query
201
+ file — in practice the collected `<developerName>` and `template.id` (AgentTemplate)
202
+ are always present, so the fallback read always runs.
192
203
 
193
204
  The `BotVersions` subquery (child relationship on `BotDefinition`) is what lets
194
205
  the classifier see the latest version's `Status` — omit it and
195
206
  `latestVersionStatus`/`needsActivation` come back `null`/`false` even when the
196
207
  existing agent is actually inactive. The classifier prints
197
208
  `{ exists, count, matchedBy, agentId, botDefinitionId, developerName, latestVersionId, latestVersionStatus, needsActivation }`
198
- (`matchedBy` is `"botDefinitionId"` | `"developerName"` | `null`; `developerName`
199
- is the ACTUAL live agent's DeveloperName read from the record — surface it in the
200
- report instead of the collected guess):
209
+ (`matchedBy` is `"botDefinitionId"` | `"agentTemplate"` | `"developerName"` | `null`;
210
+ `developerName` is the ACTUAL live agent's DeveloperName read from the record —
211
+ surface it in the report instead of the collected guess):
201
212
  - `exists:false` ⇒ proceed to create.
202
213
  - `exists:true` and `needsActivation:false` (latest version `Active`) ⇒
203
214
  **ALREADY-CREATED** (skip the entire create/publish/activate sequence).
@@ -9,12 +9,14 @@ just reactivate what's already there".
9
9
  ## When this path fires
10
10
 
11
11
  `scripts/classify-agent-existence.mjs` (called with the template's
12
- `botDefinitionId` as the PRIMARY idempotency key and the collected
13
- `DeveloperName` as the FALLBACK — see `cli-invocation.md` → "Enumerate the
14
- existing agent") returns
12
+ `botDefinitionId` as the PRIMARY idempotency key, the BotDefinition's
13
+ `AgentTemplate` — the template's OOTB source id — as the first FALLBACK, and the
14
+ collected `DeveloperName` as the last FALLBACK —
15
+ see `cli-invocation.md` → "Enumerate the existing agent") returns
15
16
  `{ exists:true, needsActivation:true, latestVersionId, latestVersionStatus:"Inactive" }`.
16
17
  That means a `BotDefinition` matching the collected `DeveloperName` (the
17
- Fulfiller's template `botDefinitionId` is null, so the fallback is what matches)
18
+ Fulfiller's template `botDefinitionId` AND `AgentTemplate` are both null, so the
19
+ `DeveloperName` fallback is what matches)
18
20
  already exists from a prior run of this skill but its most-recent `BotVersion` is
19
21
  inactive; creating a new bundle would produce a duplicate. Instead, flip the
20
22
  existing version to `Active`.
@@ -26,10 +26,10 @@ sf api request rest "/services/data/v67.0/connect/service-itsm/agent-templates?a
26
26
  node "<skill_dir>/scripts/classify-preflight.mjs" ${SCRATCH_DIR}/studio-access.json ${SCRATCH_DIR}/agent-templates.json "IT Service Fulfiller"
27
27
  ```
28
28
 
29
- The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgentScript,botDefinitionId,signal}, verdict, reasons }`:
29
+ The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgentScript,botDefinitionId,masterLabel,signal}, verdict, reasons }`:
30
30
 
31
31
  - `template.hasAgentScript:true` confirms the template carries the Agent Script content Phase 4 needs; capture the raw `agent-templates.json` file path — Phase 4 re-reads it directly (via `scripts/build-create-body.mjs`) rather than the classifier re-emitting the full script content.
32
- - **`template.botDefinitionId` is the PRIMARY Phase-2 idempotency key**, but the Fulfiller is never pre-provisioned and this skill's create path never stamps `templateName`, so its template `botDefinitionId` is `null` on every run — the collected `<developerName>` is the guard that actually fires. Capture `botDefinitionId` for Phase 2 and carry the collected `<developerName>` as the fallback key.
32
+ - **`template.botDefinitionId` is the PRIMARY Phase-2 idempotency key**, but the Fulfiller is never pre-provisioned and this skill's create path never stamps `templateName`, so its template `botDefinitionId` is `null` on every run — the collected `<developerName>` and the template's `masterLabel` are the guards that actually fire. Capture `botDefinitionId` for Phase 2 and carry the collected `<developerName>` (first fallback) and `template.masterLabel` (second fallback) as the fallback keys.
33
33
  - `verdict:"READY"` ⇒ continue to Phase 2.
34
34
  - `verdict:"ERROR"` because `studio.signal="ERROR"` (the Studio-access read returned a parseable but non-404 error body — e.g. `401`/`403`) ⇒ do NOT proceed — surface the raw error and stop. This is a failed prerequisite read, not an unwired gate; do not let a present template push the verdict to READY.
35
35
  - `verdict:"NOT-READY"` because `studio.signal="FAIL"` (`hasAccess=false`) ⇒ do NOT proceed — **offer the prerequisite hand-off**:
@@ -42,22 +42,22 @@ The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgen
42
42
 
43
43
  ## Phase 2 — Idempotency: does the target agent already exist, and is it active?
44
44
 
45
- Idempotency is keyed **PRIMARILY** on the template's `botDefinitionId` (captured in Phase 1) and **FALLS BACK** to the collected `<developerName>`. The `botDefinitionId` is the platform's authoritative link from the `agent-templates` row to the `BotDefinition` it was instantiated into, but it is back-filled **only** for pre-provisioned agents — and the Fulfiller is never pre-provisioned: this skill's create path never stamps `templateName`, so its template `botDefinitionId` is `null` on the first run and every run after. The `DeveloperName`-keyed fallback is therefore the guard that actually catches repeat runs (if the agent already exists under a `DeveloperName` matching the collected `<developerName>`, a name-only miss would let the create fail with `DUPLICATE_VALUE`), so never short-circuit to create when `botDefinitionId` is absent.
45
+ Idempotency is keyed **PRIMARILY** on the template's `botDefinitionId` (captured in Phase 1), **FALLS BACK** first to the BotDefinition's `AgentTemplate` (the OOTB namespaced source template = Phase-1 `template.id`), and then to the collected `<developerName>`. The `botDefinitionId` is the platform's authoritative link from the `agent-templates` row to the `BotDefinition` it was instantiated into, but it is back-filled **only** for pre-provisioned agents — and the Fulfiller is never pre-provisioned: this skill's create path never stamps `templateName`, so BOTH its template `botDefinitionId` AND the agent's `AgentTemplate` are `null` on the first run and every run after. The `DeveloperName`-keyed fallback is therefore the guard that actually catches repeat runs (if the agent already exists under a `DeveloperName` matching the collected `<developerName>`, a name-only miss would let the create fail with `DUPLICATE_VALUE`); the `AgentTemplate` key — which would recover a live agent instantiated from the source template regardless of its `DeveloperName`, far more reliably than a display name — is carried as a defensive/symmetric key that keeps this classifier identical to the Employee one (where `AgentTemplate` IS the load-bearing catcher). Never short-circuit to create when `botDefinitionId` is absent.
46
46
 
47
- - **`template.botDefinitionId` is empty/null** (the normal Fulfiller case) ⇒ fall back to a DeveloperName-keyed read (do **not** short-circuit to create — a self-created agent from a prior run has a null template `botDefinitionId` but still exists). The `BotVersions` subquery is required — omitting it leaves `needsActivation` permanently false and hides the reactivation path:
47
+ - **`template.botDefinitionId` is empty/null** (the normal Fulfiller case) ⇒ fall back to an `AgentTemplate` **`OR` `DeveloperName`** read (do **not** short-circuit to create — a self-created agent from a prior run carries a null `AgentTemplate`, so `DeveloperName` is what recovers it). The `BotVersions` subquery is required — omitting it leaves `needsActivation` permanently false and hides the reactivation path:
48
48
  ```bash
49
- sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE DeveloperName='<developerName>'" \
49
+ sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'" \
50
50
  --target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
51
- node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>"
51
+ node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>" "<agentTemplate>"
52
52
  ```
53
- - **`template.botDefinitionId` is present** (a pre-provisioned instantiation, if any — never the normal Fulfiller case) ⇒ read the `BotDefinition` by Id **`OR` by the collected `<developerName>`** in one query, INCLUDING its latest version's status, then classify. The `OR DeveloperName=` clause is what catches a **dangling** template→BotDefinition link — a `botDefinitionId` whose target row was since deleted: the by-Id half returns nothing, but the live same-name agent still surfaces, so the classifier falls back to it (`matchedBy:"developerName"`) instead of concluding `exists:false` and letting the create collide with `DUPLICATE_VALUE`:
53
+ - **`template.botDefinitionId` is present** (a pre-provisioned instantiation, if any — never the normal Fulfiller case) ⇒ read the `BotDefinition` by Id **`OR` by the template's `AgentTemplate` `OR` by the collected `<developerName>`** in one query, INCLUDING its latest version's status, then classify. The `OR AgentTemplate=` clause would catch a live agent by its platform-stamped source template (`matchedBy:"agentTemplate"`) regardless of its DeveloperName. The `OR DeveloperName=` clause catches a **dangling** template→BotDefinition link — a `botDefinitionId` whose target row was since deleted: the by-Id half returns nothing, but the live same-name agent still surfaces, so the classifier falls back to it (`matchedBy:"developerName"`) instead of concluding `exists:false` and letting the create collide with `DUPLICATE_VALUE`:
54
54
  ```bash
55
- sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'" \
55
+ sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'" \
56
56
  --target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
57
- node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>"
57
+ node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>" "<agentTemplate>"
58
58
  ```
59
59
 
60
- The classifier prints `{ exists, count, matchedBy, agentId, botDefinitionId, developerName, latestVersionId, latestVersionStatus, needsActivation }` — `matchedBy` is `"botDefinitionId"` | `"developerName"` | `null`, and `developerName` is the ACTUAL live agent's DeveloperName read from the record, surface it in the report rather than the collected guess:
60
+ The classifier prints `{ exists, count, matchedBy, agentId, botDefinitionId, developerName, latestVersionId, latestVersionStatus, needsActivation }` — `matchedBy` is `"botDefinitionId"` | `"agentTemplate"` | `"developerName"` | `null`, and `developerName` is the ACTUAL live agent's DeveloperName read from the record, surface it in the report rather than the collected guess:
61
61
 
62
62
  - `exists:false` ⇒ proceed to Phase 3 (create path).
63
63
  - `exists:true` and `needsActivation:false` (latest version `Active`) ⇒ **ALREADY-CREATED** — skip the write, fall through to Phase 7 verification.