@salesforce/afv-skills 1.57.0 → 1.59.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/agentforce-bot-upgrade/SKILL.md +3 -1
- package/skills/agentforce-generate/SKILL.md +21 -1
- package/skills/agentforce-generate/references/known-issues.md +51 -1
- package/skills/agentforce-generate/references/legacy-nga-regression-eval.md +547 -0
- package/skills/agentforce-generate/references/upgrade-legacy-agent-to-agentscript.md +577 -0
- package/skills/agentforce-generate/scripts/upgrade-legacy-nga-analyze.py +404 -0
- package/skills/agentforce-generate/scripts/upgrade-legacy-nga-run-trials.sh +58 -0
- package/skills/agentforce-persona-generate/SKILL.md +505 -0
- package/skills/agentforce-persona-generate/assets/checklist-template.md +100 -0
- package/skills/agentforce-persona-generate/assets/persona-encoding-template.md +252 -0
- package/skills/agentforce-persona-generate/assets/persona-template.md +229 -0
- package/skills/agentforce-persona-generate/assets/sample-dialog-template.md +50 -0
- package/skills/agentforce-persona-generate/assets/workshop-outline-template.md +97 -0
- package/skills/agentforce-persona-generate/examples/concierge-persona.md +189 -0
- package/skills/agentforce-persona-generate/examples/concierge-sample-dialog.md +70 -0
- package/skills/agentforce-persona-generate/examples/drover-persona.md +194 -0
- package/skills/agentforce-persona-generate/examples/drover-sample-dialog.md +62 -0
- package/skills/agentforce-persona-generate/examples/steady-hand-persona.md +188 -0
- package/skills/agentforce-persona-generate/examples/steady-hand-sample-dialog.md +68 -0
- package/skills/agentforce-persona-generate/examples/yt-persona.md +195 -0
- package/skills/agentforce-persona-generate/examples/yt-sample-dialog.md +66 -0
- package/skills/agentforce-persona-generate/references/attributions.md +36 -0
- package/skills/agentforce-persona-generate/references/interaction-design-notes.md +30 -0
- package/skills/agentforce-persona-generate/references/persona-encoding-guide.md +566 -0
- package/skills/agentforce-persona-generate/references/persona-framework.md +796 -0
- package/skills/agentforce-persona-generate/references/refinement-mappings.md +31 -0
- package/skills/agentforce-persona-generate/references/scoring-rubric.md +58 -0
- package/skills/agentforce-persona-generate/references/voice-catalog.json +2327 -0
- package/skills/agentforce-persona-generate/references/workshop-question-policy.md +90 -0
- package/skills/agentforce-persona-generate/references/workshop-scenario-seeds.md +119 -0
- package/skills/agentforce-persona-generate/scripts/parse_agent_yaml.py +223 -0
- package/skills/dx-app-analytics-query/SKILL.md +8 -8
- package/skills/experience-ui-bundle-app-coordinate/SKILL.md +12 -12
- package/skills/experience-ui-bundle-app-coordinate/references/phase-completion-validation.md +3 -3
- package/skills/experience-ui-bundle-app-coordinate/references/phase-execution-pattern.md +9 -7
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.mjs +132 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.mjs +62 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.mjs +71 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.mjs +95 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.mjs +78 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.mjs +21 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.mjs +28 -0
- package/skills/experience-ui-bundle-custom-app-generate/SKILL.md +3 -1
- package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.mjs +40 -0
- package/skills/experience-ui-bundle-frontend-generate/SKILL.md +7 -5
- package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.mjs +70 -0
- package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.mjs +76 -0
- package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.mjs +115 -0
- package/skills/experience-ui-bundle-metadata-generate/SKILL.md +7 -3
- package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +1 -1
- package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +1 -1
- package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.mjs +43 -0
- package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.mjs +131 -0
- package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.mjs +152 -0
- package/skills/experience-ui-bundle-mfa-configure/references/branding.md +10 -6
- package/skills/experience-ui-bundle-project-generate/SKILL.md +13 -39
- package/skills/experience-ui-bundle-project-generate/scripts/flatten-project.mjs +33 -11
- package/skills/experience-ui-bundle-project-generate/scripts/generate-project.mjs +77 -0
- package/skills/experience-ui-bundle-project-generate/scripts/install-deps.mjs +95 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +1 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +1 -0
- package/skills/service-omni-command-center-configure/SKILL.md +92 -0
- package/skills/service-omni-command-center-configure/references/api-notes.md +50 -0
- package/skills/service-omni-command-center-configure/scripts/configure-and-report.sh +257 -0
- package/skills/service-omni-command-center-configure/scripts/settings_document.py +97 -0
- package/skills/service-omni-command-center-configure/scripts/tests/test_command_center_configure_contracts.py +206 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.sh +0 -42
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh +0 -26
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.sh +0 -35
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.sh +0 -47
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.sh +0 -33
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.sh +0 -20
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh +0 -15
- package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.sh +0 -30
- package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.sh +0 -37
- package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.sh +0 -66
- package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.sh +0 -58
- package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh +0 -30
- package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +0 -73
- package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +0 -118
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Legacy vs NGA Regression Eval
|
|
2
|
+
|
|
3
|
+
Regression-test a migrated Agent Script (**NGA draft**) against the **legacy**
|
|
4
|
+
agent it replaces, across a fixed utterance suite, to verify equivalent routing
|
|
5
|
+
and actions and acceptable (ideally improved) responses. Findings drive edits to
|
|
6
|
+
the draft; the draft is re-tested until it clears the bar.
|
|
7
|
+
|
|
8
|
+
This is the mechanism behind **Step 5d** of
|
|
9
|
+
[upgrade-legacy-agent-to-agentscript.md](upgrade-legacy-agent-to-agentscript.md).
|
|
10
|
+
It is **draft-only**: nothing here publishes, activates, or deploys anything
|
|
11
|
+
without explicit human approval (per [SKILL.md](../SKILL.md) Rule 1). Two bundled
|
|
12
|
+
scripts do the mechanical work — both under this skill's `scripts/`:
|
|
13
|
+
|
|
14
|
+
- `scripts/upgrade-legacy-nga-run-trials.sh` — collects preview trials.
|
|
15
|
+
- `scripts/upgrade-legacy-nga-analyze.py` — emits the comparison report.
|
|
16
|
+
|
|
17
|
+
> **Example values.** Throughout, `Financial_Service_Agent` (the agent
|
|
18
|
+
> api-name/bundle) and `mk-c360a` (an org alias) appear **only as illustrative
|
|
19
|
+
> examples**. Substitute your own agent and org — nothing in the scripts or this
|
|
20
|
+
> doc is agent-, utterance-, or org-specific; all values come from config/env.
|
|
21
|
+
|
|
22
|
+
## Contents
|
|
23
|
+
|
|
24
|
+
- [Subjects under test](#subjects-under-test)
|
|
25
|
+
- [Prerequisites](#prerequisites)
|
|
26
|
+
- [Configuration and the utterance suite](#configuration-and-the-utterance-suite)
|
|
27
|
+
- [Generating the utterance suite](#generating-the-utterance-suite)
|
|
28
|
+
- [Side-effect safety gate](#side-effect-safety-gate)
|
|
29
|
+
- [Execution mechanics and traps](#execution-mechanics-and-traps)
|
|
30
|
+
- [Rubric #1 — Trace / action equality (mechanical)](#rubric-1--trace--action-equality-mechanical)
|
|
31
|
+
- [Rubric #2 — Response discrepancy (LLM judge)](#rubric-2--response-discrepancy-llm-judge)
|
|
32
|
+
- [Trials, caching, and the canonical response](#trials-caching-and-the-canonical-response)
|
|
33
|
+
- [The report](#the-report)
|
|
34
|
+
- [Iteration loop and stop condition](#iteration-loop-and-stop-condition)
|
|
35
|
+
|
|
36
|
+
## Subjects under test
|
|
37
|
+
|
|
38
|
+
Three references are in play: the two agents previewed, plus the org.
|
|
39
|
+
|
|
40
|
+
| Role | Selector | How previewed | Frozen? |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| **Legacy** | the classic `BotDefinition` (e.g. `PlannerType = AiCopilot__ReAct`) | `sf agent preview … --api-name <agent>` | **Yes** — must be **Active**; cache its results |
|
|
43
|
+
| **NGA draft** | the `AiAuthoringBundle` (local `.agent`) | `sf agent preview … --authoring-bundle <agent>` (`--simulate-actions` default; `--use-live-actions` to opt in) | **No** — changes as you edit; never cache |
|
|
44
|
+
| Org | an org alias (e.g. `mk-c360a`) | `-o <org-alias>` | — |
|
|
45
|
+
|
|
46
|
+
> **Name collision (by design).** The legacy Bot and the draft bundle typically
|
|
47
|
+
> share the **same** API name (the migration preserves it). Always disambiguate
|
|
48
|
+
> by *flag*, never by the bare name: `--api-name` → legacy (published/activated
|
|
49
|
+
> Bot), `--authoring-bundle` → local draft.
|
|
50
|
+
|
|
51
|
+
## Prerequisites
|
|
52
|
+
|
|
53
|
+
Assert all four before running trials:
|
|
54
|
+
|
|
55
|
+
1. **Correct org.** `sf config get target-org --json` may be stale — do not
|
|
56
|
+
trust a default alias. Confirm the intended org with
|
|
57
|
+
`sf org display -o <org-alias> --json`.
|
|
58
|
+
2. **Legacy has an Active `BotVersion`.** Otherwise `preview start` fails with
|
|
59
|
+
`Bot <id> has no active version.` Activation is a consequential release
|
|
60
|
+
action — **require explicit human approval; never auto-activate.**
|
|
61
|
+
3. **Draft compiles clean.** `node scripts/index-agent.mjs <agent-file>` (see
|
|
62
|
+
[agentscript-toolchain.md](agentscript-toolchain.md)).
|
|
63
|
+
4. **`sf` CLI ≥ 2.149.9** (bundles `@salesforce/agents ≥ 2.0.4`). Assert with
|
|
64
|
+
`sf --version`. Below this, the **legacy** local plan trace is written empty
|
|
65
|
+
(`{}`) and Rubric #1 for legacy cannot be verified locally (see
|
|
66
|
+
[Rubric #1](#rubric-1--trace--action-equality-mechanical)). Upgrade with
|
|
67
|
+
`sf update` (or `npm i -g @salesforce/cli@latest`).
|
|
68
|
+
|
|
69
|
+
## Configuration and the utterance suite
|
|
70
|
+
|
|
71
|
+
All values are overridable; the scripts read the agent and org from **env** so
|
|
72
|
+
nothing is baked in:
|
|
73
|
+
|
|
74
|
+
- `EVAL_AGENT` — the legacy Bot api-name **and** the draft bundle name (they
|
|
75
|
+
collide by design).
|
|
76
|
+
- `EVAL_ORG` — the target-org alias.
|
|
77
|
+
- `EVAL_BASELINE` — path to the accepted-baseline JSON (default
|
|
78
|
+
`agent-eval/baseline.json`; override to taste).
|
|
79
|
+
- `DEMO_CONTACT` — optional; if set, the analyzer asserts the first draft action
|
|
80
|
+
input binds this contact Id.
|
|
81
|
+
|
|
82
|
+
A representative config (example values):
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
org_alias: mk-c360a # example — use your alias
|
|
86
|
+
legacy:
|
|
87
|
+
api_name: Financial_Service_Agent # example — use your agent
|
|
88
|
+
draft:
|
|
89
|
+
authoring_bundle: Financial_Service_Agent
|
|
90
|
+
use_live_actions: false # default = simulated (safe); true = live side effects (opt in per safety gate)
|
|
91
|
+
trials: 3 # N runs per (subject, utterance)
|
|
92
|
+
demo_contact_id: "0034V00003XcASMQA3" # example — the fixed contact data actions use
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The **utterance suite** is data-driven. Each entry carries: `id`, `text`,
|
|
96
|
+
`source` (one of `user | generated`), `spec_status` (one of
|
|
97
|
+
`confirmed | inferred`), `expected_subagent`, `expected_actions`, `side_effects`
|
|
98
|
+
(one of `none | email | lead | escalate`), and `notes`. The `expected_*` columns
|
|
99
|
+
are the **contract**, not a convenience — they encode intent independently of what
|
|
100
|
+
legacy happened to do, so the oracle triangle (below) can report a legacy defect
|
|
101
|
+
as a *win* rather than a regression.
|
|
102
|
+
|
|
103
|
+
`source` records the **utterance's provenance**, independent of the spec. `user`
|
|
104
|
+
marks real traffic the human supplied — **prioritize as must-pass**; `generated`
|
|
105
|
+
marks utterances the skill synthesized to widen coverage (edge cases, robustness
|
|
106
|
+
paths). Provenance sets *importance*; it does not by itself make the `expected_*`
|
|
107
|
+
trustworthy — that is `spec_status`.
|
|
108
|
+
|
|
109
|
+
`spec_status` records confidence in the **`expected_*` contract**, on a separate
|
|
110
|
+
axis from `source`. A user commonly supplies only the utterance `text` and no
|
|
111
|
+
`expected_*`; the skill then **infers** the intended route + actions and marks the
|
|
112
|
+
entry `inferred` — provisional, **pending the user's validation on inspection**.
|
|
113
|
+
Once the human confirms (or authors) the spec it flips to `confirmed`, and only a
|
|
114
|
+
`confirmed` spec anchors the oracle triangle as truth. **Infer from intent — the
|
|
115
|
+
utterance plus the agent's topic/action catalog — never by copying the legacy
|
|
116
|
+
trace**: a spec cloned from legacy makes legacy pass the triangle by construction
|
|
117
|
+
and collapses the legacy-is-a-baseline-not-oracle design. The provenance ×
|
|
118
|
+
confidence combinations:
|
|
119
|
+
|
|
120
|
+
- `user` + `confirmed` — human gave or validated the spec: full oracle, must-pass.
|
|
121
|
+
- `user` + `inferred` — human gave only the utterance; skill proposed the spec; **awaiting the user's sign-off**.
|
|
122
|
+
- `generated` + `inferred` — skill proposed both; awaiting sign-off.
|
|
123
|
+
- `generated` + `confirmed` — skill proposed, human reviewed and accepted.
|
|
124
|
+
|
|
125
|
+
A `SPEC-REVIEW` / `ADJUDICATE` on an `inferred` row most often means the proposed
|
|
126
|
+
spec was wrong — fix the suite entry, don't touch the draft. An example suite:
|
|
127
|
+
|
|
128
|
+
| id | source | spec_status | utterance | expected subagent | expected action(s) | side effects |
|
|
129
|
+
|---|---|---|---|---|---|---|
|
|
130
|
+
| mortgage_faq | user | confirmed | "What's better, a fixed-rate or an adjustable-rate mortgage?" | Financial_Knowledge_Base_Search | Get_Financial_Customer_Details, Answer_Financial_FAQs | none |
|
|
131
|
+
| loan_status | user | inferred | "What is the status of my Personal Loan Application?" | Loan_Application_Status_Query_Snowflake0 | Get_All_Loan_Application_Details | none |
|
|
132
|
+
| cc_fees | user | confirmed | "What are my credit card fees?" | Credit_Card_Assistance0 | Financial_Credit_Card_Customer | none |
|
|
133
|
+
| cc_txns | user | confirmed | "Show me my last 5 credit card transactions" | Credit_Card_Assistance0 | Get_Credit_Card_Transactions_Details | none |
|
|
134
|
+
| product_rec | user | inferred | "Can you recommend a product for me?" | Provide_Product_Recommendations0 | Get_Customer_Product_List | none |
|
|
135
|
+
| offers | user | confirmed | "What offers are available?" | Offers | Financial_Offers_by_Category | none |
|
|
136
|
+
| escalate | generated | confirmed | "I want to speak to a human." | escalation | escalate_to_human | **escalate** |
|
|
137
|
+
| offtopic | generated | inferred | "What's the weather today?" | off_topic | (none) | none |
|
|
138
|
+
| ambiguous | generated | inferred | "help" | ambiguous_question | (none) | none |
|
|
139
|
+
| wrap_up | generated | confirmed | "No thanks, that's all. Bye." | Chat_Wrap_Up2 | Financial_Email_Action_Post_Agent_Chat | **email** |
|
|
140
|
+
|
|
141
|
+
(The rows above are examples from one financial-services agent — replace them
|
|
142
|
+
with utterances for the agent under test. Here the six domain queries are
|
|
143
|
+
`user`-supplied while the four edge cases — escalation, off-topic, ambiguous,
|
|
144
|
+
wrap-up — are `generated` for coverage; `spec_status` marks which `expected_*` are
|
|
145
|
+
`confirmed` versus still `inferred` and awaiting the user's validation — here
|
|
146
|
+
`loan_status`, `product_rec`, `offtopic`, and `ambiguous`.)
|
|
147
|
+
|
|
148
|
+
## Generating the utterance suite
|
|
149
|
+
|
|
150
|
+
`user`-supplied utterances (real traffic) come first and set the must-pass bar.
|
|
151
|
+
The skill then **generates** additional utterances to close the coverage gaps that
|
|
152
|
+
real traffic leaves open. Generation is **catalog-driven, not free-form**:
|
|
153
|
+
enumerate the agent's topics/subagents and their actions — the same topic/action
|
|
154
|
+
catalog used to infer specs (see
|
|
155
|
+
[Configuration](#configuration-and-the-utterance-suite)) — and synthesize
|
|
156
|
+
utterances so every route and guardrail is exercised. The oracle triangle can only
|
|
157
|
+
*catch* a routing/action regression on a path some utterance actually walks; an
|
|
158
|
+
untested route is invisible to the eval, so generation is fundamentally
|
|
159
|
+
coverage-gap-closing, not invention.
|
|
160
|
+
|
|
161
|
+
**How many — aim for 5–10 initially.** This is a **probing** eval — spin the draft
|
|
162
|
+
against the legacy, surface discrepancies, act on them — **not** a full-coverage
|
|
163
|
+
certification suite. Mind the cost: each utterance runs `trials` × 2 subjects =
|
|
164
|
+
**6 preview sessions** at the default `trials: 3` (legacy is cached after the first
|
|
165
|
+
run, so later iterations re-run only the draft's N — see
|
|
166
|
+
[Trials, caching](#trials-caching-and-the-canonical-response)). So 5–10 utterances
|
|
167
|
+
is ~30–60 preview runs — enough to probe the behavior surface without a marathon.
|
|
168
|
+
Report the scope back to the user, e.g. *"Generated T = 8 utterances covering
|
|
169
|
+
roughly ~30% of the agent's topic/action catalog."* Then let the user decide
|
|
170
|
+
whether to go deeper — the suite grows **on request**, not by default.
|
|
171
|
+
|
|
172
|
+
**Six criteria, in priority order:**
|
|
173
|
+
|
|
174
|
+
1. **Route coverage (the backbone).** One+ utterance per topic/subagent and per
|
|
175
|
+
distinct action path, so no route goes untested.
|
|
176
|
+
2. **Boundary / near-miss routing.** Utterances that sit *between* two topics.
|
|
177
|
+
Mis-routing is the top migration regression and only surfaces when you probe the
|
|
178
|
+
decision boundary, not the center of a topic.
|
|
179
|
+
3. **Guardrail & terminal behaviors.** Off-topic (should deflect/refuse),
|
|
180
|
+
ambiguous/underspecified ("help"), escalation-to-human, wrap-up/goodbye — the
|
|
181
|
+
agent's *edges*, not its domain answers.
|
|
182
|
+
4. **Side-effect paths.** Anything that fires email / lead / escalate, tagged
|
|
183
|
+
`side_effects` for the [safety gate](#side-effect-safety-gate) before any live
|
|
184
|
+
run. High-risk and high-value: most likely to diverge legacy → draft.
|
|
185
|
+
5. **Grounding variants.** An utterance that *should* force a retrieval/grounding
|
|
186
|
+
action, paired with one answerable from model knowledge — to catch the "draft
|
|
187
|
+
answered from its own knowledge, legacy grounded" regression that
|
|
188
|
+
[Rubric #2](#rubric-2--response-discrepancy-llm-judge) step 5 is built to flag.
|
|
189
|
+
6. **Paraphrase robustness.** A couple of phrasings of the same intent — NL routing
|
|
190
|
+
is phrasing-sensitive, so one wording passing does not mean the intent is safe.
|
|
191
|
+
|
|
192
|
+
**Priorities and guardrails:**
|
|
193
|
+
|
|
194
|
+
- **Real traffic first.** `generated` provenance is lower must-pass priority than
|
|
195
|
+
`user` (see [Configuration](#configuration-and-the-utterance-suite)); generate to
|
|
196
|
+
*widen* coverage, never to substitute for what users actually say.
|
|
197
|
+
- **Skew toward hidden behavior.** The happy path is usually already covered by
|
|
198
|
+
real traffic, so the `generated` set earns its keep by probing what the happy
|
|
199
|
+
path won't: guardrail firing, terminal/lifecycle side effects, degenerate input,
|
|
200
|
+
boundary routing — exactly where migrations quietly break (a topic that silently
|
|
201
|
+
stopped matching, a guardrail that stopped firing, a side effect that changed
|
|
202
|
+
shape). Happy-path-only generation makes the eval look green while leaving the
|
|
203
|
+
risky surface untested.
|
|
204
|
+
- **Every generated utterance still needs a spec**, inferred from *intent +
|
|
205
|
+
catalog* and marked `inferred` until the user confirms it — **never cloned from
|
|
206
|
+
the legacy trace** (the anti-circularity rule; see
|
|
207
|
+
[the oracle triangle](#the-oracle-triangle--legacy-is-a-baseline-not-the-source-of-truth)).
|
|
208
|
+
Bare text with no `expected_*` yields only a `SPEC-UNVERIFIED` verdict.
|
|
209
|
+
- **No redundant padding.** Each utterance should test a route, action, boundary,
|
|
210
|
+
or guardrail a prior one didn't; drop duplicates that add trials without adding
|
|
211
|
+
coverage.
|
|
212
|
+
|
|
213
|
+
## Side-effect safety gate
|
|
214
|
+
|
|
215
|
+
**This gate is mandatory.** In `use_live_actions: true`, some utterances fire
|
|
216
|
+
**real** effects — e.g. `escalate` (transfer to a human), `email`/`wrap_up`
|
|
217
|
+
(sends a customer email), and lead-capture paths (create a Lead + post to Slack).
|
|
218
|
+
Before running trials the skill MUST:
|
|
219
|
+
|
|
220
|
+
- **(a) Tag** every utterance with its `side_effects` category.
|
|
221
|
+
- **(b) Default tagged utterances to simulated mode** (`--simulate-actions` at
|
|
222
|
+
`start`) **or** require explicit per-utterance confirmation before any live run.
|
|
223
|
+
- **(c) Never run a tagged utterance repeatedly** (the N trials) in live mode
|
|
224
|
+
without opt-in.
|
|
225
|
+
|
|
226
|
+
Read-only data utterances (`side_effects: none`) are safe to run N× live.
|
|
227
|
+
|
|
228
|
+
The runner enforces (b) mechanically: `upgrade-legacy-nga-run-trials.sh` **defaults
|
|
229
|
+
to `simulate`** and passes `--use-live-actions` only when you give it an explicit
|
|
230
|
+
`live` mode arg. Legacy (`--api-name`) has no simulate mode and always runs live, so
|
|
231
|
+
gate side-effecting utterances against legacy upstream (confirm, or hold it to N=1).
|
|
232
|
+
|
|
233
|
+
## Execution mechanics and traps
|
|
234
|
+
|
|
235
|
+
For each subject × utterance × trial: **start → send → end**, one fresh session
|
|
236
|
+
per trial (independent trials, no cross-turn contamination). This is exactly what
|
|
237
|
+
`scripts/upgrade-legacy-nga-run-trials.sh` does:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
EVAL_AGENT=<agent> EVAL_ORG=<org-alias> \
|
|
241
|
+
bash scripts/upgrade-legacy-nga-run-trials.sh <legacy|draft> <utt_id> <N> <outdir> "<utterance text>" [simulate|live]
|
|
242
|
+
# mode defaults to `simulate`; pass `live` only for opted-in utterances (gates the
|
|
243
|
+
# draft — legacy has no simulate mode and always runs live)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The reliable per-trial path it runs:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# START — capture stdout to a FILE (see trap below); mode flag is start-ONLY
|
|
250
|
+
sf agent preview start --json <SUBJECT_SELECTOR> -o "$ORG" > start.json 2> start.err
|
|
251
|
+
SID=$(python3 -c "import json;print(json.load(open('start.json'))['result']['sessionId'])")
|
|
252
|
+
|
|
253
|
+
# SEND
|
|
254
|
+
sf agent preview send --json <SUBJECT_SELECTOR> --session-id "$SID" \
|
|
255
|
+
-u "$UTTERANCE" -o "$ORG" > send.json 2> send.err
|
|
256
|
+
|
|
257
|
+
# END — frees the session; writes the local plan trace to disk
|
|
258
|
+
sf agent preview end --json <SUBJECT_SELECTOR> --session-id "$SID" -o "$ORG" > end.json 2> end.err
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`<SUBJECT_SELECTOR>` is `--api-name <agent>` (legacy) or
|
|
262
|
+
`--authoring-bundle <agent>` (draft). **The mode flag
|
|
263
|
+
`--use-live-actions` / `--simulate-actions` is `start`-ONLY** — mode is fixed at
|
|
264
|
+
session creation. `send`/`end` accept only the subject selector + `--session-id`;
|
|
265
|
+
passing `--use-live-actions` to them fails with
|
|
266
|
+
`Nonexistent flag: --use-live-actions`. Legacy via `--api-name` runs live by
|
|
267
|
+
default and takes no mode flag.
|
|
268
|
+
|
|
269
|
+
### Traps (baked into the scripts — do not undo them)
|
|
270
|
+
|
|
271
|
+
- **Never pipe preview output through `2>&1 | python`.** The CLI writes a
|
|
272
|
+
progress spinner to stderr; merged into stdout it corrupts the JSON
|
|
273
|
+
(`JSONDecodeError: Expecting value`). Redirect **stdout to a file** and parse
|
|
274
|
+
the file; keep stderr separate. (This is consistent with [SKILL.md](../SKILL.md)
|
|
275
|
+
Rule 1's "always `--json`, don't mangle the data stream".)
|
|
276
|
+
- **Force plain output — `export NO_COLOR=1 FORCE_COLOR=0`.** In some shells/CI,
|
|
277
|
+
`sf … --json` still emits ANSI color codes, and once stdout is redirected to a
|
|
278
|
+
file those escape bytes corrupt the JSON the collector parses. The collector
|
|
279
|
+
script sets both env vars near the top; export them yourself before any manual
|
|
280
|
+
`sf … --json > file` invocation too.
|
|
281
|
+
- **The analyzer locates `.sfdx/agents` by walking up from cwd** (then from
|
|
282
|
+
`<run_dir>`), so it runs from anywhere inside the repo. A fixed cwd would
|
|
283
|
+
report a **false `UNVERIFIED-TRACE`** for both subjects when run from a
|
|
284
|
+
subdirectory — the upward search stays.
|
|
285
|
+
- Always `--json`. Never `jq`/`2>/dev/null` on the data stream itself.
|
|
286
|
+
- Response text lives at `result.messages[*].message`. Also capture
|
|
287
|
+
`isContentSafe`, `planId`, `result[]`, `citedReferences`.
|
|
288
|
+
- Live-action sends can take **30–120 s**. Use a generous timeout (≥ 180 s).
|
|
289
|
+
- Session artifacts land in `.sfdx/agents/<agentIdOrName>/sessions/<sessionId>/`.
|
|
290
|
+
Legacy sessions nest under the **Bot Id** dir (e.g. `0Xx…`); draft sessions
|
|
291
|
+
under the **bundle api-name** dir.
|
|
292
|
+
|
|
293
|
+
### Captured record (one JSON per trial)
|
|
294
|
+
|
|
295
|
+
`run-trials.sh` writes `<outdir>/<subject>/<utt>/trial-<n>/trial.json`:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"subject": "legacy|draft",
|
|
300
|
+
"utterance_id": "<utt_id>",
|
|
301
|
+
"trial": 1,
|
|
302
|
+
"sessionId": "…", "planId": "…",
|
|
303
|
+
"response": "…full message text…",
|
|
304
|
+
"isContentSafe": true,
|
|
305
|
+
"result": [], "citedReferences": [],
|
|
306
|
+
"latency_ms": 6731
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The `planId` is the join key: the analyzer resolves the plan trace from it.
|
|
311
|
+
|
|
312
|
+
## Rubric #1 — Trace / action equality (mechanical)
|
|
313
|
+
|
|
314
|
+
The CLI *response* does not expose the plan trace, and `result[]` is empty even
|
|
315
|
+
when an action ran. But **`sf agent preview end` writes a full local plan trace
|
|
316
|
+
to disk, keyed by `planId`** — the authoritative, zero-network, type-agnostic
|
|
317
|
+
source for **both** legacy and NGA:
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
.sfdx/agents/<agentId>/sessions/<sessionId>/traces/<planId>.json
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`scripts/upgrade-legacy-nga-analyze.py` reads it — this part is **mechanical**
|
|
324
|
+
(objective equality of structured identifiers), so the script does it:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
EVAL_ORG=<org-alias> \
|
|
328
|
+
python3 scripts/upgrade-legacy-nga-analyze.py <run_dir> <utt_id> [expected_subagent] [expected_actions_csv]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The trace file is a `PlanSuccessResponse` with `topic`/`intent` (the selected
|
|
332
|
+
subagent) and an ordered `plan[]`. The steps that matter:
|
|
333
|
+
|
|
334
|
+
- `FunctionStep.function.{name,input,output}` — each **action call** in order,
|
|
335
|
+
with bound inputs and `output.__action_execution_status__`. Same record shape
|
|
336
|
+
for any action type (Apex / Flow / Prompt / utility) — generic ordered-action
|
|
337
|
+
evidence.
|
|
338
|
+
- **Routing hop:** the NGA draft uses `TransitionStep.data.{from_agent,to_agent}`;
|
|
339
|
+
legacy ReAct uses `UpdateTopicStep` + the top-level `topic` field (no
|
|
340
|
+
`TransitionStep`). The analyzer normalizes both into one
|
|
341
|
+
`Agent Router → <subagent>` row.
|
|
342
|
+
|
|
343
|
+
### Two trace-shape differences (both handled in the analyzer)
|
|
344
|
+
|
|
345
|
+
1. **Routing step:** draft = `TransitionStep`; legacy = `UpdateTopicStep` +
|
|
346
|
+
`topic`. The analyzer synthesizes `("Agent Router", topic)` when there is no
|
|
347
|
+
`TransitionStep`.
|
|
348
|
+
2. **Name suffixes:** legacy `topic`/action names carry a definition-id suffix
|
|
349
|
+
appended to the base name (e.g.
|
|
350
|
+
`Get_All_Loan_Application_Details_179KX000000YehM`); the draft uses the clean
|
|
351
|
+
base name. The suffix format is **not fixed**, so a regex strip is unreliable.
|
|
352
|
+
Instead names match by **substring containment in either direction**
|
|
353
|
+
(`name_match()`, case-insensitive) — two names denote the same entity when one
|
|
354
|
+
contains the other. Names are kept raw in the report; only the match is
|
|
355
|
+
suffix-tolerant.
|
|
356
|
+
|
|
357
|
+
### Legacy trace needs CLI ≥ 2.149.9
|
|
358
|
+
|
|
359
|
+
The **legacy** trace only populates on `sf` CLI ≥ 2.149.9 (`@salesforce/agents ≥
|
|
360
|
+
2.0.4`), which fetches it from the v1.1 preview plans endpoint. On older CLIs the
|
|
361
|
+
legacy trace is written as `{}` and `parse_trace()` reports `empty plan ({}) —
|
|
362
|
+
SDK too old`. The draft trace populates on all supported versions. (See
|
|
363
|
+
[Prerequisites](#prerequisites).)
|
|
364
|
+
|
|
365
|
+
### The oracle triangle — legacy is a BASELINE, not the source of truth
|
|
366
|
+
|
|
367
|
+
Legacy is **not** the pass condition. The legacy agent can itself be defective
|
|
368
|
+
(miss a grounding action, route poorly, leak a value), and a migration that
|
|
369
|
+
*fixes* such a defect must not be flagged as a regression. So Rubric #1 compares
|
|
370
|
+
**three** references and classifies the triangle:
|
|
371
|
+
|
|
372
|
+
| reference | what it is | authority |
|
|
373
|
+
|---|---|---|
|
|
374
|
+
| **spec** | `expected_subagent` / `expected_actions` from the suite, authored from intent | **ORACLE** (intended behavior) |
|
|
375
|
+
| **legacy** | what the old agent actually did (its local trace) | descriptive **baseline** — can be wrong |
|
|
376
|
+
| **draft** | what the NGA under test actually did (its local trace) | **under test** |
|
|
377
|
+
|
|
378
|
+
The analyzer computes a route+action **signature** = `[subagent] + ordered
|
|
379
|
+
actions` for each reference (compared with suffix-tolerant `name_match()`) and
|
|
380
|
+
emits one **triangle verdict**:
|
|
381
|
+
|
|
382
|
+
| legacy vs spec | draft vs spec | verdict | meaning / action |
|
|
383
|
+
|---|---|---|---|
|
|
384
|
+
| match | match | **PASS** | all three agree — nothing to do |
|
|
385
|
+
| match | differ | **DRAFT-REGRESSION** | legacy was right, the draft broke it → fix the draft |
|
|
386
|
+
| differ | match | **PASS + LEGACY-DEFECT** | draft matches intent; legacy was wrong → migration *fixed* a legacy bug |
|
|
387
|
+
| differ | differ (l == d) | **SPEC-REVIEW** | legacy and draft agree with each other but not the spec → spec is likely stale; a human re-confirms intent |
|
|
388
|
+
| differ | differ (l ≠ d) | **ADJUDICATE** | three-way disagreement → hand traces + responses to the LLM judge to decide |
|
|
389
|
+
|
|
390
|
+
If **no spec** is supplied for an utterance, the verdict is **SPEC-UNVERIFIED**:
|
|
391
|
+
the analyzer falls back to the legacy-vs-draft cross-check and notes the judge
|
|
392
|
+
must decide (no oracle to anchor on). An **`inferred`** spec is different — it is
|
|
393
|
+
present, so the triangle *does* fire, but the verdict holds only *pending the
|
|
394
|
+
user's validation*; flag those rows and do not stop the loop while any tested
|
|
395
|
+
utterance is still `inferred` (see
|
|
396
|
+
[Iteration loop and stop condition](#iteration-loop-and-stop-condition)). Supply
|
|
397
|
+
or confirm `expected_*` for every utterance you care about so the triangle can
|
|
398
|
+
anchor on truth.
|
|
399
|
+
|
|
400
|
+
### Accepted baseline & drift (unintended change across iterations)
|
|
401
|
+
|
|
402
|
+
The spec catches *intended* correctness; a separate **accepted baseline** catches
|
|
403
|
+
*unintended* drift between iterations. The analyzer maintains a JSON file
|
|
404
|
+
(`EVAL_BASELINE`, default `agent-eval/baseline.json`) mapping each utterance to
|
|
405
|
+
the last **human-approved** draft signature:
|
|
406
|
+
|
|
407
|
+
```json
|
|
408
|
+
{ "mortgage_faq": { "subagent": "Financial_Knowledge_Base_Search",
|
|
409
|
+
"actions": ["Get_Financial_Customer_Details","Answer_Financial_FAQs"],
|
|
410
|
+
"approved_at": "…", "approved_from_run": "…" } }
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
- **Every run** diffs the current draft signature against the recorded baseline
|
|
414
|
+
and reports **Drift: none / DETECTED / no baseline yet**. Drift fires even
|
|
415
|
+
when the draft still matches the spec — it flags *any* change from the last
|
|
416
|
+
approved behavior, catching an accidental regression from an unrelated `.agent`
|
|
417
|
+
edit.
|
|
418
|
+
- **Deliberate re-baseline** is explicit and human-gated: run with `REBASELINE=1`
|
|
419
|
+
to record the current draft signature as the new approved truth and exit
|
|
420
|
+
(writes nothing else). Use it the first time an utterance passes, and again
|
|
421
|
+
whenever the authored intent **intentionally** changes.
|
|
422
|
+
- The baseline stores **clean** draft names (no legacy suffix), and drift is
|
|
423
|
+
compared with `name_match()`, so it is robust to the same suffix noise.
|
|
424
|
+
|
|
425
|
+
Together: the **triangle** answers "is the draft doing the *intended* thing
|
|
426
|
+
(regardless of legacy)?"; **drift** answers "did the draft change from what we
|
|
427
|
+
last approved?". A healthy iteration ends `PASS` (or `PASS + LEGACY-DEFECT`) with
|
|
428
|
+
`Drift: none`.
|
|
429
|
+
|
|
430
|
+
## Rubric #2 — Response discrepancy (LLM judge)
|
|
431
|
+
|
|
432
|
+
Response comparison is a **semantic judgment and MUST be performed by an LLM**,
|
|
433
|
+
never by regex/heuristics. A scripted approach is actively harmful here: it
|
|
434
|
+
produces false diffs (e.g. tokenizing `617,` in prose ≠ `617`) and cannot judge
|
|
435
|
+
tone, grounding, or spec compliance. It would also force agent/utterance-specific
|
|
436
|
+
vocabulary into the analyzer, which is prohibited.
|
|
437
|
+
|
|
438
|
+
**Mechanism.** The analyzer does **not** score responses. It emits the verbatim
|
|
439
|
+
responses for all N trials (both subjects) followed by a fixed, domain-free
|
|
440
|
+
**analysis prompt** (pre-loaded with the triangle verdict + drift status as
|
|
441
|
+
context). An LLM judge — the agent running the skill, or a dedicated judge
|
|
442
|
+
subagent — reads the responses + the Rubric #1 trace facts and completes it:
|
|
443
|
+
|
|
444
|
+
1. **Intra-trial consistency** — per subject, are the N trials materially
|
|
445
|
+
equivalent (same facts, acceptable wording variance) or divergent? Pick a
|
|
446
|
+
canonical trial for each side.
|
|
447
|
+
2. **Data fidelity** — do legacy and draft agree on every concrete fact
|
|
448
|
+
(numbers, amounts, dates, statuses, named entities, IDs)? List any value the
|
|
449
|
+
draft changed, dropped, or added. A factual mismatch is a **REGRESSION**, not
|
|
450
|
+
a style diff.
|
|
451
|
+
3. **Formatting** — structural differences: bullets/headers/lists, number/money
|
|
452
|
+
formatting, any leaked internal field/action API names.
|
|
453
|
+
4. **Verbosity & tone** — length, clarity, empathy, appropriate closing offer.
|
|
454
|
+
5. **Spec compliance** — does each side satisfy the utterance's intent? If the
|
|
455
|
+
action sets differ (e.g. one side grounded via a retrieval action, the other
|
|
456
|
+
answered from model knowledge), judge whether that changes answer
|
|
457
|
+
quality/trustworthiness.
|
|
458
|
+
6. **Classification** — `EQUIVALENT` | `IMPROVED` | `ACCEPTABLE-DIFF` |
|
|
459
|
+
`REGRESSION`, with a one-line justification and, if not
|
|
460
|
+
`EQUIVALENT`/`IMPROVED`, a concrete recommended `.agent` change.
|
|
461
|
+
|
|
462
|
+
`isContentSafe` (per trial) and the Rubric #1 trace verdicts are the only
|
|
463
|
+
mechanical inputs the judge is handed; everything evaluative is the judge's call.
|
|
464
|
+
Classification meanings:
|
|
465
|
+
|
|
466
|
+
- `EQUIVALENT` — same data, differences immaterial.
|
|
467
|
+
- `IMPROVED` — draft better (clearer/warmer/better formatted/better grounded),
|
|
468
|
+
data intact.
|
|
469
|
+
- `ACCEPTABLE-DIFF` — differs but within tolerance.
|
|
470
|
+
- `REGRESSION` — draft loses/changes data, breaks a policy, is unsafe, or
|
|
471
|
+
routes/acts differently in a way that degrades the answer.
|
|
472
|
+
|
|
473
|
+
## Trials, caching, and the canonical response
|
|
474
|
+
|
|
475
|
+
- Run `trials` (default 3) per (subject, utterance). Record all N.
|
|
476
|
+
- **Intra-trial variance is an LLM judgment, not a regex diff.** The analyzer
|
|
477
|
+
presents all N verbatim responses per subject; the Rubric #2 judge decides
|
|
478
|
+
whether they are identical / materially equivalent / divergent and picks the
|
|
479
|
+
canonical trial (materially-equivalent set → trial 1; otherwise the
|
|
480
|
+
representative/modal variant, flagging `HIGH-VARIANCE`). Do **not** extract
|
|
481
|
+
"facts" with hardcoded field labels or token regexes — it is agent-specific
|
|
482
|
+
(prohibited) and produces false diffs.
|
|
483
|
+
- **Legacy caching:** legacy is frozen, so cache its per-trial results and reuse
|
|
484
|
+
across draft edits. Cache key =
|
|
485
|
+
`sha256(legacy_api_name + active_BotVersionId + utterance_text + trial_index)`.
|
|
486
|
+
Store `active_BotVersionId` in cache metadata and **invalidate** if it changes
|
|
487
|
+
(legacy got re-published). `--refresh-legacy` forces a re-run.
|
|
488
|
+
- **Draft results are never cached** — re-collect them on every iteration
|
|
489
|
+
because the `.agent` file changes.
|
|
490
|
+
|
|
491
|
+
Three separate artifacts, three separate concerns — do not conflate them: the
|
|
492
|
+
**legacy cache** (don't re-run frozen legacy), the **accepted baseline**
|
|
493
|
+
(draft-over-time drift), and the **spec** (intent).
|
|
494
|
+
|
|
495
|
+
## The report
|
|
496
|
+
|
|
497
|
+
The analyzer writes `<run_dir>/report.md` (and prints it): the mechanical part +
|
|
498
|
+
the raw responses + the Rubric #2 prompt. The LLM judge's completed analysis is
|
|
499
|
+
appended (or captured alongside), as is the spec-validation block below — both are
|
|
500
|
+
skill/judge-supplied, since the mechanical analyzer has no `spec_status` notion (it
|
|
501
|
+
receives only `expected_*` as arguments).
|
|
502
|
+
|
|
503
|
+
1. **Header** — org, run id, `trials`, per-trial latency, per-trial
|
|
504
|
+
`isContentSafe`, per-trial word count, legacy/draft planIds.
|
|
505
|
+
2. **Spec validation (inferred specs)** — every utterance whose `spec_status` is
|
|
506
|
+
`inferred`, each with its `text` and the skill's **proposed** `expected_subagent`
|
|
507
|
+
+ `expected_actions` plus a one-line note on the intent they were inferred from
|
|
508
|
+
(the utterance + the agent's topic/action catalog — never copied from legacy).
|
|
509
|
+
The user confirms or corrects them in one pass; on confirm, flip the entry to
|
|
510
|
+
`confirmed` (and `REBASELINE` it the first time it passes — see
|
|
511
|
+
[drift](#accepted-baseline--drift-unintended-change-across-iterations)). This
|
|
512
|
+
block comes **before** Rubric #1 because any triangle verdict for an `inferred`
|
|
513
|
+
row is provisional until its spec is confirmed; the run cannot be declared done
|
|
514
|
+
while the block is non-empty (see
|
|
515
|
+
[stop condition](#iteration-loop-and-stop-condition)). When every spec is
|
|
516
|
+
already `confirmed` the block is empty and the report says so.
|
|
517
|
+
3. **Rubric #1 (mechanical)** — per-subject trace table
|
|
518
|
+
(subagent / ordered actions / verdict) + legacy-vs-draft cross-check + oracle
|
|
519
|
+
triangle + drift + optional draft action-input binding.
|
|
520
|
+
4. **Responses (verbatim)** — all N trials for both subjects.
|
|
521
|
+
5. **Rubric #2 (LLM judge)** — the analysis prompt and the judge's completed
|
|
522
|
+
answer.
|
|
523
|
+
6. **Actionable diffs** — consolidated proposed `.agent` edits, each linked to
|
|
524
|
+
the utterance(s) that motivated it.
|
|
525
|
+
|
|
526
|
+
## Iteration loop and stop condition
|
|
527
|
+
|
|
528
|
+
```text
|
|
529
|
+
compile draft (index-agent.mjs) ──► collect draft trials (N×)
|
|
530
|
+
▲ │
|
|
531
|
+
│ ▼
|
|
532
|
+
edit .agent ◄── findings ◄── report ◄── compare vs cached legacy trials
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Stop when** every utterance's **triangle verdict** is `PASS` or
|
|
536
|
+
`PASS + LEGACY-DEFECT` (no open `DRAFT-REGRESSION`; any `SPEC-REVIEW` /
|
|
537
|
+
`ADJUDICATE` resolved by a human/judge decision, spec updated if it was stale),
|
|
538
|
+
**every tested utterance's `spec_status` is `confirmed`** (no `inferred` spec still
|
|
539
|
+
awaiting the user's validation), **drift** is `none` against a
|
|
540
|
+
deliberately-approved baseline, and Rubric #2 is `EQUIVALENT` / `IMPROVED` (no open
|
|
541
|
+
`REGRESSION`). A `LEGACY-DEFECT` is a **pass** —
|
|
542
|
+
do not chase legacy parity when legacy was the one that was wrong.
|
|
543
|
+
|
|
544
|
+
**Draft-only throughout: no publish/activate without explicit human approval.**
|
|
545
|
+
Deploy the draft bundle (`sf project deploy start`) only if a preview needs it to
|
|
546
|
+
pick up on-disk edits — confirm once whether an `--authoring-bundle` preview
|
|
547
|
+
reflects the `.agent` immediately, and skip the deploy if it does.
|