@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.
Files changed (136) hide show
  1. package/package.json +1 -1
  2. package/skills/automation-flow-generate/SKILL.md +32 -43
  3. package/skills/experience-portal-create/SKILL.md +497 -0
  4. package/skills/experience-portal-create/assets/report-template.md +30 -0
  5. package/skills/experience-portal-create/references/mcp-invocation.md +288 -0
  6. package/skills/experience-portal-create/references/post-creation-activate-publish.md +165 -0
  7. package/skills/experience-portal-create/references/templates.md +253 -0
  8. package/skills/experience-ui-bundle-features-generate/SKILL.md +5 -1
  9. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -0
  10. package/skills/experience-ui-bundle-frontend-generate/references/page.md +1 -0
  11. package/skills/platform-datamask-run/SKILL.md +345 -0
  12. package/skills/platform-datamask-run/references/api-surface.md +130 -0
  13. package/skills/platform-datamask-run/references/policy-authoring.md +185 -0
  14. package/skills/platform-datamask-run/references/run-and-abort.md +116 -0
  15. package/skills/platform-datamask-run/scripts/poll-job.sh +115 -0
  16. package/skills/platform-dataspace-access-configure/SKILL.md +51 -3
  17. package/skills/platform-dataspace-access-configure/scripts/inspect-dataspace-scopes.sh +56 -0
  18. package/skills/platform-lightning-type-widget-coordinate/references/build-plan-format.md +1 -0
  19. package/skills/platform-sandbox-configure/SKILL.md +17 -2
  20. package/skills/platform-trial-org-create/SKILL.md +175 -0
  21. package/skills/platform-trial-org-create/examples/create_request.json +9 -0
  22. package/skills/platform-trial-org-create/examples/error_response.json +41 -0
  23. package/skills/platform-trial-org-create/examples/success_response.json +27 -0
  24. package/skills/platform-trial-org-create/references/error_codes.md +42 -0
  25. package/skills/platform-trial-org-create/references/signup_request_fields.md +69 -0
  26. package/skills/platform-trial-org-create/scripts/create_signup_request.sh +175 -0
  27. package/skills/platform-trial-org-create/scripts/get_signup_request.sh +155 -0
  28. package/skills/platform-widget-generate/SKILL.md +47 -6
  29. package/skills/platform-widget-generate/examples/conditional.json +3 -3
  30. package/skills/platform-widget-generate/examples/list-with-foreach.json +2 -2
  31. package/skills/platform-widget-generate/examples/single-object.json +2 -2
  32. package/skills/platform-widget-generate/references/widget-bundle-layout.md +1 -1
  33. package/skills/service-agentforce-channel-configure/SKILL.md +271 -0
  34. package/skills/service-agentforce-channel-configure/references/agent-wiring.md +97 -0
  35. package/skills/service-agentforce-channel-configure/references/channel-branch-email.md +145 -0
  36. package/skills/service-agentforce-channel-configure/references/channel-branch-voice.md +69 -0
  37. package/skills/service-agentforce-channel-configure/references/channel-types.md +61 -0
  38. package/skills/service-agentforce-channel-configure/references/live-traffic-gate.md +86 -0
  39. package/skills/service-agentforce-channel-configure/references/queue-resolution.md +135 -0
  40. package/skills/service-agentforce-channel-configure/references/routing-flow.md +384 -0
  41. package/skills/service-catalog-template-deploy/SKILL.md +310 -0
  42. package/skills/service-catalog-template-deploy/references/cli-invocation.md +258 -0
  43. package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +164 -0
  44. package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +94 -0
  45. package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +331 -0
  46. package/skills/service-catalog-template-search/SKILL.md +212 -0
  47. package/skills/service-catalog-template-search/references/cli-invocation.md +128 -0
  48. package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +205 -0
  49. package/skills/service-concierge-portal-generate/SKILL.md +126 -0
  50. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1428 -0
  51. package/skills/service-digital-engagement-channel-configure/SKILL.md +46 -6
  52. package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +2 -1
  53. package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -1
  54. package/skills/service-helpagent-coordinate/README.md +8 -2
  55. package/skills/service-helpagent-coordinate/SKILL.md +126 -130
  56. package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +70 -53
  57. package/skills/service-helpagent-coordinate/references/agent-script.md +571 -457
  58. package/skills/service-helpagent-coordinate/references/channel-voice.md +38 -9
  59. package/skills/service-helpagent-coordinate/references/channel-web-chat.md +173 -49
  60. package/skills/service-helpagent-coordinate/references/output-report-format.md +126 -0
  61. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +153 -0
  62. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +79 -0
  63. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +35 -0
  64. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +271 -0
  65. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/references/cli-invocation.md +265 -0
  66. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-enable-plan.mjs +220 -0
  67. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-final-report.mjs +102 -0
  68. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/record-enable-result.mjs +73 -0
  69. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/SKILL.md +206 -0
  70. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/references/cli-invocation.md +194 -0
  71. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/scripts/classify-readiness.mjs +223 -0
  72. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +45 -7
  73. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +45 -5
  74. package/skills/service-itsm-agentic-setup-configure/SKILL.md +116 -0
  75. package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +64 -0
  76. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +158 -0
  77. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +361 -0
  78. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/error-taxonomy.md +44 -0
  79. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +66 -0
  80. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +66 -0
  81. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/specialized-templates.md +148 -0
  82. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +169 -0
  83. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/build-create-body.mjs +116 -0
  84. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +185 -0
  85. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +168 -0
  86. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/create-scratch-dir.mjs +58 -0
  87. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +197 -0
  88. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +186 -0
  89. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/action-availability.md +51 -0
  90. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +345 -0
  91. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/error-taxonomy.md +44 -0
  92. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +63 -0
  93. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +44 -0
  94. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +149 -0
  95. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/build-create-body.mjs +110 -0
  96. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-action-availability.mjs +201 -0
  97. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-activate-result.mjs +135 -0
  98. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +194 -0
  99. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +158 -0
  100. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/create-scratch-dir.mjs +58 -0
  101. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +191 -0
  102. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +133 -0
  103. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +71 -0
  104. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +308 -0
  105. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +23 -0
  106. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +193 -0
  107. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +57 -0
  108. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +394 -0
  109. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/SKILL.md +266 -0
  110. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/cli-invocation.md +106 -0
  111. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/helper-contracts.md +142 -0
  112. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/permset-topology.md +82 -0
  113. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-action-surface.mjs +137 -0
  114. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-assignment-state.mjs +99 -0
  115. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-permset-availability.mjs +120 -0
  116. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/resolve-target-user.mjs +86 -0
  117. package/skills/service-itsm-agentic-setup-uel-user-create/SKILL.md +284 -0
  118. package/skills/service-itsm-agentic-setup-uel-user-create/references/mcp-invocation.md +302 -0
  119. package/skills/service-itsm-channels-coordinate/SKILL.md +472 -0
  120. package/skills/service-itsm-incident-mgmt-configure/SKILL.md +212 -0
  121. package/skills/service-itsm-incident-mgmt-configure/references/mcp-invocation.md +225 -0
  122. package/skills/service-itsm-incident-priority-configure/SKILL.md +53 -12
  123. package/skills/service-itsm-swarming-configure/SKILL.md +212 -0
  124. package/skills/service-itsm-teams-configure/SKILL.md +395 -0
  125. package/skills/service-itsm-teams-configure/references/azure-credential-population.md +213 -0
  126. package/skills/service-itsm-teams-configure/references/gotchas.md +23 -0
  127. package/skills/service-itsm-teams-coordinate/SKILL.md +175 -0
  128. package/skills/service-itsm-teams-coordinate/examples/output-templates.md +85 -0
  129. package/skills/service-itsm-teams-debug/SKILL.md +144 -0
  130. package/skills/service-itsm-teams-debug/references/configuration-checklists.md +277 -0
  131. package/skills/service-itsm-teams-debug/references/report-generation.md +95 -0
  132. package/skills/service-itsm-teams-employee-agent-configure/SKILL.md +139 -0
  133. package/skills/service-itsm-teams-employee-agent-configure/assets/Teams_AgentForce.EmbeddedServiceConfig-meta.xml +43 -0
  134. package/skills/service-itsm-teams-employee-agent-configure/references/teams-embedded-employee-agent.md +480 -0
  135. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +232 -0
  136. 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');
@@ -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');
@@ -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
+ }