@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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic idempotency / verification classifier for the Fulfiller-agent create flow.
|
|
3
|
+
//
|
|
4
|
+
// Idempotency for this flow is keyed PRIMARILY on the TEMPLATE's own
|
|
5
|
+
// `botDefinitionId` — the platform's authoritative link from an `agent-templates`
|
|
6
|
+
// row to the live `BotDefinition` it was instantiated into — and FALLS BACK to a
|
|
7
|
+
// `DeveloperName`-keyed read when that row carries no `botDefinitionId`.
|
|
8
|
+
// If the Fulfiller template was already instantiated under a DeveloperName that
|
|
9
|
+
// differs from this skill's default collected `IT_Service_Fulfiller_Agent`, a
|
|
10
|
+
// DeveloperName-only read false-negatives (exists:false) and the create then
|
|
11
|
+
// collides on `apiName` with `DUPLICATE_VALUE` — hence botDefinitionId is the
|
|
12
|
+
// primary key. But botDefinitionId is back-filled onto the template row ONLY for
|
|
13
|
+
// platform-pre-provisioned agents (the ITSM create path never stamps
|
|
14
|
+
// `templateName`, so the platform never joins the agent-templates row to its
|
|
15
|
+
// BotDefinition), and the Fulfiller is never pre-provisioned — so its template
|
|
16
|
+
// `botDefinitionId` is null on the first run and every run after, and the
|
|
17
|
+
// DeveloperName-keyed read is the guard that actually protects this path, used as
|
|
18
|
+
// the FALLBACK below. (Preflight emits `template.botDefinitionId`; the workflow
|
|
19
|
+
// passes it as the primary key and the collected developerName as the fallback key.)
|
|
20
|
+
//
|
|
21
|
+
// This classifier consumes the raw JSON of a `sf data query ... --json` read of
|
|
22
|
+
// BotDefinition (BY Id primarily, BY DeveloperName on the fallback) and decides
|
|
23
|
+
// whether the agent already exists — and if so, whether its latest BotVersion is
|
|
24
|
+
// Inactive (so the workflow can offer to activate it instead of creating a
|
|
25
|
+
// duplicate). This is the deterministic decision that gates whether the write is
|
|
26
|
+
// skipped — it MUST NOT be interpreted by the model in prose (authoring standard A9).
|
|
27
|
+
//
|
|
28
|
+
// Usage:
|
|
29
|
+
// node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName]
|
|
30
|
+
//
|
|
31
|
+
// When <botDefinitionId> is empty, `-`, or the literal `null`/`undefined` (the
|
|
32
|
+
// matched template carries no botDefinitionId), the classifier FALLS BACK to
|
|
33
|
+
// keying on <developerName>, and <bot-query.json> must be a DeveloperName-keyed
|
|
34
|
+
// BotDefinition read: self-created agents are never back-filled with a
|
|
35
|
+
// botDefinitionId (the ITSM create path doesn't stamp `templateName`, so the
|
|
36
|
+
// platform never joins the agent-templates row to its BotDefinition), so
|
|
37
|
+
// DeveloperName is the only idempotency guard left for them. Only when BOTH
|
|
38
|
+
// <botDefinitionId> and <developerName> are absent is exists:false emitted
|
|
39
|
+
// without reading a query file. Otherwise <bot-query.json> is a FILE PATH to the
|
|
40
|
+
// stdout captured from:
|
|
41
|
+
// sf data query -q "SELECT Id,DeveloperName,MasterLabel,
|
|
42
|
+
// (SELECT Id,Status FROM BotVersions ORDER BY VersionNumber DESC LIMIT 1)
|
|
43
|
+
// FROM BotDefinition WHERE Id='<botDefinitionId>' OR DeveloperName='<developerName>'" \
|
|
44
|
+
// --target-org <org> --json > file.json
|
|
45
|
+
// (The present-id read ORs in DeveloperName so a DANGLING template→BotDefinition
|
|
46
|
+
// link — a botDefinitionId whose target BotDefinition was since deleted — still
|
|
47
|
+
// surfaces the live same-name agent: the classifier tries the Id first, then
|
|
48
|
+
// falls back to the name on the SAME records. The pure FALLBACK read, taken when
|
|
49
|
+
// the template row carries no botDefinitionId at all, keys on
|
|
50
|
+
// `DeveloperName='<developerName>'` only; everything else is identical.
|
|
51
|
+
// `sf data query --json` wraps results in a `.result.records[]` envelope. The
|
|
52
|
+
// BotVersions subquery is required so this classifier can see the latest
|
|
53
|
+
// version's Status — a query without it leaves latestVersionStatus null and
|
|
54
|
+
// needsActivation false, which silently disables the reactivation path.)
|
|
55
|
+
//
|
|
56
|
+
// Emits a single JSON object to stdout:
|
|
57
|
+
// { exists, count, matchedBy, agentId, botDefinitionId, developerName, latestVersionId, latestVersionStatus, needsActivation }
|
|
58
|
+
// where exists is true|false, developerName is the ACTUAL DeveloperName read
|
|
59
|
+
// from the matched record (NOT the collected guess `IT_Service_Fulfiller_Agent`)
|
|
60
|
+
// — so the report surfaces the live agent's real identity;
|
|
61
|
+
// latestVersionStatus is "Active"|"Inactive"|null (null means no BotVersion
|
|
62
|
+
// child row was returned), and needsActivation is true only when exists is true
|
|
63
|
+
// AND latestVersionStatus is "Inactive" — the signal to offer activating the
|
|
64
|
+
// existing version instead of creating a new agent. `matchedBy` is
|
|
65
|
+
// "botDefinitionId" | "developerName" | null, recording which key hit. Exit code
|
|
66
|
+
// is always 0 on a parseable body (or when neither key is supplied); the verdict
|
|
67
|
+
// is carried in the payload. On an unparseable/failed query it exits 3 so the
|
|
68
|
+
// workflow surfaces the raw error rather than assuming NOT-EXISTS.
|
|
69
|
+
|
|
70
|
+
import { readFileSync } from 'node:fs';
|
|
71
|
+
|
|
72
|
+
const [queryPath, rawBotDefinitionId, rawDeveloperName] = process.argv.slice(2);
|
|
73
|
+
if (!queryPath) {
|
|
74
|
+
process.stderr.write('usage: node classify-agent-existence.mjs <bot-query.json> <botDefinitionId> [developerName]\n');
|
|
75
|
+
process.exit(2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Salesforce Ids compare on their case-sensitive 15-char base (the trailing 3
|
|
79
|
+
// chars are a case-normalizing checksum), so a 15-char and an 18-char form of
|
|
80
|
+
// the same record compare equal on the first 15 chars.
|
|
81
|
+
function idKey(v) {
|
|
82
|
+
return String(v ?? '').trim().slice(0, 15);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function notSet(v) {
|
|
86
|
+
const s = String(v ?? '').trim().toLowerCase();
|
|
87
|
+
return s === '' || s === '-' || s === 'null' || s === 'undefined';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const botDefinitionId = String(rawBotDefinitionId ?? '').trim();
|
|
91
|
+
const developerName = String(rawDeveloperName ?? '').trim();
|
|
92
|
+
const noId = notSet(botDefinitionId);
|
|
93
|
+
const noName = notSet(developerName);
|
|
94
|
+
|
|
95
|
+
const NOT_EXISTS = {
|
|
96
|
+
exists: false,
|
|
97
|
+
count: 0,
|
|
98
|
+
matchedBy: null,
|
|
99
|
+
agentId: null,
|
|
100
|
+
botDefinitionId: null,
|
|
101
|
+
developerName: null,
|
|
102
|
+
latestVersionId: null,
|
|
103
|
+
latestVersionStatus: null,
|
|
104
|
+
needsActivation: false,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Nothing to key on — no botDefinitionId AND no developerName fallback. There is
|
|
108
|
+
// no way to detect an existing agent, so this is the create path. (The workflow
|
|
109
|
+
// should always pass the collected developerName so the fallback below can run;
|
|
110
|
+
// this bare branch only fires when neither key is supplied.)
|
|
111
|
+
if (noId && noName) {
|
|
112
|
+
process.stdout.write(JSON.stringify(NOT_EXISTS, null, 2) + '\n');
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let data;
|
|
117
|
+
try {
|
|
118
|
+
const text = readFileSync(queryPath, 'utf8').trim();
|
|
119
|
+
data = text ? JSON.parse(text) : null;
|
|
120
|
+
} catch {
|
|
121
|
+
process.stderr.write(`error: could not read/parse ${queryPath}\n`);
|
|
122
|
+
process.exit(3);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A failed `sf data query --json` has status !== 0 (e.g. malformed SOQL, auth error).
|
|
126
|
+
// Do NOT treat that as "agent does not exist" — surface it.
|
|
127
|
+
if (!data || (typeof data.status === 'number' && data.status !== 0) || data.result === undefined) {
|
|
128
|
+
process.stderr.write('error: query did not return a results envelope; surface the raw CLI error and stop\n');
|
|
129
|
+
process.exit(3);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const records = Array.isArray(data.result?.records) ? data.result.records : [];
|
|
133
|
+
|
|
134
|
+
// PRIMARY key: the template's `botDefinitionId` (the platform's authoritative
|
|
135
|
+
// template→BotDefinition link) — catches a pre-provisioned agent whose live
|
|
136
|
+
// DeveloperName differs from this skill's collected guess. For the Fulfiller this
|
|
137
|
+
// branch is defensive: the template `botDefinitionId` is null on every run (the
|
|
138
|
+
// Fulfiller is never pre-provisioned and this skill's create path never stamps
|
|
139
|
+
// `templateName`), so in practice the DeveloperName fallback below is what fires.
|
|
140
|
+
// The matching LOGIC here is kept identical to the Employee classifier on
|
|
141
|
+
// purpose — one shared matching contract, so the two can't drift and a future
|
|
142
|
+
// platform change that DOES populate the Fulfiller's botDefinitionId is handled
|
|
143
|
+
// correctly rather than silently mismatched; see that script's header for the
|
|
144
|
+
// pre-provisioned rationale it exercises.
|
|
145
|
+
// FALLBACK: the collected DeveloperName. Self-created agents are NOT
|
|
146
|
+
// back-filled — the ITSM create path never stamps `templateName`, so the
|
|
147
|
+
// platform never joins the agent-templates row to its BotDefinition and
|
|
148
|
+
// `botDefinitionId` stays null on every later read — so DeveloperName is their
|
|
149
|
+
// only guard. We ALSO fall back to DeveloperName when a PRESENT botDefinitionId
|
|
150
|
+
// matches nothing (a dangling link whose target BotDefinition was deleted): the
|
|
151
|
+
// present-id Phase-2 SOQL reads `WHERE Id=... OR DeveloperName=...`, so a stale
|
|
152
|
+
// link still surfaces the live same-name agent instead of slipping through to the
|
|
153
|
+
// create path and colliding with DUPLICATE_VALUE. Try the Id first, then the
|
|
154
|
+
// name; each filter is self-checking on top of the SOQL WHERE so a wrong-keyed
|
|
155
|
+
// read can't false-positive.
|
|
156
|
+
let matches = [];
|
|
157
|
+
let matchedBy = null;
|
|
158
|
+
if (!noId) {
|
|
159
|
+
const wantedIdKey = idKey(botDefinitionId);
|
|
160
|
+
matches = records.filter((r) => idKey(r?.Id) === wantedIdKey);
|
|
161
|
+
if (matches.length) matchedBy = 'botDefinitionId';
|
|
162
|
+
}
|
|
163
|
+
if (!matches.length && !noName) {
|
|
164
|
+
// DeveloperName uniqueness is case-insensitive; compare accordingly.
|
|
165
|
+
const wantedName = developerName.toLowerCase();
|
|
166
|
+
matches = records.filter((r) => String(r?.DeveloperName ?? '').trim().toLowerCase() === wantedName);
|
|
167
|
+
if (matches.length) matchedBy = 'developerName';
|
|
168
|
+
}
|
|
169
|
+
const exists = matches.length > 0;
|
|
170
|
+
|
|
171
|
+
// The BotVersions child subquery (if present in the SOQL) surfaces the latest
|
|
172
|
+
// version's Status so we can tell "already created and active" apart from
|
|
173
|
+
// "already created but inactive" — the latter should offer reactivation
|
|
174
|
+
// instead of a silent ALREADY-CREATED skip. The actual BotVersion status is
|
|
175
|
+
// authoritative over the template's `isActivated` flag.
|
|
176
|
+
const latestVersion = exists ? matches[0]?.BotVersions?.records?.[0] ?? null : null;
|
|
177
|
+
const latestVersionStatus = latestVersion?.Status ?? null;
|
|
178
|
+
const needsActivation = exists && latestVersionStatus === 'Inactive';
|
|
179
|
+
|
|
180
|
+
process.stdout.write(JSON.stringify({
|
|
181
|
+
exists,
|
|
182
|
+
count: matches.length,
|
|
183
|
+
// Which key matched — `botDefinitionId` (primary) or `developerName` (the
|
|
184
|
+
// fallback for self-created agents). null when nothing matched.
|
|
185
|
+
matchedBy: exists ? matchedBy : null,
|
|
186
|
+
agentId: exists ? (matches[0].Id ?? null) : null,
|
|
187
|
+
// On a DeveloperName-fallback hit this surfaces the live BotDefinition Id the
|
|
188
|
+
// template row was missing; on the Id path it echoes the queried id.
|
|
189
|
+
botDefinitionId: exists ? (matches[0].Id ?? null) : (noId ? null : botDefinitionId),
|
|
190
|
+
developerName: exists ? (matches[0].DeveloperName ?? null) : null,
|
|
191
|
+
latestVersionId: latestVersion?.Id ?? null,
|
|
192
|
+
latestVersionStatus,
|
|
193
|
+
needsActivation,
|
|
194
|
+
}, null, 2) + '\n');
|
package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic preflight classifier for the Fulfiller-agent create flow.
|
|
3
|
+
//
|
|
4
|
+
// Two fixed JSON-parsing / decision rules that must not live in model prose
|
|
5
|
+
// (authoring standard A9):
|
|
6
|
+
// 1. Studio access: read `hasAccess` from the agentforce-studio/access/Agents
|
|
7
|
+
// body → PASS (true) / FAIL (false) / CANNOT-CONFIRM (404 / not wired).
|
|
8
|
+
// 2. Template provisioning: find the agent-templates item whose masterLabel
|
|
9
|
+
// matches the target ("IT Service Fulfiller"), confirm it carries a non-empty
|
|
10
|
+
// `agentScript` (the Agent Script YAML fed into the NGA bundle create) → PRESENT
|
|
11
|
+
// (+hasAgentScript) / ABSENT (empty data[] or no match) / CANNOT-CONFIRM
|
|
12
|
+
// (404 / not wired).
|
|
13
|
+
//
|
|
14
|
+
// Usage:
|
|
15
|
+
// node classify-preflight.mjs <studio-access.json> <agent-templates.json> <masterLabel>
|
|
16
|
+
//
|
|
17
|
+
// Both file args are paths to raw `sf api request rest` stdout captures; the
|
|
18
|
+
// files may hold a Connect error array or be empty when the CLI exited non-zero.
|
|
19
|
+
// <masterLabel> is the template's display label (default "IT Service Fulfiller").
|
|
20
|
+
//
|
|
21
|
+
// Emits a single JSON object to stdout:
|
|
22
|
+
// { studio: { hasAccess, signal, reason },
|
|
23
|
+
// template: { present, id, hasAgentScript, botDefinitionId, signal, reason },
|
|
24
|
+
// verdict, reasons: [...] }
|
|
25
|
+
// `botDefinitionId` is copied from the matched agent-templates row and passed to
|
|
26
|
+
// the Phase-2 idempotency read as the PRIMARY key (the platform's authoritative
|
|
27
|
+
// template→BotDefinition link); the collected developerName is the FALLBACK key
|
|
28
|
+
// there. See classify-agent-existence.mjs for why. (The template row's
|
|
29
|
+
// `isInstalled`/`isActivated` flags are NOT emitted: they ride the same
|
|
30
|
+
// AgentTemplate join as botDefinitionId, so for agents this skill creates — which
|
|
31
|
+
// never stamp `templateName` — they read false even after the agent exists, and
|
|
32
|
+
// nothing downstream consumes them.)
|
|
33
|
+
// where signal is PASS | FAIL | CANNOT-CONFIRM | ERROR and verdict is
|
|
34
|
+
// READY | NOT-READY | CANNOT-CONFIRM | ERROR. Exit is always 0 on usable args.
|
|
35
|
+
// ERROR (studio only) means the Studio-access read returned a parseable but
|
|
36
|
+
// non-404 error body (401/403/unexpected) — that is a failed prerequisite
|
|
37
|
+
// read, not an unwired gate, so it must stop the workflow rather than fall
|
|
38
|
+
// through to a CANNOT-CONFIRM that a present template can outrun to READY.
|
|
39
|
+
|
|
40
|
+
import { readFileSync } from 'node:fs';
|
|
41
|
+
|
|
42
|
+
function readJson(path) {
|
|
43
|
+
try {
|
|
44
|
+
const text = readFileSync(path, 'utf8').trim();
|
|
45
|
+
if (!text) return { ok: false };
|
|
46
|
+
return { ok: true, data: JSON.parse(text) };
|
|
47
|
+
} catch {
|
|
48
|
+
return { ok: false };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function asErrorArray(data) {
|
|
53
|
+
if (Array.isArray(data)) return data.filter((e) => e && (e.errorCode || e.message));
|
|
54
|
+
if (data && (data.errorCode || data.message)) return [data];
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isNotFoundError(data) {
|
|
59
|
+
return asErrorArray(data).some((e) => {
|
|
60
|
+
const code = String(e?.errorCode ?? '').toUpperCase();
|
|
61
|
+
const msg = String(e?.message ?? '').toUpperCase();
|
|
62
|
+
return code.includes('NOT_FOUND') || code.includes('NOT_EXIST') ||
|
|
63
|
+
msg.includes('NOT FOUND') || msg.includes('DOES NOT EXIST') ||
|
|
64
|
+
msg.includes('COULD NOT BE FOUND');
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function errorSnippet(errs) {
|
|
69
|
+
return errs.map((e) => `${e.errorCode ?? 'ERROR'}: ${e.message ?? ''}`.trim()).join(' | ').slice(0, 500);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function classifyStudio(res) {
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
return { hasAccess: null, signal: 'CANNOT-CONFIRM', reason: 'Studio access — no readable body (endpoint not wired / empty). A successful createAgent is authoritative.' };
|
|
75
|
+
}
|
|
76
|
+
const d = res.data;
|
|
77
|
+
const errs = asErrorArray(d);
|
|
78
|
+
if (errs.length) {
|
|
79
|
+
if (isNotFoundError(d)) return { hasAccess: null, signal: 'CANNOT-CONFIRM', reason: 'Studio access → 404 (access-check gate not wired on this org). Treat as cannot-confirm; a successful createAgent is authoritative.' };
|
|
80
|
+
return { hasAccess: null, signal: 'ERROR', reason: `Studio access read failed: ${errorSnippet(errs)} — surface the raw response and stop; do not treat as an unwired gate.` };
|
|
81
|
+
}
|
|
82
|
+
if (d?.hasAccess === true) return { hasAccess: true, signal: 'PASS', reason: 'Studio access → hasAccess=true.' };
|
|
83
|
+
if (d?.hasAccess === false) return { hasAccess: false, signal: 'FAIL', reason: 'Studio access → hasAccess=false — offer the prerequisite readiness hand-off before any write.' };
|
|
84
|
+
return { hasAccess: null, signal: 'CANNOT-CONFIRM', reason: 'Studio access body had no boolean hasAccess field.' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function classifyTemplate(res, masterLabel) {
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
return { present: false, id: null, hasAgentScript: false, signal: 'CANNOT-CONFIRM', reason: 'agent-templates — no readable body (endpoint not wired / empty).' };
|
|
90
|
+
}
|
|
91
|
+
const d = res.data;
|
|
92
|
+
if (asErrorArray(d).length) {
|
|
93
|
+
if (isNotFoundError(d)) return { present: false, id: null, hasAgentScript: false, signal: 'CANNOT-CONFIRM', reason: 'agent-templates → 404 (ITSM Connect namespace not provisioned). Hand off to the readiness check.' };
|
|
94
|
+
return { present: false, id: null, hasAgentScript: false, signal: 'CANNOT-CONFIRM', reason: 'agent-templates read returned an error body — surface it.' };
|
|
95
|
+
}
|
|
96
|
+
const items = Array.isArray(d?.data) ? d.data : null;
|
|
97
|
+
if (!items) return { present: false, id: null, hasAgentScript: false, signal: 'CANNOT-CONFIRM', reason: 'agent-templates had no data[] array (unexpected shape).' };
|
|
98
|
+
const wanted = String(masterLabel).trim().toLowerCase();
|
|
99
|
+
const match = items.find((it) => String(it?.masterLabel ?? '').trim().toLowerCase() === wanted);
|
|
100
|
+
if (!match) {
|
|
101
|
+
return {
|
|
102
|
+
present: false, id: null, hasAgentScript: false, signal: 'FAIL',
|
|
103
|
+
reason: items.length === 0
|
|
104
|
+
? 'agent-templates returned an empty data[] — wrong agentType or the org is not ITSM-provisioned; hand off to the readiness check.'
|
|
105
|
+
: `No template with masterLabel "${masterLabel}" in data[] (got: ${items.map((i) => i?.masterLabel).filter(Boolean).join(', ') || 'none'}). Hand off to the readiness check.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Idempotency identity carried by the matched row — the platform's own link
|
|
109
|
+
// from this template to the BotDefinition it was instantiated into. A populated
|
|
110
|
+
// botDefinitionId is the reliable idempotency signal for PRE-PROVISIONED agents
|
|
111
|
+
// (see classify-agent-existence.mjs): it means the agent already exists
|
|
112
|
+
// regardless of what DeveloperName this skill would have guessed. Agents this
|
|
113
|
+
// skill creates never stamp `templateName`, so their row's botDefinitionId stays
|
|
114
|
+
// null — the Phase-2 read falls back to a DeveloperName key for that case.
|
|
115
|
+
const botDefinitionId = typeof match.botDefinitionId === 'string' && match.botDefinitionId.trim() ? match.botDefinitionId : null;
|
|
116
|
+
if (!match.agentScript || typeof match.agentScript !== 'string' || !match.agentScript.trim()) {
|
|
117
|
+
return { present: true, id: match.id ?? null, hasAgentScript: false, botDefinitionId, signal: 'CANNOT-CONFIRM', reason: `Matched "${masterLabel}" (id=${match.id}) but it had no agentScript field — the NGA bundle create needs the template's Agent Script content.` };
|
|
118
|
+
}
|
|
119
|
+
return { present: true, id: match.id ?? null, hasAgentScript: true, botDefinitionId, signal: 'PASS', reason: `Template "${masterLabel}" present (id=${match.id}) with a non-empty agentScript${botDefinitionId ? `; already instantiated (botDefinitionId=${botDefinitionId})` : '; not yet instantiated (no botDefinitionId — the Phase-2 read falls back to DeveloperName)'} — feed it to scripts/build-create-body.mjs for the NGA bundle create.` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const [studioPath, templatePath, rawLabel] = process.argv.slice(2);
|
|
123
|
+
if (!studioPath || !templatePath) {
|
|
124
|
+
process.stderr.write('usage: node classify-preflight.mjs <studio-access.json> <agent-templates.json> [masterLabel]\n');
|
|
125
|
+
process.exit(2);
|
|
126
|
+
}
|
|
127
|
+
const masterLabel = rawLabel && rawLabel.trim() ? rawLabel : 'IT Service Fulfiller';
|
|
128
|
+
|
|
129
|
+
const studio = classifyStudio(readJson(studioPath));
|
|
130
|
+
const template = classifyTemplate(readJson(templatePath), masterLabel);
|
|
131
|
+
|
|
132
|
+
// Verdict: the template MUST be present to create. Studio FALSE blocks (hand-off);
|
|
133
|
+
// Studio ERROR (non-404 failed read, e.g. 401/403) blocks — surface it, stop.
|
|
134
|
+
// Studio CANNOT-CONFIRM (confirmed 404 gate) does not block — createAgent is authoritative.
|
|
135
|
+
let verdict;
|
|
136
|
+
if (studio.signal === 'ERROR') verdict = 'ERROR';
|
|
137
|
+
else if (studio.signal === 'FAIL' || template.signal === 'FAIL') verdict = 'NOT-READY';
|
|
138
|
+
else if (template.signal === 'PASS') verdict = 'READY'; // studio PASS, or unconfirmed via a documented 404 gate — template present is enough to attempt
|
|
139
|
+
else verdict = 'CANNOT-CONFIRM';
|
|
140
|
+
|
|
141
|
+
process.stdout.write(JSON.stringify({
|
|
142
|
+
studio: { hasAccess: studio.hasAccess, signal: studio.signal, reason: studio.reason },
|
|
143
|
+
template: {
|
|
144
|
+
present: template.present,
|
|
145
|
+
id: template.id,
|
|
146
|
+
hasAgentScript: template.hasAgentScript,
|
|
147
|
+
// Idempotency identity (from the matched agent-templates row): the Phase-2
|
|
148
|
+
// read uses botDefinitionId as the PRIMARY key and the collected developerName
|
|
149
|
+
// as the FALLBACK. A populated botDefinitionId is the load-bearing "agent
|
|
150
|
+
// already exists" signal for pre-provisioned agents; null just means this
|
|
151
|
+
// template row was never joined (e.g. a self-created agent) — not "no agent".
|
|
152
|
+
botDefinitionId: template.botDefinitionId ?? null,
|
|
153
|
+
signal: template.signal,
|
|
154
|
+
reason: template.reason,
|
|
155
|
+
},
|
|
156
|
+
verdict,
|
|
157
|
+
reasons: [studio.reason, template.reason],
|
|
158
|
+
}, null, 2) + '\n');
|
package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/create-scratch-dir.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic Phase-0 scratch-directory creator for the Fulfiller-agent
|
|
3
|
+
// create/activate flow.
|
|
4
|
+
//
|
|
5
|
+
// Phase-0 path selection + `mkdtemp` behavior are deterministic and must not
|
|
6
|
+
// live in workflow prose (authoring standard A9).
|
|
7
|
+
//
|
|
8
|
+
// The scratch dir MUST stay OUTSIDE the harness's scored `${outputDir}` tree.
|
|
9
|
+
// Several phases capture raw `sf api request rest` stdout, and that command is
|
|
10
|
+
// in beta — it prints beta/update/plugin diagnostics to STDERR. If an agent
|
|
11
|
+
// captures combined output (`2>&1`) instead of the documented `2>…err` split,
|
|
12
|
+
// that stderr preamble is merged into the captured file; a raw capture rooted
|
|
13
|
+
// under `${outputDir}` would then land in the eval's scored artifact set and
|
|
14
|
+
// fail the `core:json-valid` assertion even though the response body itself is
|
|
15
|
+
// well-formed JSON. So the scratch dir roots under `${TMPDIR}` (private,
|
|
16
|
+
// user-scoped), then `/tmp`, and only as an ultimate fallback (both temp bases
|
|
17
|
+
// unwritable) under the harness `${outputDir}`. `mkdtempSync` creates the run
|
|
18
|
+
// dir mode 0700 (owner-only) with a random suffix, so the world-writable `/tmp`
|
|
19
|
+
// parent is not a concern. Only the durable `${outputDir}/report.md` (written
|
|
20
|
+
// directly by render-report.mjs) is ever meant to be scored.
|
|
21
|
+
//
|
|
22
|
+
// Usage:
|
|
23
|
+
// node create-scratch-dir.mjs [outputDir]
|
|
24
|
+
//
|
|
25
|
+
// Emits the absolute path of the created directory to stdout (a single line,
|
|
26
|
+
// trailing newline). Exit 0 on success, 3 when no candidate base is writable.
|
|
27
|
+
|
|
28
|
+
import { mkdtempSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
|
|
31
|
+
const PREFIX = 'itsm-fulfiller-agent.';
|
|
32
|
+
|
|
33
|
+
// Candidate scratch bases, in priority order. `${outputDir}` is LAST so
|
|
34
|
+
// transient captures never enter the scored tree except when no temp dir works.
|
|
35
|
+
const candidates = [];
|
|
36
|
+
const tmp = (process.env.TMPDIR ?? '').trim();
|
|
37
|
+
if (tmp) candidates.push(tmp);
|
|
38
|
+
candidates.push('/tmp');
|
|
39
|
+
const outputDir = (process.argv[2] ?? '').trim();
|
|
40
|
+
if (outputDir) candidates.push(outputDir);
|
|
41
|
+
|
|
42
|
+
let dir = null;
|
|
43
|
+
let lastErr = null;
|
|
44
|
+
for (const base of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
dir = mkdtempSync(join(base, PREFIX));
|
|
47
|
+
break;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
lastErr = err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (dir) {
|
|
54
|
+
process.stdout.write(`${dir}\n`);
|
|
55
|
+
} else {
|
|
56
|
+
process.stderr.write(`error: mkdtemp failed under all of [${candidates.join(', ')}]: ${lastErr?.message ?? 'no base dir'}\n`);
|
|
57
|
+
process.exit(3);
|
|
58
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic renderer for the Fulfiller-agent create/activate report.
|
|
3
|
+
//
|
|
4
|
+
// Composing the report from phase state in model prose is unreliable
|
|
5
|
+
// (authoring standard A9) — the layout, field placement, and next-step
|
|
6
|
+
// wording all belong in a script that emits byte-identical output for a
|
|
7
|
+
// given state input. This helper is the single source of report text for
|
|
8
|
+
// both the chat-turn response and the harness's ${outputDir}/report.md
|
|
9
|
+
// file, so the two never diverge.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// node render-report.mjs <state.json> [outPath]
|
|
13
|
+
//
|
|
14
|
+
// state.json shape (all fields optional except `verdict`):
|
|
15
|
+
// {
|
|
16
|
+
// "org": string,
|
|
17
|
+
// "developerName": string,
|
|
18
|
+
// "label": string,
|
|
19
|
+
// "preflight": { "studioHasAccess": "true" | "false" | "cannot-confirm",
|
|
20
|
+
// "templateAgentScriptPresent": "yes" | "no" | "pending" },
|
|
21
|
+
// "enumerate": { "existsBeforeWrite": "yes" | "no" | "pending",
|
|
22
|
+
// "latestVersionStatus": "Active" | "Inactive" | "n/a" | "pending" },
|
|
23
|
+
// "confirmToWrite": "true" | "false" | "pending",
|
|
24
|
+
// "createBundle": string,
|
|
25
|
+
// "publish": string,
|
|
26
|
+
// "activate": string,
|
|
27
|
+
// "verify": string,
|
|
28
|
+
// "verdict": "CREATED" | "ALREADY-CREATED" | "ACTIVATED" |
|
|
29
|
+
// "PENDING CONFIRMATION" | "DECLINED" | "FAILED",
|
|
30
|
+
// "reason": string
|
|
31
|
+
// }
|
|
32
|
+
//
|
|
33
|
+
// If `outPath` is provided, writes to that path; otherwise emits to stdout.
|
|
34
|
+
// The rendered report is always ~30 lines and shape-identical run to run.
|
|
35
|
+
|
|
36
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
37
|
+
|
|
38
|
+
const [statePath, outPath] = process.argv.slice(2);
|
|
39
|
+
if (!statePath) {
|
|
40
|
+
process.stderr.write('usage: node render-report.mjs <state.json> [outPath]\n');
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let state;
|
|
45
|
+
try {
|
|
46
|
+
state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
process.stderr.write(`error: could not read/parse ${statePath}: ${err.message}\n`);
|
|
49
|
+
process.exit(3);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const VALID_VERDICTS = new Set([
|
|
53
|
+
'CREATED', 'ALREADY-CREATED', 'ACTIVATED',
|
|
54
|
+
'PENDING CONFIRMATION', 'DECLINED', 'FAILED',
|
|
55
|
+
]);
|
|
56
|
+
const verdict = String(state?.verdict ?? '').trim();
|
|
57
|
+
if (!VALID_VERDICTS.has(verdict)) {
|
|
58
|
+
process.stderr.write(`error: verdict "${verdict}" is not one of: ${[...VALID_VERDICTS].join(', ')}\n`);
|
|
59
|
+
process.exit(3);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function field(v, fallback = 'pending') {
|
|
63
|
+
const s = v === undefined || v === null ? '' : String(v).trim();
|
|
64
|
+
return s === '' ? fallback : s;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const org = field(state.org, '<unspecified>');
|
|
68
|
+
const devName = field(state.developerName, '<pending>');
|
|
69
|
+
const label = field(state.label, '<pending>');
|
|
70
|
+
|
|
71
|
+
const preStudio = field(state?.preflight?.studioHasAccess, 'pending');
|
|
72
|
+
const preTmpl = field(state?.preflight?.templateAgentScriptPresent, 'pending');
|
|
73
|
+
const enumExists = field(state?.enumerate?.existsBeforeWrite, 'pending');
|
|
74
|
+
const enumVer = field(state?.enumerate?.latestVersionStatus, 'pending');
|
|
75
|
+
|
|
76
|
+
function classifyPreflightVerdict() {
|
|
77
|
+
const explicit = field(state?.preflight?.verdict, '');
|
|
78
|
+
if (explicit !== 'pending' && explicit !== '') return explicit;
|
|
79
|
+
if (preStudio === 'false' || preTmpl === 'no') return 'NOT-READY';
|
|
80
|
+
const okStudio = preStudio === 'true';
|
|
81
|
+
const okTmpl = preTmpl === 'yes';
|
|
82
|
+
if (okStudio && okTmpl) return 'READY';
|
|
83
|
+
if (preStudio === 'cannot-confirm' || preTmpl === 'pending') return 'CANNOT-CONFIRM';
|
|
84
|
+
return 'pending';
|
|
85
|
+
}
|
|
86
|
+
function classifyEnumerate() {
|
|
87
|
+
const explicit = field(state?.enumerate?.verdict, '');
|
|
88
|
+
if (explicit !== 'pending' && explicit !== '') return explicit;
|
|
89
|
+
if (enumExists === 'yes') {
|
|
90
|
+
if (enumVer === 'Active') return 'exists:true, needsActivation:false';
|
|
91
|
+
if (enumVer === 'Inactive') return 'exists:true, needsActivation:true';
|
|
92
|
+
return 'exists:true';
|
|
93
|
+
}
|
|
94
|
+
if (enumExists === 'no') return 'exists:false';
|
|
95
|
+
return 'pending';
|
|
96
|
+
}
|
|
97
|
+
const preVerdict = classifyPreflightVerdict();
|
|
98
|
+
const enumVerdict = classifyEnumerate();
|
|
99
|
+
|
|
100
|
+
const confirm = field(state.confirmToWrite, 'pending');
|
|
101
|
+
const isPending = verdict === 'PENDING CONFIRMATION';
|
|
102
|
+
const isDeclined = verdict === 'DECLINED';
|
|
103
|
+
const isCreated = verdict === 'CREATED';
|
|
104
|
+
const isActivated = verdict === 'ACTIVATED';
|
|
105
|
+
const isAlready = verdict === 'ALREADY-CREATED';
|
|
106
|
+
const isFailed = verdict === 'FAILED';
|
|
107
|
+
const gatedFallback = isPending
|
|
108
|
+
? 'pending confirmation'
|
|
109
|
+
: isDeclined ? 'skipped'
|
|
110
|
+
: isAlready ? 'skipped (already exists)'
|
|
111
|
+
: isFailed ? 'skipped'
|
|
112
|
+
: 'pending';
|
|
113
|
+
|
|
114
|
+
const bundleVersionId = field(state.bundleVersionId, '');
|
|
115
|
+
const bundleVersionIdSuffix = bundleVersionId ? ` bundleVersionId=${bundleVersionId}` : '';
|
|
116
|
+
const onBundleVersionId = bundleVersionId ? ` on bundleVersionId=${bundleVersionId}` : '';
|
|
117
|
+
|
|
118
|
+
function withCreateSuffix(base) {
|
|
119
|
+
if (base === 'pending' || base.startsWith('skipped') || base === 'pending confirmation') return base;
|
|
120
|
+
return `${base}${bundleVersionIdSuffix}`;
|
|
121
|
+
}
|
|
122
|
+
function withPublishActivateSuffix(base) {
|
|
123
|
+
if (base === 'pending' || base.startsWith('skipped') || base === 'pending confirmation') return base;
|
|
124
|
+
return `${base}${onBundleVersionId}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const createBundle = withCreateSuffix(field(state.createBundle, gatedFallback));
|
|
128
|
+
const publish = withPublishActivateSuffix(field(state.publish, gatedFallback));
|
|
129
|
+
const activate = withPublishActivateSuffix(field(state.activate, gatedFallback));
|
|
130
|
+
const verifyFallback = isDeclined || isPending || isFailed
|
|
131
|
+
? 'skipped'
|
|
132
|
+
: isAlready
|
|
133
|
+
? `exists:true; latestVersionStatus=${enumVer}`
|
|
134
|
+
: 'pending';
|
|
135
|
+
let verify = field(state.verify, verifyFallback);
|
|
136
|
+
if (
|
|
137
|
+
(isCreated || isActivated) &&
|
|
138
|
+
verify !== 'pending' &&
|
|
139
|
+
!verify.startsWith('skipped') &&
|
|
140
|
+
!/exists\s*[:=]/i.test(verify)
|
|
141
|
+
) {
|
|
142
|
+
verify = `${verify}; exists:true; latestVersionStatus=Active`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const reason = String(state.reason ?? '').trim();
|
|
146
|
+
|
|
147
|
+
const NEXT_STEPS = {
|
|
148
|
+
'CREATED':
|
|
149
|
+
'The IT Service Fulfiller agent is live as an NGA-native agent (no external-link icon in Agentforce Studio). No new agent was provisioned beyond the one just created. Verify end-to-end in Agentforce Studio\'s Agents list.',
|
|
150
|
+
'ALREADY-CREATED':
|
|
151
|
+
'The IT Service Fulfiller agent was already present and active — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list.',
|
|
152
|
+
'ACTIVATED':
|
|
153
|
+
'The existing IT Service Fulfiller agent was found inactive and has been activated — no new agent was provisioned. Verify end-to-end in Agentforce Studio\'s Agents list.',
|
|
154
|
+
'PENDING CONFIRMATION':
|
|
155
|
+
'The run is paused at the confirm-to-write gate. In this skill, create + publish + activate happen as one atomic sequence gated by a single confirmation — there is no supported create-only or publish-without-activate mode, and no partial "create-only" mode of any kind. Re-run and reply "yes" when you are ready for the agent to go live.',
|
|
156
|
+
'DECLINED':
|
|
157
|
+
'No write occurred. This skill\'s create + publish + activate run as one atomic sequence gated by a single confirmation; there is no supported create-only or publish-without-activate mode, and no partial "create-only" mode of any kind. The user\'s decline (or "hold off on activation") kept the run at the gate. Re-run and confirm when ready for the full sequence.',
|
|
158
|
+
'FAILED':
|
|
159
|
+
reason
|
|
160
|
+
? `Review the Reason above and remediate. Enablement of missing prerequisites is a Setup-UI/admin action outside this skill's scope — use \`service-itsm-agentic-setup-agentforce-studio-validate\` or \`service-itsm-agentic-setup-agentforce-studio-configure\` to check/enable readiness first. Re-run this skill once the blocker is resolved.`
|
|
161
|
+
: 'Review the observed error and remediate. Enablement of missing prerequisites is a Setup-UI/admin action outside this skill\'s scope — use `service-itsm-agentic-setup-agentforce-studio-validate` or `service-itsm-agentic-setup-agentforce-studio-configure` to check/enable readiness first. Re-run this skill once the blocker is resolved.',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const report = `# Fulfiller Agent — Create & Activate
|
|
165
|
+
|
|
166
|
+
IT Service Fulfiller Agent Creation (via service-itsm-agentic-setup-fulfiller-agent-configure)
|
|
167
|
+
|
|
168
|
+
Org: ${org} (API v67.0)
|
|
169
|
+
Agent: ${devName} ("${label}") — NGA-native bundle
|
|
170
|
+
|
|
171
|
+
Preflight ......................... Studio hasAccess=${preStudio}; template agentScript present=${preTmpl}; verdict=${preVerdict}
|
|
172
|
+
Enumerate ......................... target agent exists before write=${enumExists}; latest version status=${enumVer}; verdict=${enumVerdict}
|
|
173
|
+
Confirm-to-write ................... user-confirmed=${confirm}${(confirm === 'true' && (devName !== '<pending>' || label !== '<pending>')) ? `; confirmed developerName=${devName}, label="${label}"` : ''}
|
|
174
|
+
Create bundle ...................... ${createBundle}
|
|
175
|
+
Publish ............................ ${publish}
|
|
176
|
+
Activate ........................... ${activate}
|
|
177
|
+
Verify ............................. ${verify}
|
|
178
|
+
|
|
179
|
+
Verdict: ${verdict}
|
|
180
|
+
Reason: ${reason}
|
|
181
|
+
|
|
182
|
+
Next steps:
|
|
183
|
+
- ${NEXT_STEPS[verdict]}
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
if (outPath) {
|
|
187
|
+
writeFileSync(outPath, report, 'utf8');
|
|
188
|
+
process.stdout.write(outPath + '\n');
|
|
189
|
+
} else {
|
|
190
|
+
process.stdout.write(report);
|
|
191
|
+
}
|