@salesforce/afv-skills 1.40.0 → 1.41.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/automation-flow-generate/SKILL.md +32 -43
- package/skills/experience-portal-create/SKILL.md +497 -0
- package/skills/experience-portal-create/assets/report-template.md +30 -0
- package/skills/experience-portal-create/references/mcp-invocation.md +288 -0
- package/skills/experience-portal-create/references/post-creation-activate-publish.md +165 -0
- package/skills/experience-portal-create/references/templates.md +253 -0
- package/skills/experience-ui-bundle-features-generate/SKILL.md +5 -1
- package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -0
- package/skills/experience-ui-bundle-frontend-generate/references/page.md +1 -0
- package/skills/platform-datamask-run/SKILL.md +345 -0
- package/skills/platform-datamask-run/references/api-surface.md +130 -0
- package/skills/platform-datamask-run/references/policy-authoring.md +185 -0
- package/skills/platform-datamask-run/references/run-and-abort.md +116 -0
- package/skills/platform-datamask-run/scripts/poll-job.sh +115 -0
- package/skills/platform-dataspace-access-configure/SKILL.md +51 -3
- package/skills/platform-dataspace-access-configure/scripts/inspect-dataspace-scopes.sh +56 -0
- package/skills/platform-lightning-type-widget-coordinate/references/build-plan-format.md +1 -0
- package/skills/platform-sandbox-configure/SKILL.md +17 -2
- package/skills/platform-trial-org-create/SKILL.md +175 -0
- package/skills/platform-trial-org-create/examples/create_request.json +9 -0
- package/skills/platform-trial-org-create/examples/error_response.json +41 -0
- package/skills/platform-trial-org-create/examples/success_response.json +27 -0
- package/skills/platform-trial-org-create/references/error_codes.md +42 -0
- package/skills/platform-trial-org-create/references/signup_request_fields.md +69 -0
- package/skills/platform-trial-org-create/scripts/create_signup_request.sh +175 -0
- package/skills/platform-trial-org-create/scripts/get_signup_request.sh +155 -0
- package/skills/platform-widget-generate/SKILL.md +47 -6
- package/skills/platform-widget-generate/examples/conditional.json +3 -3
- package/skills/platform-widget-generate/examples/list-with-foreach.json +2 -2
- package/skills/platform-widget-generate/examples/single-object.json +2 -2
- package/skills/platform-widget-generate/references/widget-bundle-layout.md +1 -1
- package/skills/service-agentforce-channel-configure/SKILL.md +271 -0
- package/skills/service-agentforce-channel-configure/references/agent-wiring.md +97 -0
- package/skills/service-agentforce-channel-configure/references/channel-branch-email.md +145 -0
- package/skills/service-agentforce-channel-configure/references/channel-branch-voice.md +69 -0
- package/skills/service-agentforce-channel-configure/references/channel-types.md +61 -0
- package/skills/service-agentforce-channel-configure/references/live-traffic-gate.md +86 -0
- package/skills/service-agentforce-channel-configure/references/queue-resolution.md +135 -0
- package/skills/service-agentforce-channel-configure/references/routing-flow.md +384 -0
- package/skills/service-catalog-template-deploy/SKILL.md +310 -0
- package/skills/service-catalog-template-deploy/references/cli-invocation.md +258 -0
- package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +164 -0
- package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +94 -0
- package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +331 -0
- package/skills/service-catalog-template-search/SKILL.md +212 -0
- package/skills/service-catalog-template-search/references/cli-invocation.md +128 -0
- package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +205 -0
- package/skills/service-concierge-portal-generate/SKILL.md +126 -0
- package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1428 -0
- package/skills/service-digital-engagement-channel-configure/SKILL.md +46 -6
- package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +2 -1
- package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -1
- package/skills/service-helpagent-coordinate/README.md +8 -2
- package/skills/service-helpagent-coordinate/SKILL.md +126 -130
- package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +70 -53
- package/skills/service-helpagent-coordinate/references/agent-script.md +571 -457
- package/skills/service-helpagent-coordinate/references/channel-voice.md +38 -9
- package/skills/service-helpagent-coordinate/references/channel-web-chat.md +173 -49
- package/skills/service-helpagent-coordinate/references/output-report-format.md +126 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +153 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +79 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +35 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +271 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/references/cli-invocation.md +265 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-enable-plan.mjs +220 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-final-report.mjs +102 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/record-enable-result.mjs +73 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-validate/SKILL.md +206 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-validate/references/cli-invocation.md +194 -0
- package/skills/service-itsm-agentic-setup-agentforce-studio-validate/scripts/classify-readiness.mjs +223 -0
- package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +45 -7
- package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +45 -5
- package/skills/service-itsm-agentic-setup-configure/SKILL.md +116 -0
- package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +64 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +158 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +361 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/error-taxonomy.md +44 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +66 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +66 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/specialized-templates.md +148 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +169 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/build-create-body.mjs +116 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +185 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +168 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/create-scratch-dir.mjs +58 -0
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +197 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +186 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/action-availability.md +51 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +345 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/error-taxonomy.md +44 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +63 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +44 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +149 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/build-create-body.mjs +110 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-action-availability.mjs +201 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-activate-result.mjs +135 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +194 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +158 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/create-scratch-dir.mjs +58 -0
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +191 -0
- package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +133 -0
- package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +71 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +308 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +23 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +193 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +57 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +394 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/SKILL.md +266 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/cli-invocation.md +106 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/helper-contracts.md +142 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/permset-topology.md +82 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-action-surface.mjs +137 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-assignment-state.mjs +99 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-permset-availability.mjs +120 -0
- package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/resolve-target-user.mjs +86 -0
- package/skills/service-itsm-agentic-setup-uel-user-create/SKILL.md +284 -0
- package/skills/service-itsm-agentic-setup-uel-user-create/references/mcp-invocation.md +302 -0
- package/skills/service-itsm-channels-coordinate/SKILL.md +472 -0
- package/skills/service-itsm-incident-mgmt-configure/SKILL.md +212 -0
- package/skills/service-itsm-incident-mgmt-configure/references/mcp-invocation.md +225 -0
- package/skills/service-itsm-incident-priority-configure/SKILL.md +53 -12
- package/skills/service-itsm-swarming-configure/SKILL.md +212 -0
- package/skills/service-itsm-teams-configure/SKILL.md +395 -0
- package/skills/service-itsm-teams-configure/references/azure-credential-population.md +213 -0
- package/skills/service-itsm-teams-configure/references/gotchas.md +23 -0
- package/skills/service-itsm-teams-coordinate/SKILL.md +175 -0
- package/skills/service-itsm-teams-coordinate/examples/output-templates.md +85 -0
- package/skills/service-itsm-teams-debug/SKILL.md +144 -0
- package/skills/service-itsm-teams-debug/references/configuration-checklists.md +277 -0
- package/skills/service-itsm-teams-debug/references/report-generation.md +95 -0
- package/skills/service-itsm-teams-employee-agent-configure/SKILL.md +139 -0
- package/skills/service-itsm-teams-employee-agent-configure/assets/Teams_AgentForce.EmbeddedServiceConfig-meta.xml +43 -0
- package/skills/service-itsm-teams-employee-agent-configure/references/teams-embedded-employee-agent.md +480 -0
- package/skills/service-itsm-teams-itdesk-configure/SKILL.md +232 -0
- package/skills/service-itsm-teams-itservice-configure/SKILL.md +391 -0
package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Fulfiller Agent — Workflow Detail
|
|
2
|
+
|
|
3
|
+
Detailed per-phase command shapes + verdict narrative for `service-itsm-agentic-setup-fulfiller-agent-configure`. Read this alongside the phase headers in `SKILL.md` — the SKILL body holds the load-bearing rules, this file holds the exact commands and branch-by-branch verdict handling.
|
|
4
|
+
|
|
5
|
+
All calls go through `sf`; substitute `<alias>` with the collected target org and `<developerName>` / `<label>` with the collected values.
|
|
6
|
+
|
|
7
|
+
## Phase 0 — Establish the per-run scratch directory
|
|
8
|
+
|
|
9
|
+
Before any phase writes a transient JSON file, invoke `scripts/create-scratch-dir.mjs` — path selection (`${TMPDIR}` → `/tmp` → `${outputDir}` last-resort, keeping transient captures out of the scored `${outputDir}` tree) and the `mkdtemp` call live in the helper so the deterministic logic never sits in prose (A9). Every subsequent phase writes its scratch JSON under `${SCRATCH_DIR}`, so runs never collide and the transient state stays scoped to the run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
SCRATCH_DIR="$(node "<skill_dir>/scripts/create-scratch-dir.mjs" "${outputDir:-}")"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The remaining phases reference `${SCRATCH_DIR}` for every transient path (studio-access, agent-templates, bot-existing, create-bundle-body, publish/activate response captures, report-state, verify). The **durable** run artifact — `${outputDir}/report.md` — is still written to the harness-provided directory.
|
|
16
|
+
|
|
17
|
+
## Phase 1 — Preflight: confirm Studio access + template provisioning
|
|
18
|
+
|
|
19
|
+
Capture the Studio-access read and the `agent-templates` read, then let `scripts/classify-preflight.mjs` make the deterministic decisions (never parse `hasAccess` or search `data[]` in prose — A9). The `agent-templates` `agentType` query param is **required** with value `AgentforceEmployeeAgent` (a call without it returns `400 MISSING_ARGUMENT: agentType`):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
sf api request rest "/services/data/v67.0/agentforce-studio/access/Agents" \
|
|
23
|
+
--method GET --target-org <alias> > ${SCRATCH_DIR}/studio-access.json 2>${SCRATCH_DIR}/studio-access.err || true
|
|
24
|
+
sf api request rest "/services/data/v67.0/connect/service-itsm/agent-templates?agentType=AgentforceEmployeeAgent" \
|
|
25
|
+
--method GET --target-org <alias> > ${SCRATCH_DIR}/agent-templates.json 2>${SCRATCH_DIR}/agent-templates.err || true
|
|
26
|
+
node "<skill_dir>/scripts/classify-preflight.mjs" ${SCRATCH_DIR}/studio-access.json ${SCRATCH_DIR}/agent-templates.json "IT Service Fulfiller"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The classifier prints `{ studio:{hasAccess,signal}, template:{present,id,hasAgentScript,botDefinitionId,signal}, verdict, reasons }`:
|
|
30
|
+
|
|
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.
|
|
33
|
+
- `verdict:"READY"` ⇒ continue to Phase 2.
|
|
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
|
+
- `verdict:"NOT-READY"` because `studio.signal="FAIL"` (`hasAccess=false`) ⇒ do NOT proceed — **offer the prerequisite hand-off**:
|
|
36
|
+
- Present a single `AskUserQuestion`: _"Agentforce for IT Service prerequisites don't look satisfied on this org. Run `service-itsm-agentic-setup-agentforce-studio-validate` to check readiness first?"_ (options: **Yes, check readiness** / **No, stop here**).
|
|
37
|
+
- On **Yes**: delegate to `service-itsm-agentic-setup-agentforce-studio-validate` (read-only readiness check). If it reports NOT-READY, stop and report the missing prerequisite (enablement is a Setup-UI/admin action, outside both skills' write scope). If READY, resume from Phase 2.
|
|
38
|
+
- On **No**: stop and report the missing prerequisite verbatim — no writes.
|
|
39
|
+
- `verdict:"NOT-READY"` because `template.signal="FAIL"` (empty/no matching template) ⇒ the org is not ITSM-provisioned; hand off to the readiness check.
|
|
40
|
+
- `template.signal="CANNOT-CONFIRM"` because the matched template has no `agentScript` ⇒ the NGA create has nothing to build from; surface this and stop (do not fall back to the legacy `createAgent` route — it is out of scope for this skill).
|
|
41
|
+
- `studio.signal="CANNOT-CONFIRM"` (a **confirmed** `404` — the access-check gate isn't wired on the org, expected on scratch orgs) does **not** block: a successful create/publish/activate is authoritative. A later `403 FUNCTIONALITY_NOT_ENABLED` on the write is the same signal as `hasAccess=false` — trigger the same hand-off offer.
|
|
42
|
+
|
|
43
|
+
## Phase 2 — Idempotency: does the target agent already exist, and is it active?
|
|
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.
|
|
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:
|
|
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>'" \
|
|
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>"
|
|
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`:
|
|
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>'" \
|
|
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>"
|
|
58
|
+
```
|
|
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:
|
|
61
|
+
|
|
62
|
+
- `exists:false` ⇒ proceed to Phase 3 (create path).
|
|
63
|
+
- `exists:true` and `needsActivation:false` (latest version `Active`) ⇒ **ALREADY-CREATED** — skip the write, fall through to Phase 7 verification.
|
|
64
|
+
- `exists:true` and `needsActivation:true` (latest version `Inactive`) ⇒ do **not** create a duplicate agent — go to Phase 2b to offer activating the existing version instead.
|
|
65
|
+
- A non-zero exit (exit 3) means the query itself failed — surface the raw CLI error, do not assume the agent is absent.
|
|
66
|
+
|
|
67
|
+
## Phase 2b — Reactivation offer (existing agent found inactive)
|
|
68
|
+
|
|
69
|
+
Present the discovery to the user via `AskUserQuestion`: _"A Fulfiller agent named `<developerName>` already exists but its latest version is Inactive. Activate the existing version instead of creating a new agent?"_ (options: **Yes, activate the existing version** / **No, stop here**).
|
|
70
|
+
|
|
71
|
+
- On **Yes**: activate the captured `latestVersionId` directly — no create/publish needed since the `BotDefinition`/`BotVersion` already exist:
|
|
72
|
+
```bash
|
|
73
|
+
sf api request rest "/services/data/v67.0/connect/bot-versions/<latestVersionId>/activation" \
|
|
74
|
+
--method POST --body '{"status":"Active"}' \
|
|
75
|
+
--target-org <alias> > ${SCRATCH_DIR}/activate-existing.json 2>${SCRATCH_DIR}/activate-existing.err || true
|
|
76
|
+
```
|
|
77
|
+
On success, skip Phases 3–6 entirely (no create/publish/confirm-to-write for a fresh agent) and go straight to Phase 7 to verify. Report the aggregate verdict as **ACTIVATED** in Phase 8, not CREATED.
|
|
78
|
+
- On **No**: stop and report the current state (agent exists, latest version Inactive) without any writes.
|
|
79
|
+
|
|
80
|
+
## Phase 3 — Confirm-to-Write Checkpoint (REQUIRED, create path only)
|
|
81
|
+
|
|
82
|
+
**Before raising the confirmation gate**, if `${outputDir}` was provided, render the report file via `scripts/render-report.mjs` with `verdict:"PENDING CONFIRMATION"` so it exists even if the harness parks at the question. Skip this write for interactive runs. Report-state JSON shape + checkpoint policy live in `references/report-format.md`.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node "<skill_dir>/scripts/render-report.mjs" ${SCRATCH_DIR}/report-state.json "${outputDir}/report.md"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
THEN **present the exact create request** (developerName + label, NGA-native from the Fulfiller template's Agent Script) and require an explicit "yes" via `AskUserQuestion`. **Proceed to Phase 4 ONLY on an explicit "yes".** On "no", stop and — if `${outputDir}` was provided — re-render with `verdict:"DECLINED"` and a one-line `reason`. Never fabricate a partial "create-only" outcome.
|
|
89
|
+
|
|
90
|
+
## Phase 4 — Create the NGA bundle
|
|
91
|
+
|
|
92
|
+
Build the `createBundleWithVersion` request body with the helper — it re-reads the Phase-1 `agent-templates.json`, extracts the matched template's `agentScript`, fully HTML-decodes it, and substitutes its internal `config.developer_name` / `config.agent_label` with the **collected** `<developerName>` / `<label>` (so the bundle's outer `apiName` and the script's internal identity match), keeping the large script content and free-text quotes out of an inline shell string:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
node "<skill_dir>/scripts/build-create-body.mjs" ${SCRATCH_DIR}/agent-templates.json "IT Service Fulfiller" "<developerName>" "<label>" ${SCRATCH_DIR}/create-bundle-body.json
|
|
96
|
+
sf api request rest "/services/data/v67.0/nextgen-authoring/bundles" \
|
|
97
|
+
--method POST \
|
|
98
|
+
--body @${SCRATCH_DIR}/create-bundle-body.json \
|
|
99
|
+
--target-org <alias> > ${SCRATCH_DIR}/create-bundle.json 2>${SCRATCH_DIR}/create-bundle.err || true
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
On success, the `POST /nextgen-authoring/bundles` response is a bundle-version detail: `{ apiName, label, bundleId, id, versionStatus:"DRAFT", assets:[...], ... }`. **Capture `id` — that is the `bundleVersionId` used in Phases 5 and 6, not `bundleId`.**
|
|
103
|
+
|
|
104
|
+
- `403 FUNCTIONALITY_NOT_ENABLED` / `404` → trigger the Phase-1 prerequisite hand-off offer (the NGA authoring namespace isn't provisioned on the org).
|
|
105
|
+
- A build-script exit 3 (template or `agentScript` not found in `${SCRATCH_DIR}/agent-templates.json`) → surface the script's stderr verbatim and stop; do not retry with a hand-typed template id.
|
|
106
|
+
|
|
107
|
+
## Phase 5 — Publish the bundle version
|
|
108
|
+
|
|
109
|
+
Publish the captured `bundleVersionId` — empty `--body '{}'` required (the CLI rejects a POST with no `--body`):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
sf api request rest "/services/data/v67.0/nextgen-authoring/bundle-versions/<bundleVersionId>/publish" \
|
|
113
|
+
--method POST --body '{}' \
|
|
114
|
+
--target-org <alias> > ${SCRATCH_DIR}/publish-bundle.json 2>${SCRATCH_DIR}/publish-bundle.err || true
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
On success: `{ lastPublishedOn, publishedBotId, publishedBotVersionId }`. This is the call that creates the underlying `BotDefinition`/`BotVersion`. An error here means the DRAFT version failed platform-side validation — surface it verbatim; do not attempt to activate an unpublished version.
|
|
118
|
+
|
|
119
|
+
## Phase 6 — Activate the bundle version
|
|
120
|
+
|
|
121
|
+
Activate the same `bundleVersionId`:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
sf api request rest "/services/data/v67.0/nextgen-authoring/bundle-versions/<bundleVersionId>/activate" \
|
|
125
|
+
--method POST --body '{}' \
|
|
126
|
+
--target-org <alias> > ${SCRATCH_DIR}/activate-bundle.json 2>${SCRATCH_DIR}/activate-bundle.err || true
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Success returns an **empty body** — do not treat empty stdout as a failure; check the CLI exit code and fall through to Phase 7 to confirm via `BotDefinition`/`BotVersion` state instead of parsing a response payload.
|
|
130
|
+
|
|
131
|
+
## Phase 7 — Verify the agent is live
|
|
132
|
+
|
|
133
|
+
Read `BotDefinition` by Id (with `BotVersions` subquery, same shape as Phase 2) and classify. The verify `<verifyId>` is the create path's **`publishedBotId`** (captured from the Phase-5 publish response) or, on the ALREADY-CREATED / reactivation path, the **live matched Id the Phase-2 classifier returned** (its `botDefinitionId`/`agentId` output — the actual `BotDefinition.Id` of the matched record), **not** the Phase-1 template `botDefinitionId` (which is always `null` for the Fulfiller, so on any existing-agent hit using it would run the verify as `WHERE Id=''` and falsely report failure after a successful skip/activation). Never the collected `<developerName>`:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
sf data query -q "SELECT Id,DeveloperName,MasterLabel,(SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1) FROM BotDefinition WHERE Id='<verifyId>'" \
|
|
137
|
+
--target-org <alias> --json > ${SCRATCH_DIR}/bot-verify.json 2>${SCRATCH_DIR}/bot-verify.err || true
|
|
138
|
+
node "<skill_dir>/scripts/classify-agent-existence.mjs" ${SCRATCH_DIR}/bot-verify.json "<verifyId>"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Confirm `exists:true` with `count:1`, and — on the create or reactivation path — `latestVersionStatus:"Active"`. `exists:false` after a successful activate ⇒ report the discrepancy verbatim, do not fabricate success. Optionally cross-check `GET /nextgen-authoring/bundles` for an entry with the collected `apiName` and `isLegacy:false` to confirm the agent is NGA-native (no external-link icon in Agentforce Studio).
|
|
142
|
+
|
|
143
|
+
## Phase 8 — Aggregate verdict
|
|
144
|
+
|
|
145
|
+
Report CREATED / ALREADY-CREATED / ACTIVATED / FAILED plus the `BotDefinition` Id / bundle `id`. Use **ACTIVATED** when Phase 2b's reactivation path was taken (existing agent found inactive, activated instead of creating new). Emit the final report by re-invoking `scripts/render-report.mjs` — the single source of report text. If `${outputDir}` was provided, overwrite `${outputDir}/report.md`; otherwise emit the helper's stdout as the turn-side report.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node "<skill_dir>/scripts/render-report.mjs" ${SCRATCH_DIR}/report-state.json "${outputDir}/report.md"
|
|
149
|
+
```
|
package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/build-create-body.mjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Build the NGA createBundleWithVersion request body from the shipped
|
|
3
|
+
// Fulfiller template's agentScript.
|
|
4
|
+
//
|
|
5
|
+
// The legacy `agent-templates` read already returns the template's full Agent
|
|
6
|
+
// Script (AFScript) content in its `agentScript` field, HTML-entity-encoded
|
|
7
|
+
// (sometimes double-encoded). This script:
|
|
8
|
+
// 1. Locates the matching template item by masterLabel in the
|
|
9
|
+
// agent-templates response.
|
|
10
|
+
// 2. Fully HTML-decodes `agentScript` (named + numeric entities, multi-pass
|
|
11
|
+
// to unwind double-encoding).
|
|
12
|
+
// 3. Substitutes the script's internal `config.developer_name` /
|
|
13
|
+
// `config.agent_label` with the collected <developerName> / <label> —
|
|
14
|
+
// the bundle's outer apiName must match the script's internal
|
|
15
|
+
// developer_name or the two diverge (the platform will otherwise
|
|
16
|
+
// auto-suffix the apiName).
|
|
17
|
+
// 4. Writes the createBundleWithVersion body
|
|
18
|
+
// { apiName, label, assets: [{ resourceName: "agentDefinition",
|
|
19
|
+
// resourceType: "agentDefinition", sections: [], resourceContent }] }
|
|
20
|
+
// to <outPath>.
|
|
21
|
+
//
|
|
22
|
+
// Usage:
|
|
23
|
+
// node build-create-body.mjs <agent-templates.json> <masterLabel> <developerName> <label> <outPath>
|
|
24
|
+
//
|
|
25
|
+
// Exits 2 on missing/bad args, 3 if the template or its agentScript can't be found.
|
|
26
|
+
|
|
27
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
const [templatesPath, masterLabel, developerName, label, outPath] = process.argv.slice(2);
|
|
30
|
+
if (!templatesPath || !masterLabel || !developerName || !label || !outPath) {
|
|
31
|
+
process.stderr.write('usage: node build-create-body.mjs <agent-templates.json> <masterLabel> <developerName> <label> <outPath>\n');
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function decodeHtmlEntities(text) {
|
|
36
|
+
let prev = text;
|
|
37
|
+
for (let pass = 0; pass < 4; pass += 1) {
|
|
38
|
+
const next = prev
|
|
39
|
+
.replace(/&/g, '&')
|
|
40
|
+
.replace(/"/g, '"')
|
|
41
|
+
.replace(/'/g, "'")
|
|
42
|
+
.replace(/</g, '<')
|
|
43
|
+
.replace(/>/g, '>')
|
|
44
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
|
|
45
|
+
if (next === prev) break;
|
|
46
|
+
prev = next;
|
|
47
|
+
}
|
|
48
|
+
return prev;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let templatesData;
|
|
52
|
+
try {
|
|
53
|
+
templatesData = JSON.parse(readFileSync(templatesPath, 'utf8'));
|
|
54
|
+
} catch {
|
|
55
|
+
process.stderr.write(`error: could not read/parse ${templatesPath}\n`);
|
|
56
|
+
process.exit(3);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const items = Array.isArray(templatesData?.data) ? templatesData.data : [];
|
|
60
|
+
const wanted = String(masterLabel).trim().toLowerCase();
|
|
61
|
+
const match = items.find((it) => String(it?.masterLabel ?? '').trim().toLowerCase() === wanted);
|
|
62
|
+
if (!match) {
|
|
63
|
+
process.stderr.write(`error: no template with masterLabel "${masterLabel}" found in ${templatesPath}\n`);
|
|
64
|
+
process.exit(3);
|
|
65
|
+
}
|
|
66
|
+
if (!match.agentScript || typeof match.agentScript !== 'string' || !match.agentScript.trim()) {
|
|
67
|
+
process.stderr.write(`error: template "${masterLabel}" (id=${match.id}) has no agentScript content\n`);
|
|
68
|
+
process.exit(3);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Escapes a value for use as a YAML double-quoted scalar — the agentScript's
|
|
72
|
+
// developer_name/agent_label fields are plain `"..."` strings, so a raw
|
|
73
|
+
// double-quote or backslash in a user-supplied label would otherwise break
|
|
74
|
+
// out of the scalar (e.g. label `Sam "Ops"` -> `agent_label: "Sam "Ops""`).
|
|
75
|
+
function yamlDoubleQuoteEscape(text) {
|
|
76
|
+
return String(text).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const decoded = decodeHtmlEntities(match.agentScript);
|
|
80
|
+
const developerNameEscaped = yamlDoubleQuoteEscape(developerName);
|
|
81
|
+
const labelEscaped = yamlDoubleQuoteEscape(label);
|
|
82
|
+
|
|
83
|
+
const substituted = decoded
|
|
84
|
+
.replace(/developer_name:\s*"[^"]*"/, `developer_name: "${developerNameEscaped}"`)
|
|
85
|
+
.replace(/agent_label:\s*"[^"]*"/, `agent_label: "${labelEscaped}"`);
|
|
86
|
+
|
|
87
|
+
if (!substituted.includes(`developer_name: "${developerNameEscaped}"`)) {
|
|
88
|
+
process.stderr.write('error: failed to substitute config.developer_name in the decoded agentScript\n');
|
|
89
|
+
process.exit(3);
|
|
90
|
+
}
|
|
91
|
+
if (!substituted.includes(`agent_label: "${labelEscaped}"`)) {
|
|
92
|
+
process.stderr.write('error: failed to substitute config.agent_label in the decoded agentScript\n');
|
|
93
|
+
process.exit(3);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const body = {
|
|
97
|
+
apiName: developerName,
|
|
98
|
+
label,
|
|
99
|
+
assets: [
|
|
100
|
+
{
|
|
101
|
+
resourceName: 'agentDefinition',
|
|
102
|
+
resourceType: 'agentDefinition',
|
|
103
|
+
sections: [],
|
|
104
|
+
resourceContent: substituted,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
writeFileSync(outPath, JSON.stringify(body), 'utf8');
|
|
110
|
+
process.stdout.write(outPath + '\n');
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic action-availability classifier for the NGA agent activation flow.
|
|
3
|
+
//
|
|
4
|
+
// The Fulfiller Agent Script references a fixed set of `svc_itsm_intelligence__*`
|
|
5
|
+
// invocable actions via its `source:` and `target:` fields (e.g.
|
|
6
|
+
// `source: "svc_itsm_intelligence__SummarizeIncident"`,
|
|
7
|
+
// `target: "generatePromptResponse://svc_itsm_intelligence__SummarizeIncident"`).
|
|
8
|
+
// A bundle version cannot activate when any of those actions is not surfaced by
|
|
9
|
+
// `/services/data/v67.0/actions/custom/generatePromptResponse` for the running
|
|
10
|
+
// user — the activate endpoint returns HTTP 200 with `{success:false,
|
|
11
|
+
// messages:[{... "does not exist"}]}` (silent-failure body). Diagnosing that
|
|
12
|
+
// after the fact is expensive; this classifier flags the mismatch BEFORE the
|
|
13
|
+
// activate call.
|
|
14
|
+
//
|
|
15
|
+
// Two fixed rules (A9 — no prose interpretation):
|
|
16
|
+
// 1. Enumerate every `svc_*__<ActionName>` token that appears inside a
|
|
17
|
+
// `source:` or `target:` line of the decoded Agent Script.
|
|
18
|
+
// 2. Enumerate every action name exposed by
|
|
19
|
+
// `/actions/custom/generatePromptResponse` — accepts both response shapes:
|
|
20
|
+
// (a) legacy `{ actions: [ { name, ... }, ... ] }`
|
|
21
|
+
// (b) newer `{ actions: { <fullyQualifiedName>: {...}, ... } }`
|
|
22
|
+
// Emit `missing = referenced - present`. If missing.length > 0 ⇒ verdict
|
|
23
|
+
// NOT-READY. On non-parseable inputs, verdict is CANNOT-CONFIRM (never block
|
|
24
|
+
// on unreadable state — surface it and let the workflow decide).
|
|
25
|
+
//
|
|
26
|
+
// Usage:
|
|
27
|
+
// node classify-action-availability.mjs \
|
|
28
|
+
// <agent-templates.json> <masterLabel> <generate-prompt-response.json>
|
|
29
|
+
//
|
|
30
|
+
// <agent-templates.json> — Phase-1 `agent-templates` capture; the matched
|
|
31
|
+
// template's `agentScript` is decoded exactly
|
|
32
|
+
// like build-create-body.mjs decodes it, then
|
|
33
|
+
// scanned for source:/target: action tokens.
|
|
34
|
+
// <masterLabel> — the display label of the target template
|
|
35
|
+
// (e.g. "IT Service Fulfiller").
|
|
36
|
+
// <generate-prompt-response.json> — GET
|
|
37
|
+
// /services/data/v67.0/actions/custom/generatePromptResponse
|
|
38
|
+
// response body.
|
|
39
|
+
//
|
|
40
|
+
// Emits a single JSON object to stdout:
|
|
41
|
+
// { referenced: [...],
|
|
42
|
+
// present: [...],
|
|
43
|
+
// missing: [...],
|
|
44
|
+
// verdict: "READY" | "NOT-READY" | "CANNOT-CONFIRM",
|
|
45
|
+
// reasons: [...] }
|
|
46
|
+
// Exit is always 0 on parseable inputs; the verdict is carried in the payload.
|
|
47
|
+
// A truly unreadable input (missing/malformed file) becomes CANNOT-CONFIRM +
|
|
48
|
+
// exit 0 so the caller can surface the raw CLI error rather than assuming
|
|
49
|
+
// READY.
|
|
50
|
+
|
|
51
|
+
import { readFileSync } from 'node:fs';
|
|
52
|
+
|
|
53
|
+
function readJson(path) {
|
|
54
|
+
try {
|
|
55
|
+
const text = readFileSync(path, 'utf8').trim();
|
|
56
|
+
if (!text) return { ok: false, reason: `empty file: ${path}` };
|
|
57
|
+
return { ok: true, data: JSON.parse(text) };
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return { ok: false, reason: `unreadable/invalid JSON at ${path}: ${e?.message ?? e}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Multi-pass HTML entity decode — matches build-create-body.mjs so both see the
|
|
64
|
+
// same script content. Connect API bodies can be double-encoded.
|
|
65
|
+
const NAMED = { amp: '&', quot: '"', apos: "'", lt: '<', gt: '>', '#39': "'", nbsp: ' ' };
|
|
66
|
+
function decodeEntitiesOnce(s) {
|
|
67
|
+
return s.replace(/&(#?\w+);/g, (m, ref) => {
|
|
68
|
+
if (ref.startsWith('#')) {
|
|
69
|
+
const n = ref[1] === 'x' || ref[1] === 'X' ? parseInt(ref.slice(2), 16) : parseInt(ref.slice(1), 10);
|
|
70
|
+
return Number.isFinite(n) ? String.fromCodePoint(n) : m;
|
|
71
|
+
}
|
|
72
|
+
return Object.prototype.hasOwnProperty.call(NAMED, ref) ? NAMED[ref] : m;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function decodeEntities(s) {
|
|
76
|
+
let prev = s, next = decodeEntitiesOnce(s);
|
|
77
|
+
while (next !== prev) { prev = next; next = decodeEntitiesOnce(next); }
|
|
78
|
+
return next;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function extractReferencedActions(scriptText) {
|
|
82
|
+
// Only lines whose YAML key is `source` or `target` are load-bearing — a
|
|
83
|
+
// namespace-prefixed action name mentioned inside a description, instruction,
|
|
84
|
+
// or comment is documentation, not a required invocation. Scanning globally
|
|
85
|
+
// would mis-flag such a mention as NOT-READY and block a valid activation.
|
|
86
|
+
//
|
|
87
|
+
// Agent Script YAML forms observed:
|
|
88
|
+
// source: "svc_itsm_intelligence__SummarizeIncident"
|
|
89
|
+
// source: svc_itsm_intelligence__SummarizeIncident
|
|
90
|
+
// target: "generatePromptResponse://svc_itsm_intelligence__SummarizeIncident"
|
|
91
|
+
// - source: "..."
|
|
92
|
+
//
|
|
93
|
+
// The key line regex tolerates leading whitespace, an optional YAML list dash,
|
|
94
|
+
// and both quoted and unquoted scalar values (single line). Block scalars
|
|
95
|
+
// (`source: |`) are extraordinarily rare for these fields and safely fall
|
|
96
|
+
// through to CANNOT-CONFIRM when nothing is extracted.
|
|
97
|
+
const referenced = new Set();
|
|
98
|
+
const tokenRe = /\bsvc_[a-z0-9_]+__[A-Za-z][A-Za-z0-9_]*\b/g;
|
|
99
|
+
const keyLineRe = /^[ \t]*-?[ \t]*(?:source|target)[ \t]*:[ \t]*(.*)$/;
|
|
100
|
+
for (const rawLine of scriptText.split(/\r?\n/)) {
|
|
101
|
+
const km = keyLineRe.exec(rawLine);
|
|
102
|
+
if (!km) continue;
|
|
103
|
+
const value = km[1];
|
|
104
|
+
for (const m of value.matchAll(tokenRe)) {
|
|
105
|
+
referenced.add(m[0]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...referenced].sort();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function extractPresentActions(actionsData) {
|
|
112
|
+
const present = new Set();
|
|
113
|
+
if (!actionsData || typeof actionsData !== 'object') return [];
|
|
114
|
+
const a = actionsData.actions;
|
|
115
|
+
if (Array.isArray(a)) {
|
|
116
|
+
// Shape (a): [ { name, ... }, ... ]
|
|
117
|
+
for (const it of a) {
|
|
118
|
+
if (it && typeof it === 'object') {
|
|
119
|
+
const nm = typeof it.name === 'string' ? it.name : null;
|
|
120
|
+
if (nm) present.add(nm);
|
|
121
|
+
const label = typeof it.label === 'string' ? it.label : null;
|
|
122
|
+
if (label && /svc_[a-z0-9_]+__/.test(label)) present.add(label);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if (a && typeof a === 'object') {
|
|
126
|
+
// Shape (b): { <name>: {...}, ... }
|
|
127
|
+
for (const key of Object.keys(a)) present.add(key);
|
|
128
|
+
}
|
|
129
|
+
return [...present].sort();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function findTemplateScript(agentTemplatesData, masterLabel) {
|
|
133
|
+
if (!agentTemplatesData || typeof agentTemplatesData !== 'object') return null;
|
|
134
|
+
const items = Array.isArray(agentTemplatesData.data) ? agentTemplatesData.data : null;
|
|
135
|
+
if (!items) return null;
|
|
136
|
+
const wanted = String(masterLabel).trim().toLowerCase();
|
|
137
|
+
const match = items.find((it) => String(it?.masterLabel ?? '').trim().toLowerCase() === wanted);
|
|
138
|
+
if (!match || typeof match.agentScript !== 'string' || !match.agentScript.trim()) return null;
|
|
139
|
+
return decodeEntities(match.agentScript);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const [templatesPath, rawLabel, actionsPath] = process.argv.slice(2);
|
|
143
|
+
if (!templatesPath || !rawLabel || !actionsPath) {
|
|
144
|
+
process.stderr.write('usage: node classify-action-availability.mjs <agent-templates.json> <masterLabel> <generate-prompt-response.json>\n');
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const templatesRead = readJson(templatesPath);
|
|
149
|
+
const actionsRead = readJson(actionsPath);
|
|
150
|
+
|
|
151
|
+
if (!templatesRead.ok) {
|
|
152
|
+
process.stdout.write(JSON.stringify({
|
|
153
|
+
referenced: [], present: [], missing: [],
|
|
154
|
+
verdict: 'CANNOT-CONFIRM',
|
|
155
|
+
reasons: [templatesRead.reason],
|
|
156
|
+
}, null, 2) + '\n');
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
if (!actionsRead.ok) {
|
|
160
|
+
process.stdout.write(JSON.stringify({
|
|
161
|
+
referenced: [], present: [], missing: [],
|
|
162
|
+
verdict: 'CANNOT-CONFIRM',
|
|
163
|
+
reasons: [actionsRead.reason],
|
|
164
|
+
}, null, 2) + '\n');
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const scriptText = findTemplateScript(templatesRead.data, rawLabel);
|
|
169
|
+
if (!scriptText) {
|
|
170
|
+
process.stdout.write(JSON.stringify({
|
|
171
|
+
referenced: [], present: [], missing: [],
|
|
172
|
+
verdict: 'CANNOT-CONFIRM',
|
|
173
|
+
reasons: [`No template with masterLabel "${rawLabel}" carrying a non-empty agentScript in agent-templates response.`],
|
|
174
|
+
}, null, 2) + '\n');
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const referenced = extractReferencedActions(scriptText);
|
|
179
|
+
const present = extractPresentActions(actionsRead.data);
|
|
180
|
+
const presentSet = new Set(present);
|
|
181
|
+
const missing = referenced.filter((r) => !presentSet.has(r));
|
|
182
|
+
|
|
183
|
+
let verdict, reasons;
|
|
184
|
+
if (referenced.length === 0) {
|
|
185
|
+
verdict = 'CANNOT-CONFIRM';
|
|
186
|
+
reasons = ['No svc_*__ action tokens were found in the decoded Agent Script — either the script does not use namespace-prefixed invocables or decoding failed. Surface and continue with caution.'];
|
|
187
|
+
} else if (missing.length === 0) {
|
|
188
|
+
verdict = 'READY';
|
|
189
|
+
reasons = [`All ${referenced.length} template-referenced invocable action(s) are surfaced by /actions/custom/generatePromptResponse for the running user.`];
|
|
190
|
+
} else {
|
|
191
|
+
verdict = 'NOT-READY';
|
|
192
|
+
reasons = [
|
|
193
|
+
`${missing.length} of ${referenced.length} template-referenced invocable action(s) are NOT surfaced by /actions/custom/generatePromptResponse for the running user.`,
|
|
194
|
+
`Missing: ${missing.join(', ')}`,
|
|
195
|
+
'Activate will succeed at the HTTP level but return {success:false, messages:[...]} — the agent will not be usable. Offer to hand off to service-itsm-agentic-setup-itsm-agentforce-permset-assign, which handles both the permset-not-assigned case and the package-not-installed case.',
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
process.stdout.write(JSON.stringify({
|
|
200
|
+
referenced, present, missing, verdict, reasons,
|
|
201
|
+
}, null, 2) + '\n');
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic post-activate result classifier for the NGA agent flow.
|
|
3
|
+
//
|
|
4
|
+
// The activate endpoints return **HTTP 200** on both success AND on structural
|
|
5
|
+
// activation failures like "invocable action not found". The failure body is:
|
|
6
|
+
// { success: false, isActivated: false, messages: [ { code, ... } ] }
|
|
7
|
+
// or an array-wrapped variant. Nothing in the CLI exit code tells the workflow
|
|
8
|
+
// apart from a real success; parsing the body is the only signal.
|
|
9
|
+
//
|
|
10
|
+
// This helper is the single choke point (A9 — no prose interpretation) for that
|
|
11
|
+
// decision, shared by:
|
|
12
|
+
// - Phase 6 (create path): POST /nextgen-authoring/bundle-versions/<id>/activate
|
|
13
|
+
// - Phase 2b (reactivation): POST /connect/bot-versions/<id>/activation
|
|
14
|
+
//
|
|
15
|
+
// Rules:
|
|
16
|
+
// - Empty body → PASS (documented Phase-6 success shape)
|
|
17
|
+
// - {success:true} → PASS
|
|
18
|
+
// - {success:false} → FAIL (surface `messages[]` verbatim)
|
|
19
|
+
// - unparseable body → CANNOT-CONFIRM (surface the raw file + let workflow
|
|
20
|
+
// fall back to the Phase-7 verify SOQL)
|
|
21
|
+
//
|
|
22
|
+
// Usage:
|
|
23
|
+
// node classify-activate-result.mjs <activate-response.json>
|
|
24
|
+
//
|
|
25
|
+
// Emits a single JSON object to stdout:
|
|
26
|
+
// { verdict: "PASS" | "FAIL" | "CANNOT-CONFIRM",
|
|
27
|
+
// success, isActivated, messages: [...], reasons: [...] }
|
|
28
|
+
// Exit is always 0 on a readable file; the verdict is carried in the payload.
|
|
29
|
+
|
|
30
|
+
import { readFileSync } from 'node:fs';
|
|
31
|
+
|
|
32
|
+
const [respPath] = process.argv.slice(2);
|
|
33
|
+
if (!respPath) {
|
|
34
|
+
process.stderr.write('usage: node classify-activate-result.mjs <activate-response.json>\n');
|
|
35
|
+
process.exit(2);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let text;
|
|
39
|
+
try { text = readFileSync(respPath, 'utf8'); }
|
|
40
|
+
catch (e) {
|
|
41
|
+
process.stdout.write(JSON.stringify({
|
|
42
|
+
verdict: 'CANNOT-CONFIRM',
|
|
43
|
+
success: null, isActivated: null, messages: [],
|
|
44
|
+
reasons: [`unreadable file: ${respPath} (${e?.message ?? e})`],
|
|
45
|
+
}, null, 2) + '\n');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const trimmed = text.trim();
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
// Documented Phase-6 success — activate returns empty body on success.
|
|
52
|
+
process.stdout.write(JSON.stringify({
|
|
53
|
+
verdict: 'PASS',
|
|
54
|
+
success: true, isActivated: null, messages: [],
|
|
55
|
+
reasons: ['Empty response body — documented success shape for the activate endpoint. Confirm via the Phase-7 BotDefinition verify read.'],
|
|
56
|
+
}, null, 2) + '\n');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let parsed;
|
|
61
|
+
try { parsed = JSON.parse(trimmed); }
|
|
62
|
+
catch {
|
|
63
|
+
process.stdout.write(JSON.stringify({
|
|
64
|
+
verdict: 'CANNOT-CONFIRM',
|
|
65
|
+
success: null, isActivated: null, messages: [],
|
|
66
|
+
reasons: [`Activate response is non-empty but not JSON — surface it verbatim and rely on the Phase-7 verify read: ${trimmed.slice(0, 500)}`],
|
|
67
|
+
}, null, 2) + '\n');
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Some Connect endpoints wrap the body in a single-element array.
|
|
72
|
+
const body = Array.isArray(parsed) && parsed.length === 1 && parsed[0] && typeof parsed[0] === 'object'
|
|
73
|
+
? parsed[0]
|
|
74
|
+
: parsed;
|
|
75
|
+
|
|
76
|
+
// A Connect error array (each { errorCode, message }) also arrives here on 4xx.
|
|
77
|
+
if (Array.isArray(body)) {
|
|
78
|
+
const errs = body.filter((e) => e && (e.errorCode || e.message));
|
|
79
|
+
const asMsgs = errs.map((e) => ({ code: e.errorCode ?? 'ERROR', message: e.message ?? '' }));
|
|
80
|
+
process.stdout.write(JSON.stringify({
|
|
81
|
+
verdict: 'FAIL',
|
|
82
|
+
success: false, isActivated: false, messages: asMsgs,
|
|
83
|
+
reasons: ['Activate returned a Connect error array — surface every message verbatim.'],
|
|
84
|
+
}, null, 2) + '\n');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!body || typeof body !== 'object') {
|
|
89
|
+
process.stdout.write(JSON.stringify({
|
|
90
|
+
verdict: 'CANNOT-CONFIRM',
|
|
91
|
+
success: null, isActivated: null, messages: [],
|
|
92
|
+
reasons: [`Activate response is JSON but not an object — surface it and rely on the Phase-7 verify read: ${trimmed.slice(0, 500)}`],
|
|
93
|
+
}, null, 2) + '\n');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const success = typeof body.success === 'boolean' ? body.success : null;
|
|
98
|
+
const isActivated = typeof body.isActivated === 'boolean' ? body.isActivated : null;
|
|
99
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
100
|
+
|
|
101
|
+
if (success === true || isActivated === true) {
|
|
102
|
+
process.stdout.write(JSON.stringify({
|
|
103
|
+
verdict: 'PASS',
|
|
104
|
+
success: success ?? true, isActivated: isActivated ?? null, messages,
|
|
105
|
+
reasons: ['Activate response indicates success. Confirm via the Phase-7 BotDefinition verify read.'],
|
|
106
|
+
}, null, 2) + '\n');
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (success === false || isActivated === false) {
|
|
111
|
+
const msgSnippet = messages
|
|
112
|
+
.map((m) => `${m?.code ?? m?.errorCode ?? 'ERROR'}: ${m?.message ?? ''}`.trim())
|
|
113
|
+
.join(' | ')
|
|
114
|
+
.slice(0, 800);
|
|
115
|
+
const missingAction = /does not exist|not found|no such action|invocable action/i.test(msgSnippet);
|
|
116
|
+
const reasons = [
|
|
117
|
+
`Activate returned success=false — the agent was NOT activated. Verbatim: ${msgSnippet}`,
|
|
118
|
+
];
|
|
119
|
+
if (missingAction) {
|
|
120
|
+
reasons.push('At least one referenced invocable action is missing on this org. Prefer running scripts/classify-action-availability.mjs BEFORE activate (Phase 2c) to catch this without a wasted create/publish; if it slipped through, hand off to service-itsm-agentic-setup-itsm-agentforce-permset-assign.');
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write(JSON.stringify({
|
|
123
|
+
verdict: 'FAIL',
|
|
124
|
+
success: false, isActivated: isActivated ?? false, messages,
|
|
125
|
+
reasons,
|
|
126
|
+
}, null, 2) + '\n');
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Body present but no success/isActivated fields — surface + rely on verify.
|
|
131
|
+
process.stdout.write(JSON.stringify({
|
|
132
|
+
verdict: 'CANNOT-CONFIRM',
|
|
133
|
+
success: null, isActivated: null, messages,
|
|
134
|
+
reasons: ['Activate body had no success/isActivated field — surface it and rely on the Phase-7 verify read.'],
|
|
135
|
+
}, null, 2) + '\n');
|