@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.
- package/package.json +1 -1
- package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +72 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/references/inputs-and-namespace.md +38 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/references/procedure.md +158 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/detect-namespace.js +86 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/render-apex.js +64 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/resolve-id-by-name.js +47 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/sf-rest.js +171 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/soql-escape.js +26 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex +51 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex +21 -0
- package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +74 -0
- package/skills/consumer-goods-tpe-dashboard-configure/references/phases-1-6.md +112 -0
- package/skills/consumer-goods-tpe-dashboard-configure/references/phases-7-12.md +157 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/find-failure-reason.js +132 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/poll-status.js +116 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/render-apex.js +64 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/run-data-transform.js +121 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/schedule-business-period-export.apex +27 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/sf-rest.js +171 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/soql-escape.js +25 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +141 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/payload-shapes.md +447 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/procedure.md +263 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/clone-tpe-dashboards.js +537 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/sf-rest.js +195 -0
- package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +157 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/detect-namespace.js +86 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/download-static-resource.js +151 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/extract-crm-field-permissions.js +115 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/sf-rest.js +109 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/update-field-permissions.js +433 -0
- package/skills/service-catalog-template-coordinate/SKILL.md +263 -0
- package/skills/service-catalog-template-coordinate/examples/output-templates.md +44 -0
- package/skills/service-catalog-template-coordinate/references/mcp-invocation.md +183 -0
- package/skills/service-catalog-template-coordinate/references/operations.md +230 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +243 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/cli-invocation.md +205 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/helper-contracts.md +236 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/permset-topology.md +132 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-activated-agents.mjs +106 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-agent-access-state.mjs +113 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-assignment-state.mjs +99 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-platform-permset-availability.mjs +155 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/gate-unified-catalog-tiers.mjs +100 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/rank-candidate-users.mjs +95 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/resolve-target-user.mjs +86 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +44 -23
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +33 -9
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -18
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +3 -1
- package/skills/service-itsm-agentic-setup-configure/SKILL.md +20 -12
- package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +73 -5
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -7
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +45 -33
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +8 -6
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +10 -10
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +114 -56
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +33 -17
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +9 -3
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +43 -32
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +6 -4
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +10 -10
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +106 -55
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +27 -13
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +9 -3
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +159 -161
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +51 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +1 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +120 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +28 -5
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +19 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +350 -30
- package/skills/service-itsm-channels-coordinate/SKILL.md +80 -213
- package/skills/service-itsm-slack-itservice-configure/SKILL.md +363 -0
- package/skills/service-itsm-slack-itservice-configure/references/connect-agentforce-to-slack.md +159 -0
- package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +88 -0
- package/skills/service-itsm-slack-itservice-configure/references/manage-user-access.md +117 -0
- package/skills/service-itsm-slack-itservice-configure/references/record-visibility.md +78 -0
- package/skills/service-itsm-slack-itservice-configure/references/site-membership-verification.md +126 -0
- package/skills/service-itsm-slack-itservice-configure/scripts/classify-user-access.mjs +167 -0
- package/skills/service-itsm-teams-configure/SKILL.md +50 -47
- package/skills/service-itsm-teams-configure/references/azure-credential-population.md +42 -28
- package/skills/service-itsm-teams-configure/references/gotchas.md +1 -2
- package/skills/service-itsm-teams-coordinate/SKILL.md +22 -18
- package/skills/service-itsm-teams-coordinate/examples/output-templates.md +12 -9
- package/skills/service-itsm-teams-itdesk-configure/SKILL.md +60 -44
- package/skills/service-itsm-teams-itservice-configure/SKILL.md +56 -70
- package/skills/service-catalog-template-deploy/SKILL.md +0 -310
- package/skills/service-catalog-template-deploy/references/cli-invocation.md +0 -258
- package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +0 -164
- package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +0 -94
- package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +0 -331
- package/skills/service-catalog-template-search/SKILL.md +0 -212
- package/skills/service-catalog-template-search/references/cli-invocation.md +0 -128
- package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +0 -205
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
name: service-itsm-agentic-setup-employee-agent-configure
|
|
3
3
|
description: "Create and activate an IT Service Employee agent as a Next-Gen Authoring (NGA) native agent from an ITSM Employee agent template's Agent Script, via the Salesforce CLI (sf): read the template, check idempotency, create the NGA bundle then publish and activate, verify live. Defaults to the broad IT Service Employee template; when the user names a specialized Employee template (Password Manager Assistance, Certificate Management, Onboarding, Hardware Request, and ~47 others catalogued in references/specialized-templates.md — all under the `svc_emp_intelligence__` namespace), pins that one instead. Idempotent per developer name. TRIGGER when the user asks to create/set up/provision/activate the Employee agent, the IT Service Employee agent, or a specialized Employee agent (password manager, certificate, onboarding, hardware request, etc.). DO NOT TRIGGER: prerequisite checks (service-itsm-agentic-setup-agentforce-studio-validate), CMDB CRUD, Fulfiller setup (service-itsm-agentic-setup-fulfiller-agent-configure)."
|
|
4
4
|
metadata:
|
|
5
|
-
version: "2.
|
|
5
|
+
version: "2.5"
|
|
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-fulfiller-agent-configure"
|
|
@@ -60,7 +61,7 @@ If any of these are unmet, `sf` surfaces an auth error or a `401`/`403`/`404`; *
|
|
|
60
61
|
|---------|---------|-------|
|
|
61
62
|
| 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 |
|
|
62
63
|
| 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 resolved `<masterLabel>` template + non-empty `agentScript` |
|
|
63
|
-
| Enumerate 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=`
|
|
64
|
+
| Enumerate 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 the first fallback that catches the pre-provisioned broad `IT_Service_Employee` agent by its OOTB namespaced source template (`svc_emp_intelligence__ItEmployeeAssistance` = Phase-1 `template.id`) regardless of the collected DeveloperName guess; `OR DeveloperName=` is the last fallback for self-created agents (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 |
|
|
64
65
|
| **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 |
|
|
65
66
|
| **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` |
|
|
66
67
|
| **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 |
|
|
@@ -87,9 +88,9 @@ Collect from the user (ask only what is not already in conversation context):
|
|
|
87
88
|
| Label | Broad: `IT Service Employee Agent`. Specialized: the picked template's `masterLabel` verbatim (e.g. `Password Manager Assistance`) |
|
|
88
89
|
| Confirm the write | **REQUIRED** — present resolved template + developerName + label, then require "yes" via `AskUserQuestion` |
|
|
89
90
|
|
|
90
|
-
The collected `<masterLabel>`, `<developerName>`, `<label>` are threaded through every call — `<masterLabel>` selects the row in `agent-templates.data[]` (which also carries the `botDefinitionId` idempotency key); `<developerName>`/`<label>` are used in the `createBundleWithVersion` body (both outer `apiName`/`label` AND the substituted internal `config.developer_name`/`config.agent_label`). The **idempotency + verify reads key PRIMARILY on the template's `botDefinitionId`** (or, after a fresh create, the publish response's `publishedBotId`) and **fall back to the collected `<developerName
|
|
91
|
+
The collected `<masterLabel>`, `<developerName>`, `<label>` are threaded through every call — `<masterLabel>` selects the row in `agent-templates.data[]` (which also carries the `botDefinitionId` idempotency key); `<developerName>`/`<label>` are used in the `createBundleWithVersion` body (both outer `apiName`/`label` AND the substituted internal `config.developer_name`/`config.agent_label`). The **idempotency + verify reads key PRIMARILY on the template's `botDefinitionId`** (or, after a fresh create, the publish response's `publishedBotId`) and **fall back to the BotDefinition's `AgentTemplate`** (the OOTB source template = Phase-1 `template.id`), then to the collected `<developerName>`, when that is null. A hardcode/collect mismatch on the create body diverges the bundle's outer identity from the script's internal identity.
|
|
91
92
|
|
|
92
|
-
**Idempotency**: keyed PRIMARILY on the **template's `botDefinitionId`** (Phase-1 `agent-templates` row — the platform's authoritative template→`BotDefinition` link)
|
|
93
|
+
**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 catches the broad pre-provisioned agent by its platform-stamped source template regardless of what `DeveloperName` it carries; the `OR DeveloperName=` half is both the last fallback for self-created agents — whose `AgentTemplate` is null — AND the guard for a **dangling** Id link whose target `BotDefinition` was deleted), + latest `BotVersion.Status`. Outcomes: no match on any key ⇒ create; `Active` ⇒ ALREADY-CREATED (skip write); `Inactive` ⇒ Phase-2b reactivation offer. **Why the fallback keys:** the broad agent ships pre-provisioned as `IT_Service_Employee` ≠ the guess `IT_Service_Employee_Agent` (a null-`botDefinitionId` template row too), so neither the primary key nor the `developerName` guess catches it — its `AgentTemplate` (`svc_emp_intelligence__ItEmployeeAssistance`) is the reliable, rename-immune key that matches it; and for an agent this skill creates (which back-fills neither `botDefinitionId` nor `AgentTemplate`), the `developerName` fallback is what catches a repeat run. The server does reject a duplicate `DeveloperName` at publish (unique-constraint → bundle cleanup), but only this read turns a repeat into a graceful skip instead of a `DUPLICATE_VALUE`.
|
|
93
94
|
|
|
94
95
|
---
|
|
95
96
|
|
|
@@ -98,8 +99,8 @@ The collected `<masterLabel>`, `<developerName>`, `<label>` are threaded through
|
|
|
98
99
|
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.
|
|
99
100
|
|
|
100
101
|
0. **Phase 0 — Establish `${SCRATCH_DIR}`.** Before any phase writes a transient JSON file, 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:-}")"`. The 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's absolute path on stdout. Every subsequent phase writes its transient JSON under `${SCRATCH_DIR}`; the durable `${outputDir}/report.md` stays under the harness dir.
|
|
101
|
-
1. **Phase 1 — Preflight.** Capture the Studio-access read
|
|
102
|
-
2. **Phase 2 — Idempotency (primary key `botDefinitionId`,
|
|
102
|
+
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 "<masterLabel>"` — pass the resolved `<masterLabel>` (`"IT Service Employee"` for the broad path or the picked specialization's `masterLabel`). 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` employee path on "yes"); `ERROR` ⇒ surface + stop; `studio.signal="CANNOT-CONFIRM"` (confirmed 404) does not block.
|
|
103
|
+
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>'` (+ `BotVersions` subquery — required, else `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`** ⇒ do NOT skip to create; read `WHERE AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'` (the broad pre-provisioned agent is recovered by its OOTB source template even when its DeveloperName differs from the guess; a self-created agent carries a null `AgentTemplate`, so `DeveloperName` is its guard). `<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 3 (create); `exists:true` + `needsActivation:false` ⇒ **ALREADY-CREATED** (skip to Phase 7); `exists:true` + `needsActivation:true` ⇒ Phase 2b. Non-zero exit ⇒ surface CLI error; never assume absent. **Why the fallback keys:** the broad agent ships pre-provisioned as `IT_Service_Employee` with a null template `botDefinitionId`, so neither the primary key nor the `developerName` guess (`IT_Service_Employee_Agent`) catches it — its `AgentTemplate` (`svc_emp_intelligence__ItEmployeeAssistance`) matches it by the platform-stamped source template; the `developerName` fallback catches self-created repeats — a miss on all three re-creates and hits `DUPLICATE_VALUE`.
|
|
103
104
|
3. **Phase 2b — Reactivation offer.** `AskUserQuestion`: _"Employee agent `<developerName>` exists but latest version is Inactive. Activate it?"_. On **Yes**: `POST /connect/bot-versions/<latestVersionId>/activation` with `{"status":"Active"}` — skips Phases 3–6, straight to Phase 7. Aggregate verdict is **ACTIVATED**, not CREATED. On **No**: stop, no writes.
|
|
104
105
|
4. **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 Employee template's Agent Script". Proceed **only** on explicit "yes"; on "no" (including "hold off on activation" / "not yet" / any decline of the atomic chain), re-render with `verdict:"DECLINED"` and a one-line `reason`.
|
|
105
106
|
5. **Phase 4 — Create.** `scripts/build-create-body.mjs ${SCRATCH_DIR}/agent-templates.json "<masterLabel>" "<developerName>" "<label>" ${SCRATCH_DIR}/create-bundle-body.json` (helper re-reads Phase-1 templates JSON, HTML-decodes the matched `agentScript`, substitutes internal `config.developer_name`/`config.agent_label`, writes body to file — pass the same `<masterLabel>` used in Phase 1), then `POST /nextgen-authoring/bundles --body @${SCRATCH_DIR}/create-bundle-body.json`. **Capture response `id`** — that is the `bundleVersionId` for Phases 5–6, not `bundleId`. `403 FUNCTIONALITY_NOT_ENABLED`/`404` ⇒ trigger the Phase-1 hand-off; build-script exit 3 ⇒ surface stderr.
|
|
@@ -130,7 +131,7 @@ Substitute `<alias>` with the collected target org and `<developerName>` / `<lab
|
|
|
130
131
|
|
|
131
132
|
- [ ] Resolved `<masterLabel>` before Phase 1 (broad default or specialization from `references/specialized-templates.md`, `data[]` filtered to `svc_emp_intelligence__`, disambiguated on `id`).
|
|
132
133
|
- [ ] Preflight classified by `classify-preflight.mjs` (PASS or documented CANNOT-CONFIRM); hand-off offered on FAIL; raw error surfaced on ERROR.
|
|
133
|
-
- [ ] Idempotency keyed on the template's `botDefinitionId` (Phase-1 row) with the collected developerName as
|
|
134
|
+
- [ ] Idempotency keyed on the template's `botDefinitionId` (Phase-1 row) with the BotDefinition's `AgentTemplate` (Phase-1 `template.id`) as the first fallback and the collected developerName as the last; `BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'` (the `OR`s cover a null/dangling `botDefinitionId`, the pre-provisioned broad agent whose DeveloperName differs from the guess, and self-created agents with a null `AgentTemplate`) + latest `BotVersion.Status` (subquery present) read + classified before any write.
|
|
134
135
|
- [ ] If `needsActivation:true`, Phase-2b reactivation offer presented — no silent skip, no duplicate create.
|
|
135
136
|
- [ ] Explicit user confirmation at Phase 3 (create) or Phase 2b (reactivation) before any write.
|
|
136
137
|
- [ ] Bundle body built by `build-create-body.mjs`, POSTed via `--body @<file>` with the collected `developerName`/`label`; or write correctly skipped.
|
package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md
CHANGED
|
@@ -151,13 +151,13 @@ Classifier output:
|
|
|
151
151
|
```json
|
|
152
152
|
{
|
|
153
153
|
"studio": { "hasAccess": true, "signal": "PASS|FAIL|CANNOT-CONFIRM|ERROR", "reason": "..." },
|
|
154
|
-
"template": { "present": true, "id": "svc_emp_intelligence__ItEmployeeAssistance", "hasAgentScript": true, "botDefinitionId": "0Xx...", "signal": "PASS|FAIL|CANNOT-CONFIRM", "reason": "..." },
|
|
154
|
+
"template": { "present": true, "id": "svc_emp_intelligence__ItEmployeeAssistance", "hasAgentScript": true, "botDefinitionId": "0Xx...", "masterLabel": "IT Service Employee", "signal": "PASS|FAIL|CANNOT-CONFIRM", "reason": "..." },
|
|
155
155
|
"verdict": "READY | NOT-READY | CANNOT-CONFIRM | ERROR",
|
|
156
156
|
"reasons": ["..."]
|
|
157
157
|
}
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
`template.botDefinitionId`
|
|
160
|
+
`template.botDefinitionId`, `template.id`, and `template.masterLabel` are copied from the matched `agent-templates` row. **`botDefinitionId` is the PRIMARY Phase-2 idempotency key** — a populated value means the template has already been instantiated into a live `BotDefinition`; `null` means it has not been created yet OR it was created by this skill (a self-created agent never back-fills the template row) OR — as with the broad pre-provisioned Employee agent — the platform instantiated it without joining the template row back (see Phase 2). Capture `botDefinitionId` for the Phase-2 read below and always carry `template.id` (the BotDefinition's `AgentTemplate` — the first fallback) and the collected `<developerName>` (the last fallback) 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.)
|
|
161
161
|
|
|
162
162
|
`verdict=ERROR` (`studio.signal="ERROR"` — a parseable non-404 Studio-access
|
|
163
163
|
error, e.g. `401`/`403`) ⇒ surface the raw error and stop; takes priority over
|
|
@@ -165,53 +165,65 @@ template state so a present template cannot outrun a failed prerequisite read.
|
|
|
165
165
|
`verdict=NOT-READY` (studio FAIL or template FAIL) ⇒ hand off / stop.
|
|
166
166
|
`verdict=READY` ⇒ proceed to Phase 2. It exits `0` on usable args.
|
|
167
167
|
|
|
168
|
-
## Enumerate the existing agent — SOQL on `BotDefinition` BY Id (falling back to DeveloperName)
|
|
168
|
+
## Enumerate the existing agent — SOQL on `BotDefinition` BY Id (falling back to AgentTemplate, then DeveloperName)
|
|
169
169
|
|
|
170
170
|
Idempotency is keyed **PRIMARILY** on the template's `botDefinitionId` (from the
|
|
171
|
-
Phase-1 row)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
171
|
+
Phase-1 row), **FALLS BACK** first to the BotDefinition's `AgentTemplate` (the
|
|
172
|
+
OOTB namespaced source template = Phase-1 `template.id`), and then to the
|
|
173
|
+
collected `<developerName>`. The broad "IT Service Employee" agent ships
|
|
174
|
+
pre-provisioned+active under DeveloperName `IT_Service_Employee` **with a `null`
|
|
175
|
+
template `botDefinitionId`** (the platform instantiated it without joining the
|
|
176
|
+
template row back), and `IT_Service_Employee` never matches this skill's default
|
|
177
|
+
guess `IT_Service_Employee_Agent` — so both the primary key AND the
|
|
178
|
+
`DeveloperName` fallback miss on THAT agent, a create is attempted, and it
|
|
179
|
+
collides on `apiName` with `DUPLICATE_VALUE`. The `AgentTemplate`-keyed fallback
|
|
180
|
+
closes that gap far more reliably than a display name would: the live
|
|
181
|
+
pre-provisioned agent carries `AgentTemplate=svc_emp_intelligence__ItEmployeeAssistance`
|
|
182
|
+
(the exact template id this skill installs), the platform-stamped source template,
|
|
183
|
+
immune to any `DeveloperName` rename. `botDefinitionId` remains the
|
|
184
|
+
primary key for a genuinely joined instantiation, and the `DeveloperName`
|
|
185
|
+
fallback is the guard for a self-created repeat (an agent THIS skill creates never
|
|
186
|
+
stamps `templateName`, so BOTH its template `botDefinitionId` AND its
|
|
187
|
+
`AgentTemplate` stay `null`). Never short-circuit straight to create when
|
|
188
|
+
`botDefinitionId` is absent.
|
|
189
|
+
|
|
190
|
+
- **`botDefinitionId` empty/null** (the broad pre-provisioned agent, and every
|
|
191
|
+
agent this skill creates) ⇒ fall back to an `AgentTemplate` **`OR` `DeveloperName`**
|
|
192
|
+
read and let the classifier match on either:
|
|
187
193
|
```bash
|
|
188
|
-
sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE 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 AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'" \
|
|
189
195
|
--target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
|
|
190
|
-
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>"
|
|
196
|
+
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>" "<agentTemplate>"
|
|
191
197
|
```
|
|
192
198
|
- **`botDefinitionId` present** ⇒ read the `BotDefinition` by Id **`OR` by the
|
|
193
|
-
collected `<developerName>`** in one
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
template's `AgentTemplate` `OR` by the collected `<developerName>`** in one
|
|
200
|
+
query, then classify. The `OR AgentTemplate=` clause is what catches the
|
|
201
|
+
pre-provisioned agent by its platform-stamped source template
|
|
202
|
+
(`matchedBy:"agentTemplate"`) regardless of its DeveloperName. The `OR
|
|
203
|
+
DeveloperName=` clause catches a **dangling** link — a `botDefinitionId` whose
|
|
204
|
+
target row was since deleted: the by-Id half returns nothing, the live same-name
|
|
205
|
+
agent still surfaces, and the classifier falls back to it
|
|
206
|
+
(`matchedBy:"developerName"`) rather than concluding `exists:false` and
|
|
207
|
+
colliding with `DUPLICATE_VALUE`:
|
|
198
208
|
```bash
|
|
199
|
-
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>'" \
|
|
209
|
+
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>'" \
|
|
200
210
|
--target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
|
|
201
|
-
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>"
|
|
211
|
+
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>" "<agentTemplate>"
|
|
202
212
|
```
|
|
203
213
|
|
|
204
|
-
Only when **
|
|
205
|
-
classifier return `exists:false` without reading a query
|
|
206
|
-
|
|
214
|
+
Only when **all** of `botDefinitionId`, `<agentTemplate>`, and `<developerName>`
|
|
215
|
+
are absent does the classifier return `exists:false` without reading a query
|
|
216
|
+
file — in practice the collected `<developerName>` and `template.id` (AgentTemplate)
|
|
217
|
+
are always present, so the fallback read always runs.
|
|
207
218
|
|
|
208
219
|
The `BotVersions` subquery (child relationship on `BotDefinition`) is what lets
|
|
209
220
|
the classifier see the latest version's `Status` — omit it and
|
|
210
221
|
`latestVersionStatus`/`needsActivation` come back `null`/`false` even when the
|
|
211
222
|
existing agent is actually inactive. The classifier prints
|
|
212
223
|
`{ exists, count, matchedBy, agentId, botDefinitionId, developerName, latestVersionId, latestVersionStatus, needsActivation }`
|
|
213
|
-
(`matchedBy` is `"botDefinitionId"` | `"developerName"` | `null`;
|
|
214
|
-
is the ACTUAL live agent's DeveloperName read from the record —
|
|
224
|
+
(`matchedBy` is `"botDefinitionId"` | `"agentTemplate"` | `"developerName"` | `null`;
|
|
225
|
+
`developerName` is the ACTUAL live agent's DeveloperName read from the record —
|
|
226
|
+
surface it in the
|
|
215
227
|
report instead of the collected guess):
|
|
216
228
|
- `exists:false` ⇒ proceed to create.
|
|
217
229
|
- `exists:true` and `needsActivation:false` (latest version `Active`) ⇒
|
package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md
CHANGED
|
@@ -9,13 +9,15 @@ 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
|
|
13
|
-
`
|
|
14
|
-
|
|
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
|
-
That means a `BotDefinition` matching the template (by `botDefinitionId`)
|
|
17
|
-
collected `DeveloperName` already exists (created
|
|
18
|
-
|
|
17
|
+
That means a `BotDefinition` matching the template (by `botDefinitionId`), its
|
|
18
|
+
`AgentTemplate` source id, or the collected `DeveloperName` already exists (created
|
|
19
|
+
on a prior run, or — for the broad Employee agent, matched by its source template —
|
|
20
|
+
by the platform) 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`.
|
|
21
23
|
|
package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md
CHANGED
|
@@ -28,10 +28,10 @@ node "<skill_dir>/scripts/classify-preflight.mjs" ${SCRATCH_DIR}/studio-access.j
|
|
|
28
28
|
|
|
29
29
|
Pass the resolved `<masterLabel>` — `"IT Service Employee"` for the default broad path, or the picked specialized template's `masterLabel` (e.g. `"Password Manager Assistance"`) for a specialized path. The classifier matches `data[]` case-insensitively on `masterLabel` and confirms the matched row has a non-empty `agentScript`.
|
|
30
30
|
|
|
31
|
-
The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgentScript,botDefinitionId,signal}, verdict, reasons }`:
|
|
31
|
+
The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgentScript,botDefinitionId,masterLabel,signal}, verdict, reasons }`:
|
|
32
32
|
|
|
33
33
|
- `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.
|
|
34
|
-
- **`template.botDefinitionId` is the PRIMARY Phase-2 idempotency key.** A populated value means this template has already been instantiated into a live `BotDefinition
|
|
34
|
+
- **`template.botDefinitionId` is the PRIMARY Phase-2 idempotency key.** A populated value means this template has already been instantiated into a live `BotDefinition`; a `null` value means it has not been created yet OR it was created by this skill (self-created agents never back-fill the template row) OR — as with the broad pre-provisioned Employee agent — the platform instantiated it without joining the template row back. Capture `botDefinitionId` for Phase 2, and carry the collected `<developerName>` (first fallback) and `template.masterLabel` (second fallback) as the fallback keys.
|
|
35
35
|
- `verdict:"READY"` ⇒ continue to Phase 2.
|
|
36
36
|
- `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.
|
|
37
37
|
- `verdict:"NOT-READY"` because `studio.signal="FAIL"` (`hasAccess=false`) ⇒ do NOT proceed — **offer the prerequisite hand-off**:
|
|
@@ -44,22 +44,22 @@ The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgen
|
|
|
44
44
|
|
|
45
45
|
## Phase 2 — Idempotency: does the target agent already exist, and is it active?
|
|
46
46
|
|
|
47
|
-
Idempotency is keyed **PRIMARILY** on the template's `botDefinitionId` (captured in Phase 1)
|
|
47
|
+
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 that link is populated **only** when the platform joined the row back — which it does NOT do for the broad pre-provisioned Employee agent (it ships active under DeveloperName `IT_Service_Employee` with a `null` template `botDefinitionId`) nor for an agent this skill creates (which never stamps `templateName`). So for the broad agent both the primary key and the `DeveloperName` guess miss (`IT_Service_Employee` ≠ the default `IT_Service_Employee_Agent`), and the `AgentTemplate`-keyed fallback — matching `svc_emp_intelligence__ItEmployeeAssistance`, the platform-stamped source template on the live `BotDefinition`, immune to any rename — is what recovers it; for a self-created repeat (null `AgentTemplate` too) the `DeveloperName` fallback is the guard. Never short-circuit to create when `botDefinitionId` is absent.
|
|
48
48
|
|
|
49
|
-
- **`template.botDefinitionId` is empty/null** ⇒ fall back to
|
|
49
|
+
- **`template.botDefinitionId` is empty/null** (the broad pre-provisioned agent AND every self-created agent) ⇒ fall back to an `AgentTemplate` **`OR` `DeveloperName`** read (do **not** short-circuit to create — the broad agent is recovered by its OOTB source template, and 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:
|
|
50
50
|
```bash
|
|
51
|
-
sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE DeveloperName='<developerName>'" \
|
|
51
|
+
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>'" \
|
|
52
52
|
--target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
|
|
53
|
-
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>"
|
|
53
|
+
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "" "<developerName>" "<agentTemplate>"
|
|
54
54
|
```
|
|
55
|
-
- **`template.botDefinitionId` is present** ⇒ read the `BotDefinition` by Id **`OR` by the collected `<developerName>`** in one query, INCLUDING its latest version's status, then classify. The `OR
|
|
55
|
+
- **`template.botDefinitionId` is present** ⇒ 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 catches the pre-provisioned 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`:
|
|
56
56
|
```bash
|
|
57
|
-
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>'" \
|
|
57
|
+
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>'" \
|
|
58
58
|
--target-org <alias> --json > ${SCRATCH_DIR}/bot-existing.json 2>${SCRATCH_DIR}/bot-existing.err || true
|
|
59
|
-
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>"
|
|
59
|
+
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-existing.json "<botDefinitionId>" "<developerName>" "<agentTemplate>"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
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 (e.g. `IT_Service_Employee`), surface it in the report rather than the collected guess:
|
|
62
|
+
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 (e.g. `IT_Service_Employee`), surface it in the report rather than the collected guess:
|
|
63
63
|
|
|
64
64
|
- `exists:false` ⇒ proceed to Phase 3 (create path).
|
|
65
65
|
- `exists:true` and `needsActivation:false` (latest version `Active`) ⇒ **ALREADY-CREATED** — skip the write, fall through to Phase 7 verification.
|
|
@@ -3,50 +3,66 @@
|
|
|
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
|
|
7
|
-
//
|
|
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
|
+
// source-template API name it was instantiated from, e.g.
|
|
9
|
+
// `svc_emp_intelligence__ItEmployeeAssistance`), then to a `DeveloperName`-keyed
|
|
10
|
+
// read.
|
|
8
11
|
// The broad "IT Service Employee" agent is pre-provisioned+active under
|
|
9
12
|
// DeveloperName `IT_Service_Employee`, which does NOT match this skill's default
|
|
10
|
-
// collected developerName `IT_Service_Employee_Agent
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// pre-provisioned
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// collected developerName `IT_Service_Employee_Agent`, and its template row
|
|
14
|
+
// carries a NULL botDefinitionId — so a read keyed on botDefinitionId + collected
|
|
15
|
+
// DeveloperName false-negatives (exists:false) and the create then collides on
|
|
16
|
+
// `apiName` with `DUPLICATE_VALUE`. `AgentTemplate` is what closes that gap: the
|
|
17
|
+
// live pre-provisioned agent carries `AgentTemplate=svc_emp_intelligence__ItEmployeeAssistance`
|
|
18
|
+
// (the exact template id the skill installs — preflight `template.id`), so matching
|
|
19
|
+
// on it recovers the agent regardless of its DeveloperName. This is far more
|
|
20
|
+
// reliable than a display name (MasterLabel) — AgentTemplate is the platform-
|
|
21
|
+
// stamped source template, immune to renames, and namespaced OOTB. botDefinitionId
|
|
22
|
+
// is back-filled onto the template row ONLY for platform-pre-provisioned agents:
|
|
23
|
+
// the ITSM create path never stamps `templateName`, so agents THIS skill creates
|
|
24
|
+
// keep BOTH a null template botDefinitionId AND a null `AgentTemplate` forever —
|
|
25
|
+
// for those, the DeveloperName-keyed read is the reliable guard, used as the last
|
|
26
|
+
// FALLBACK. (Preflight emits `template.botDefinitionId`, `template.id`, and the
|
|
27
|
+
// collected developerName; the workflow passes the botDefinitionId as the primary
|
|
28
|
+
// key, template.id as the AgentTemplate key, and the developerName as the fallback.)
|
|
19
29
|
//
|
|
20
30
|
// This classifier consumes the raw JSON of a `sf data query ... --json` read of
|
|
21
|
-
// BotDefinition (BY Id primarily, BY DeveloperName on the
|
|
31
|
+
// BotDefinition (BY Id primarily, BY AgentTemplate then DeveloperName on the
|
|
32
|
+
// fallback) and decides
|
|
22
33
|
// whether the agent already exists — and if so, whether its latest BotVersion is
|
|
23
34
|
// Inactive (so the workflow can offer to activate it instead of creating a
|
|
24
35
|
// duplicate). This is the deterministic decision that gates whether the write is
|
|
25
36
|
// skipped — it MUST NOT be interpreted by the model in prose (authoring standard A9).
|
|
26
37
|
//
|
|
27
38
|
// Usage:
|
|
28
|
-
// node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName]
|
|
39
|
+
// node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName] [agentTemplate]
|
|
29
40
|
//
|
|
30
41
|
// When <botDefinitionId> is empty, `-`, or the literal `null`/`undefined` (the
|
|
31
|
-
// matched template carries no botDefinitionId
|
|
32
|
-
// keying on <
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
42
|
+
// matched template carries no botDefinitionId — the normal Employee case), the
|
|
43
|
+
// classifier FALLS BACK to keying on <agentTemplate> (the source template API
|
|
44
|
+
// name — this is what catches the pre-provisioned `IT_Service_Employee`), and then
|
|
45
|
+
// to <developerName> when the AgentTemplate misses too. <bot-query.json> must
|
|
46
|
+
// therefore be a read that ORs all three keys together: self-created agents are
|
|
47
|
+
// never back-filled with a botDefinitionId AND carry a null AgentTemplate (the
|
|
48
|
+
// ITSM create path doesn't stamp `templateName`), so DeveloperName is their only
|
|
49
|
+
// idempotency guard; the pre-provisioned broad agent — whose DeveloperName differs
|
|
50
|
+
// from this skill's collected guess — is caught by AgentTemplate. Only when ALL of
|
|
51
|
+
// <botDefinitionId>, <agentTemplate>, and <developerName> are absent is
|
|
52
|
+
// exists:false emitted without reading a query file. Otherwise <bot-query.json> is
|
|
53
|
+
// a FILE PATH to the stdout captured from:
|
|
54
|
+
// sf data query -q "SELECT Id,DeveloperName,MasterLabel,AgentTemplate,
|
|
41
55
|
// (SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1)
|
|
42
|
-
// FROM BotDefinition WHERE Id='<botDefinitionId>' OR
|
|
56
|
+
// FROM BotDefinition WHERE Id='<botDefinitionId>' OR AgentTemplate='<agentTemplate>'
|
|
57
|
+
// OR DeveloperName='<developerName>'" \
|
|
43
58
|
// --target-org <org> --json > file.json
|
|
44
|
-
// (The present-id read ORs in DeveloperName so a DANGLING
|
|
45
|
-
// link — a botDefinitionId whose target BotDefinition was
|
|
46
|
-
// surfaces the live
|
|
47
|
-
//
|
|
48
|
-
// the template row carries no botDefinitionId at
|
|
49
|
-
// `DeveloperName='<developerName>'
|
|
59
|
+
// (The present-id read ORs in AgentTemplate AND DeveloperName so a DANGLING
|
|
60
|
+
// template→BotDefinition link — a botDefinitionId whose target BotDefinition was
|
|
61
|
+
// since deleted — still surfaces the live agent: the classifier tries the Id
|
|
62
|
+
// first, then the AgentTemplate, then the DeveloperName on the SAME records. The
|
|
63
|
+
// pure FALLBACK read, taken when the template row carries no botDefinitionId at
|
|
64
|
+
// all, keys on `AgentTemplate='<agentTemplate>' OR DeveloperName='<developerName>'`;
|
|
65
|
+
// everything else is identical.
|
|
50
66
|
// `sf data query --json` wraps results in a `.result.records[]` envelope. The
|
|
51
67
|
// BotVersions subquery is required so this classifier can see the latest
|
|
52
68
|
// version's Status — a query without it leaves latestVersionStatus null and
|
|
@@ -61,16 +77,17 @@
|
|
|
61
77
|
// child row was returned), and needsActivation is true only when exists is true
|
|
62
78
|
// AND latestVersionStatus is "Inactive" — the signal to offer activating the
|
|
63
79
|
// existing version instead of creating a new agent. `matchedBy` is
|
|
64
|
-
// "botDefinitionId" | "developerName" | null, recording which
|
|
80
|
+
// "botDefinitionId" | "agentTemplate" | "developerName" | null, recording which
|
|
81
|
+
// key hit. Exit code
|
|
65
82
|
// is always 0 on a parseable body (or when neither key is supplied); the verdict
|
|
66
83
|
// is carried in the payload. On an unparseable/failed query it exits 3 so the
|
|
67
84
|
// workflow surfaces the raw error rather than assuming NOT-EXISTS.
|
|
68
85
|
|
|
69
86
|
import { readFileSync } from 'node:fs';
|
|
70
87
|
|
|
71
|
-
const [queryPath, rawBotDefinitionId, rawDeveloperName] = process.argv.slice(2);
|
|
88
|
+
const [queryPath, rawBotDefinitionId, rawDeveloperName, rawAgentTemplate] = process.argv.slice(2);
|
|
72
89
|
if (!queryPath) {
|
|
73
|
-
process.stderr.write('usage: node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName]\n');
|
|
90
|
+
process.stderr.write('usage: node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName] [agentTemplate]\n');
|
|
74
91
|
process.exit(2);
|
|
75
92
|
}
|
|
76
93
|
|
|
@@ -88,8 +105,10 @@ function notSet(v) {
|
|
|
88
105
|
|
|
89
106
|
const botDefinitionId = String(rawBotDefinitionId ?? '').trim();
|
|
90
107
|
const developerName = String(rawDeveloperName ?? '').trim();
|
|
108
|
+
const agentTemplate = String(rawAgentTemplate ?? '').trim();
|
|
91
109
|
const noId = notSet(botDefinitionId);
|
|
92
110
|
const noName = notSet(developerName);
|
|
111
|
+
const noTemplate = notSet(agentTemplate);
|
|
93
112
|
|
|
94
113
|
const NOT_EXISTS = {
|
|
95
114
|
exists: false,
|
|
@@ -103,11 +122,12 @@ const NOT_EXISTS = {
|
|
|
103
122
|
needsActivation: false,
|
|
104
123
|
};
|
|
105
124
|
|
|
106
|
-
// Nothing to key on — no botDefinitionId AND no
|
|
107
|
-
// no way to detect an existing agent, so this is
|
|
108
|
-
// should always pass the collected developerName
|
|
109
|
-
//
|
|
110
|
-
|
|
125
|
+
// Nothing to key on — no botDefinitionId AND no AgentTemplate fallback AND no
|
|
126
|
+
// DeveloperName fallback. There is no way to detect an existing agent, so this is
|
|
127
|
+
// the create path. (The workflow should always pass the collected developerName
|
|
128
|
+
// and the template's id/AgentTemplate so the fallbacks below can run; this bare
|
|
129
|
+
// branch only fires when none of the three keys is supplied.)
|
|
130
|
+
if (noId && noTemplate && noName) {
|
|
111
131
|
process.stdout.write(JSON.stringify(NOT_EXISTS, null, 2) + '\n');
|
|
112
132
|
process.exit(0);
|
|
113
133
|
}
|
|
@@ -131,19 +151,26 @@ if (!data || (typeof data.status === 'number' && data.status !== 0) || data.resu
|
|
|
131
151
|
const records = Array.isArray(data.result?.records) ? data.result.records : [];
|
|
132
152
|
|
|
133
153
|
// PRIMARY key: the template's `botDefinitionId` (the platform's authoritative
|
|
134
|
-
// template→BotDefinition link) — catches
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
// `
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
154
|
+
// template→BotDefinition link) — catches a pre-provisioned agent whose live
|
|
155
|
+
// DeveloperName differs from this skill's collected guess. For the broad Employee
|
|
156
|
+
// agent this is null (see header), so in practice the AgentTemplate fallback below
|
|
157
|
+
// is what fires.
|
|
158
|
+
// FALLBACK: the BotDefinition's own `AgentTemplate`, then the collected
|
|
159
|
+
// DeveloperName. `AgentTemplate` is the OOTB source-template API name the platform
|
|
160
|
+
// stamps on a template-instantiated agent — the reliable "an agent from THIS
|
|
161
|
+
// template already exists" signal for the pre-provisioned broad/specialized
|
|
162
|
+
// agents, independent of any DeveloperName rename (the pre-provisioned
|
|
163
|
+
// `IT_Service_Employee` carries `AgentTemplate=svc_emp_intelligence__ItEmployeeAssistance`
|
|
164
|
+
// even though its DeveloperName differs from this skill's guess). Self-created
|
|
165
|
+
// agents are NOT stamped (the ITSM create path never sets `templateName`), so their
|
|
166
|
+
// `AgentTemplate` is null and DeveloperName is their guard. We ALSO fall back this
|
|
167
|
+
// way when a PRESENT botDefinitionId matches nothing (a dangling link whose target
|
|
168
|
+
// BotDefinition was deleted): the present-id Phase-2 SOQL reads `WHERE Id=... OR
|
|
169
|
+
// AgentTemplate=... OR DeveloperName=...`, so a stale link still surfaces the live
|
|
170
|
+
// agent instead of slipping through to the create path and colliding with
|
|
171
|
+
// DUPLICATE_VALUE. Try the Id first, then the template, then the name; each filter
|
|
172
|
+
// is self-checking on top of the SOQL WHERE so a wrong-keyed read can't
|
|
173
|
+
// false-positive.
|
|
147
174
|
let matches = [];
|
|
148
175
|
let matchedBy = null;
|
|
149
176
|
if (!noId) {
|
|
@@ -151,12 +178,40 @@ if (!noId) {
|
|
|
151
178
|
matches = records.filter((r) => idKey(r?.Id) === wantedIdKey);
|
|
152
179
|
if (matches.length) matchedBy = 'botDefinitionId';
|
|
153
180
|
}
|
|
181
|
+
// FIRST FALLBACK: the BotDefinition's `AgentTemplate` (source template API name).
|
|
182
|
+
// The broad pre-provisioned "IT Service Employee" agent lives under DeveloperName
|
|
183
|
+
// `IT_Service_Employee` — which the DeveloperName filter below misses when this
|
|
184
|
+
// skill collects its default `IT_Service_Employee_Agent` — while its template row
|
|
185
|
+
// carries no botDefinitionId, so the Id and DeveloperName keys both miss and the
|
|
186
|
+
// create then collides with DUPLICATE_VALUE. Its `AgentTemplate` is
|
|
187
|
+
// `svc_emp_intelligence__ItEmployeeAssistance` (the template this skill installs),
|
|
188
|
+
// so matching on it recovers the live agent reliably. SOQL text comparison is
|
|
189
|
+
// case-insensitive, so compare the same way. `AgentTemplate` is NOT unique (a
|
|
190
|
+
// template can be instantiated more than once), so if more than one record shares
|
|
191
|
+
// it we keep them all but surface an Active version first (see below) — the report
|
|
192
|
+
// should treat the already-active one as the real match.
|
|
193
|
+
if (!matches.length && !noTemplate) {
|
|
194
|
+
const wantedTemplate = agentTemplate.toLowerCase();
|
|
195
|
+
matches = records.filter((r) => String(r?.AgentTemplate ?? '').trim().toLowerCase() === wantedTemplate);
|
|
196
|
+
if (matches.length) matchedBy = 'agentTemplate';
|
|
197
|
+
}
|
|
198
|
+
// SECOND FALLBACK: the collected DeveloperName — the guard for self-created agents
|
|
199
|
+
// (null AgentTemplate). DeveloperName uniqueness is case-insensitive; compare
|
|
200
|
+
// accordingly.
|
|
154
201
|
if (!matches.length && !noName) {
|
|
155
|
-
// DeveloperName uniqueness is case-insensitive; compare accordingly.
|
|
156
202
|
const wantedName = developerName.toLowerCase();
|
|
157
203
|
matches = records.filter((r) => String(r?.DeveloperName ?? '').trim().toLowerCase() === wantedName);
|
|
158
204
|
if (matches.length) matchedBy = 'developerName';
|
|
159
205
|
}
|
|
206
|
+
if (matchedBy === 'agentTemplate' && matches.length > 1) {
|
|
207
|
+
// Prefer a record whose latest BotVersion is Active so `matches[0]` (used for
|
|
208
|
+
// latestVersion/agentId below) reflects the live agent rather than a stale draft.
|
|
209
|
+
matches = [...matches].sort((a, b) => {
|
|
210
|
+
const aActive = a?.BotVersions?.records?.[0]?.Status === 'Active' ? 0 : 1;
|
|
211
|
+
const bActive = b?.BotVersions?.records?.[0]?.Status === 'Active' ? 0 : 1;
|
|
212
|
+
return aActive - bActive;
|
|
213
|
+
});
|
|
214
|
+
}
|
|
160
215
|
const exists = matches.length > 0;
|
|
161
216
|
|
|
162
217
|
// The BotVersions child subquery (if present in the SOQL) surfaces the latest
|
|
@@ -171,12 +226,15 @@ const needsActivation = exists && latestVersionStatus === 'Inactive';
|
|
|
171
226
|
process.stdout.write(JSON.stringify({
|
|
172
227
|
exists,
|
|
173
228
|
count: matches.length,
|
|
174
|
-
// Which key matched — `botDefinitionId` (primary)
|
|
175
|
-
//
|
|
229
|
+
// Which key matched — `botDefinitionId` (primary), `agentTemplate` (the
|
|
230
|
+
// reliable catcher for the pre-provisioned broad agent whose DeveloperName
|
|
231
|
+
// differs from the collected guess), or `developerName` (the fallback for
|
|
232
|
+
// self-created agents). null when nothing matched.
|
|
176
233
|
matchedBy: exists ? matchedBy : null,
|
|
177
234
|
agentId: exists ? (matches[0].Id ?? null) : null,
|
|
178
|
-
// On
|
|
179
|
-
// template row was missing; on the Id path it echoes the
|
|
235
|
+
// On an AgentTemplate- or DeveloperName-fallback hit this surfaces the live
|
|
236
|
+
// BotDefinition Id the template row was missing; on the Id path it echoes the
|
|
237
|
+
// queried id.
|
|
180
238
|
botDefinitionId: exists ? (matches[0].Id ?? null) : (noId ? null : botDefinitionId),
|
|
181
239
|
developerName: exists ? (matches[0].DeveloperName ?? null) : null,
|
|
182
240
|
latestVersionId: latestVersion?.Id ?? null,
|