@salesforce/afv-skills 1.66.0 → 1.68.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/README.md +55 -5
- package/package.json +2 -4
- package/skills/agentforce-offline-experimentation-run/SKILL.md +26 -49
- package/skills/agentforce-offline-experimentation-run/scripts/bt_compare.py +81 -0
- package/skills/agentforce-offline-experimentation-run/scripts/bt_fetch.py +358 -0
- package/skills/agentforce-offline-experimentation-run/scripts/bt_patch_agent.py +110 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/__init__.py +34 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/agentpatch.py +483 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/agentsrc.py +565 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/cache.py +95 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/compare.py +617 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/fetch.py +1012 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/judge.py +148 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/messages.py +92 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/patch.py +327 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/redact.py +40 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/results.py +288 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/scoring.py +340 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/sfap.py +238 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/splits.py +136 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/stdm.py +98 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/tokens.py +173 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/units.py +199 -0
- package/skills/agentforce-offline-experimentation-run/scripts/btlib/versions.py +194 -0
- package/skills/agentforce-offline-experimentation-run/scripts/build_report.py +366 -378
- package/skills/agentforce-offline-experimentation-run/scripts/resolve-token.sh +14 -4
- package/skills/agentforce-offline-experimentation-run/scripts/sfap_replay.py +472 -796
- package/skills/commerce-react-storefront-create/SKILL.md +350 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +2 -2
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +28 -16
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +18 -1
- package/skills/dx-lifecycle-center/SKILL.md +5 -2
- package/skills/dx-lifecycle-center/assets/widgets/connect-connected/connect-connected.json +65 -0
- package/skills/dx-lifecycle-center/assets/widgets/connect-connected/schema.json +32 -0
- package/skills/dx-lifecycle-center/assets/widgets/connect-sign-in/connect-sign-in.json +57 -0
- package/skills/dx-lifecycle-center/assets/widgets/connect-sign-in/schema.json +21 -0
- package/skills/dx-lifecycle-center/examples/workflows.md +20 -6
- package/skills/dx-lifecycle-center/references/connect-org.md +21 -2
- package/skills/dx-lifecycle-center/references/deploy.md +2 -2
- package/skills/dx-lifecycle-center/references/list-orgs.md +1 -1
- package/skills/dx-lifecycle-center/scripts/card-policy.jq +71 -0
- package/skills/dx-lifecycle-center/scripts/fill-widget.jq +29 -0
- package/skills/dx-lifecycle-center/scripts/widget.sh +28 -0
- package/skills/dx-org-devhub-configure/SKILL.md +85 -84
- package/skills/dx-org-devhub-configure/examples/status-output.md +25 -2
- package/skills/dx-org-devhub-configure/scripts/devhub.sh +265 -140
- package/skills/dx-org-devhub-configure/scripts/test-devhub.sh +100 -15
- package/skills/dx-packaging-move-to-2gp-migrate/SKILL.md +89 -0
- package/skills/dx-packaging-move-to-2gp-migrate/references/api-operations.md +164 -0
- package/skills/dx-scratch-def-configure/SKILL.md +163 -0
- package/skills/dx-scratch-def-configure/examples/confirm-card.md +53 -0
- package/skills/dx-scratch-def-configure/examples/report-recipes.md +100 -0
- package/skills/dx-scratch-def-configure/references/features.json +3270 -0
- package/skills/dx-scratch-def-configure/references/project-scratch-def.schema.json +1841 -0
- package/skills/dx-scratch-def-configure/references/settings-map.json +682 -0
- package/skills/dx-scratch-def-configure/scripts/lib_scratch_def.py +1306 -0
- package/skills/dx-scratch-def-configure/scripts/requirements.txt +3 -0
- package/skills/dx-scratch-def-configure/scripts/scrape_config_values.py +848 -0
- package/skills/education-cloud-domain-configure/SKILL.md +22 -24
- package/skills/education-cloud-domain-configure/references/dependent-features.md +9 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +15 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +8 -0
- package/skills/education-cloud-domain-configure/references/setup-widgets.md +123 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +1 -1
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +1 -1
- package/skills/experience-design-validate/SKILL.md +1 -1
- package/skills/experience-search-coordinate/SKILL.md +19 -13
- package/skills/experience-search-coordinate/references/content-route.md +79 -1
- package/skills/experience-search-coordinate/references/media-route.md +30 -4
- package/skills/experience-ui-bundle-features-generate/SKILL.md +1 -1
- package/skills/experience-ui-bundle-mfa-configure/SKILL.md +1 -1
- package/skills/field-service-data-capture-migrate/SKILL.md +1 -1
- package/skills/platform-apex-generate/SKILL.md +14 -11
- package/skills/platform-apex-test-generate/SKILL.md +2 -1
- package/skills/platform-datamask-run/SKILL.md +52 -71
- package/skills/platform-datamask-run/references/api-surface.md +10 -4
- package/skills/platform-datamask-run/references/policy-authoring.md +56 -7
- package/skills/platform-datamask-run/references/run-and-abort.md +17 -10
- package/skills/platform-datamask-run/scripts/poll-job.sh +15 -25
- package/skills/platform-dataspace-access-configure/SKILL.md +19 -1
- package/skills/platform-metadata-deploy/README.md +1 -1
- package/skills/platform-metadata-deploy/SKILL.md +114 -15
- package/skills/platform-metadata-deploy/references/agent-deployment-guide.md +3 -4
- package/skills/platform-metadata-deploy/references/orchestration.md +2 -3
- package/skills/platform-metadata-discovery-get/SKILL.md +185 -0
- package/skills/platform-metadata-discovery-get/references/api-map.md +25 -0
- package/skills/platform-metadata-discovery-get/references/h360-routes.md +30 -0
- package/skills/platform-metadata-discovery-get/references/scoped-soql.md +52 -0
- package/skills/platform-metadata-grounding-get/SKILL.md +170 -0
- package/skills/platform-policy-rule-generate/SKILL.md +84 -232
- package/skills/platform-policy-rule-generate/references/condition-model.md +47 -0
- package/skills/platform-policy-rule-generate/references/condition-templates.md +89 -0
- package/skills/platform-policy-rule-generate/references/conjunction-compiler.md +25 -0
- package/skills/platform-policy-rule-generate/references/conjunction-expression.md +22 -0
- package/skills/platform-policy-rule-generate/references/consumer-all.md +9 -0
- package/skills/platform-policy-rule-generate/references/consumer-datacloud-ui.md +91 -0
- package/skills/platform-policy-rule-generate/references/deploy-errors.md +24 -36
- package/skills/platform-policy-rule-generate/references/examples/ExistsJoinRLS/package.xml +12 -0
- package/skills/platform-policy-rule-generate/references/examples/ExistsJoinRLS/policyRuleDefinitionSets/ExistsJoinRLS.policyRuleDefinitionSet +6 -0
- package/skills/platform-policy-rule-generate/references/examples/ExistsJoinRLS/policyRuleDefinitions/ExistsJoinRLS_Rule_1.policyRuleDefinition +91 -0
- package/skills/platform-policy-rule-generate/references/examples/FieldReadDeny/package.xml +12 -0
- package/skills/platform-policy-rule-generate/references/examples/FieldReadDeny/policyRuleDefinitionSets/FieldReadDeny.policyRuleDefinitionSet +6 -0
- package/skills/platform-policy-rule-generate/references/examples/FieldReadDeny/policyRuleDefinitions/FieldReadDeny_Rule_1.policyRuleDefinition +72 -0
- package/skills/platform-policy-rule-generate/references/examples/LastCharactersFieldMasking/package.xml +12 -0
- package/skills/platform-policy-rule-generate/references/examples/LastCharactersFieldMasking/policyRuleDefinitionSets/LastCharactersFieldMasking.policyRuleDefinitionSet +6 -0
- package/skills/platform-policy-rule-generate/references/examples/LastCharactersFieldMasking/policyRuleDefinitions/LastCharactersFieldMasking_Rule_1.policyRuleDefinition +77 -0
- package/skills/platform-policy-rule-generate/references/examples/PrincipalFieldMatchRLS/package.xml +12 -0
- package/skills/platform-policy-rule-generate/references/examples/PrincipalFieldMatchRLS/policyRuleDefinitionSets/PrincipalFieldMatchRLS.policyRuleDefinitionSet +6 -0
- package/skills/platform-policy-rule-generate/references/examples/PrincipalFieldMatchRLS/policyRuleDefinitions/PrincipalFieldMatchRLS_Rule_1.policyRuleDefinition +77 -0
- package/skills/platform-policy-rule-generate/references/examples/TaggedObjectAccess/package.xml +12 -0
- package/skills/platform-policy-rule-generate/references/examples/TaggedObjectAccess/policyRuleDefinitionSets/TaggedObjectAccess.policyRuleDefinitionSet +4 -0
- package/skills/platform-policy-rule-generate/references/examples/TaggedObjectAccess/policyRuleDefinitions/TaggedObjectAccess_Rule_1.policyRuleDefinition +65 -0
- package/skills/platform-policy-rule-generate/references/headless-transport.md +87 -0
- package/skills/platform-policy-rule-generate/references/policy-schema-full.md +17 -10
- package/skills/platform-policy-rule-generate/references/policy-shapes.md +74 -0
- package/skills/platform-policy-rule-generate/references/principal-expression.md +31 -0
- package/skills/platform-policy-rule-generate/references/reference-resolution.md +51 -0
- package/skills/platform-policy-rule-generate/references/resource-expression.md +48 -0
- package/skills/platform-policy-rule-generate/references/runtime-context.md +9 -0
- package/skills/platform-policy-rule-generate/references/templates-access.md +11 -9
- package/skills/platform-policy-rule-generate/references/templates-advanced.md +4 -2
- package/skills/platform-policy-rule-generate/references/templates-record.md +37 -22
- package/skills/platform-policy-rule-generate/references/templates-transform.md +10 -8
- package/skills/platform-policy-rule-generate/references/templates.md +2 -2
- package/skills/platform-policy-rule-generate/references/ui-compatibility.md +35 -17
- package/skills/platform-report-generate/SKILL.md +9 -4
- package/skills/platform-report-generate/references/chart-types.md +26 -2
- package/skills/platform-report-generate/references/column-names.md +43 -0
- package/skills/platform-report-generate/references/errors-and-troubleshooting.md +1 -1
- package/skills/platform-report-generate/references/verification-checklist.md +10 -2
- package/skills/platform-value-set-generate/SKILL.md +19 -19
- package/skills/platform-widget-generate/SKILL.md +25 -51
- package/skills/platform-widget-generate/examples/formulas.json +19 -2
- package/skills/platform-widget-generate/examples/localized-labels.json +57 -0
- package/skills/platform-widget-generate/references/widget-actions.md +29 -0
- package/skills/platform-widget-generate/references/widget-bundle-validation.md +1 -0
- package/skills/platform-widget-generate/references/widget-formulas.md +24 -7
- package/skills/platform-widget-generate/references/widget-gotchas.md +10 -0
- package/skills/platform-widget-generate/references/widget-labels.md +78 -0
- package/skills/platform-widget-generate/references/widget-styling.md +12 -0
- package/skills/sales-call-scoring-configure/SKILL.md +17 -10
- package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +55 -33
- package/skills/service-digital-engagement-channel-configure/SKILL.md +11 -7
- package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +33 -0
- package/skills/service-digital-engagement-channel-configure/references/channel_settings.md +123 -16
- package/skills/service-digital-engagement-deployment-configure/SKILL.md +37 -100
- package/skills/service-digital-engagement-deployment-configure/assets/esd_web_update_template.xml +83 -8
- package/skills/service-digital-engagement-deployment-configure/examples/esd_web_prechat_fields.xml +74 -0
- package/skills/service-digital-engagement-deployment-configure/references/connect_api_creation.md +13 -3
- package/skills/service-digital-engagement-deployment-configure/references/deployment_settings.md +13 -78
- package/skills/service-digital-engagement-deployment-configure/references/onboarding_contract.md +5 -6
- package/skills/service-digital-engagement-deployment-configure/references/prechat_forms.md +245 -0
- package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +164 -120
- package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +61 -37
- package/skills/service-itsm-agentic-setup-incident-management/references/audit-tracking.md +42 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/broadcast-communications.md +23 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/email-to-incident.md +23 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-dashboards.md +25 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +30 -146
- package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +32 -136
- package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +32 -155
- package/skills/service-itsm-agentic-setup-incident-management/references/manage-incidents-with-ai.md +45 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/priority-matrix.md +32 -0
- package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +30 -140
- package/skills/service-itsm-agentic-setup-incident-management/references/sla-milestones.md +110 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch an agent's STDM sessions with read-only anonymous Apex: nothing is deployed.
|
|
3
|
+
|
|
4
|
+
python3 scripts/bt_fetch.py --org <alias> --probe [--agent NAME --start DATE --end DATE]
|
|
5
|
+
python3 scripts/bt_fetch.py --org <alias> --agent GPT41_Shopper_Agent \\
|
|
6
|
+
--start 2026-09-01 --end 2026-10-08 --out-dir runs/fetch-shop [--exclude-sessions FILE]
|
|
7
|
+
python3 scripts/sfap_replay.py --details runs/fetch-shop/details.json ...
|
|
8
|
+
|
|
9
|
+
Every query runs with `sf apex run --json` in --org, the org that holds the STDM data (with Data
|
|
10
|
+
Cloud One, not the agent's own org), in --dataspace. Each generated script is checked to be
|
|
11
|
+
read-only before it runs (SELECT queries only: no DML, no callouts) and is kept in OUT/apex/.
|
|
12
|
+
|
|
13
|
+
A data space that names the STDM DMOs and fields its own way (not ssot__AiAgentSession__dlm,
|
|
14
|
+
ssot__Id__c, ...) takes --stdm-names FILE, a bt.stdm_names/1 names map (references/replay-
|
|
15
|
+
procedure.md 1.2): the queries are sent in its names. Without it, nothing is renamed.
|
|
16
|
+
|
|
17
|
+
--probe counts the rows of each STDM DMO (and the agent's participant rows, and the sessions in
|
|
18
|
+
the window, when given) and prints each error as the org words it: run it first, and quote it
|
|
19
|
+
in an access request. With --stdm-names it also runs every query a fetch runs, with keys no row
|
|
20
|
+
has, and names each that fails.
|
|
21
|
+
|
|
22
|
+
The fetch finds the agent's sessions started in [--start, --end] with at least one turn, keeps
|
|
23
|
+
the --channels wanted (by default all but Builder*, Agent Builder's test traffic), drops the
|
|
24
|
+
sessions of --exclude-sessions and, with --split, those of the other split, then takes up to
|
|
25
|
+
--max-sessions in salted-hash order (or --order recent) and fetches them, --batch-size sessions
|
|
26
|
+
per Apex run.
|
|
27
|
+
An interrupted fetch rerun into the same --out-dir does not fetch its finished batches again.
|
|
28
|
+
|
|
29
|
+
Outputs in --out-dir (0700, files 0600): details.json (ConversationData, the --details input of
|
|
30
|
+
sfap_replay.py), sessions.json (SessionSummary), fetch.json (bt.fetch/1: parameters, counts,
|
|
31
|
+
digests; no session ids), apex/ (the scripts run). details.json holds user utterances and the
|
|
32
|
+
scripts hold session ids: keep them out of git.
|
|
33
|
+
|
|
34
|
+
--emit-kit DIR writes the same fetch as a kit for someone who can read the STDM data: this script,
|
|
35
|
+
the modules it runs, run_kit.sh with the parameters set (and the --stdm-names file), and a README.
|
|
36
|
+
Its out/ comes back.
|
|
37
|
+
|
|
38
|
+
Exit 0: done. 1: the fetch failed, or --probe found a required DMO unreadable (or, with
|
|
39
|
+
--stdm-names, a query that fails). 2: usage.
|
|
40
|
+
130: interrupted (rerun into the same --out-dir to go on).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
import argparse
|
|
44
|
+
import hashlib
|
|
45
|
+
import json
|
|
46
|
+
import os
|
|
47
|
+
import shlex
|
|
48
|
+
import shutil
|
|
49
|
+
import sys
|
|
50
|
+
import tempfile
|
|
51
|
+
import traceback
|
|
52
|
+
from datetime import datetime, timezone
|
|
53
|
+
|
|
54
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
55
|
+
|
|
56
|
+
from btlib import SCHEMA_KIT # noqa: E402
|
|
57
|
+
from btlib import fetch as fetchlib # noqa: E402
|
|
58
|
+
from btlib import splits as splitlib # noqa: E402
|
|
59
|
+
|
|
60
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
61
|
+
KIT_MODULES = ("__init__.py", "fetch.py", "redact.py", "splits.py", "stdm.py") # all bt_fetch.py imports
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def parse_args(argv=None):
|
|
65
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
66
|
+
mode = p.add_mutually_exclusive_group()
|
|
67
|
+
mode.add_argument("--probe", action="store_true", help="count the rows each STDM DMO gives, print the errors")
|
|
68
|
+
mode.add_argument("--emit-kit", metavar="DIR", help="write this fetch as a kit for someone with STDM access")
|
|
69
|
+
p.add_argument("--org", help="alias or username of the org holding the STDM data")
|
|
70
|
+
p.add_argument("--dataspace", default="default", help="Data Cloud data space (default %(default)s)")
|
|
71
|
+
p.add_argument("--stdm-names", metavar="FILE",
|
|
72
|
+
help="the data space's own DMO and field names, a bt.stdm_names/1 file (default: the "
|
|
73
|
+
"ssot__ names, as they are)")
|
|
74
|
+
p.add_argument("--agent", help="the agent's API name (participant ssot__AiAgentApiName__c)")
|
|
75
|
+
p.add_argument("--start", help="sessions started from this date or ISO 8601 time (UTC)")
|
|
76
|
+
p.add_argument("--end", help="... up to this one; a date alone includes its whole day")
|
|
77
|
+
p.add_argument("--out-dir", metavar="DIR", help="where the fetch goes (created 0700; must not hold a fetch)")
|
|
78
|
+
p.add_argument("--max-sessions", type=int, default=fetchlib.MAX_SESSIONS,
|
|
79
|
+
help="sessions to fetch at most (default %(default)s)")
|
|
80
|
+
p.add_argument("--channels", help="comma-separated channel names to keep (default: all but Builder*)")
|
|
81
|
+
p.add_argument("--exclude-sessions", metavar="FILE",
|
|
82
|
+
help="never fetch these sessions: a file that names them, e.g. one ID per line, a "
|
|
83
|
+
"units.jsonl, a sessions or details JSON, a replay CSV (references/splits.md)")
|
|
84
|
+
p.add_argument("--val-fraction", type=float, metavar="F", help="session split as sfap_replay.py labels it")
|
|
85
|
+
p.add_argument("--split", choices=splitlib.SPLITS, help="fetch only this split (needs --val-fraction)")
|
|
86
|
+
p.add_argument("--split-salt", default=splitlib.DEFAULT_SALT, help="salt of the split (default %(default)s)")
|
|
87
|
+
p.add_argument("--order", choices=("hash", "recent"), default="hash",
|
|
88
|
+
help="which sessions come first when there are more than --max-sessions: a salted hash "
|
|
89
|
+
"(an even sample, stable when the window grows) or the newest (default %(default)s)")
|
|
90
|
+
p.add_argument("--sample-salt", default=fetchlib.SAMPLE_SALT, help="salt of --order hash (default %(default)s)")
|
|
91
|
+
p.add_argument("--batch-size", type=int, default=fetchlib.BATCH_SIZE,
|
|
92
|
+
help="sessions per Apex run, halved when a run hits a limit (default %(default)s)")
|
|
93
|
+
p.add_argument("--timeout", type=int, default=fetchlib.APEX_TIMEOUT, help="seconds per Apex run")
|
|
94
|
+
p.add_argument("--dry-run", action="store_true", help="find and select the sessions, print the counts, write nothing")
|
|
95
|
+
p.add_argument("--overwrite", action="store_true", help="replace an earlier fetch in --out-dir")
|
|
96
|
+
args = p.parse_args(argv)
|
|
97
|
+
if not args.emit_kit and not args.org:
|
|
98
|
+
p.error("--org is required (the org whose STDM data to read)")
|
|
99
|
+
if not args.probe:
|
|
100
|
+
missing = [f"--{n}" for n in ("agent", "start", "end") if not getattr(args, n)]
|
|
101
|
+
if missing:
|
|
102
|
+
p.error(" ".join(missing) + " required")
|
|
103
|
+
if not (args.emit_kit or args.dry_run or args.out_dir):
|
|
104
|
+
p.error("--out-dir is required (or --dry-run)")
|
|
105
|
+
if args.val_fraction is not None and not 0 < args.val_fraction < 1:
|
|
106
|
+
p.error("--val-fraction must be in (0, 1)")
|
|
107
|
+
if args.split and args.val_fraction is None:
|
|
108
|
+
p.error("--split needs --val-fraction (and the --split-salt of the units)")
|
|
109
|
+
if args.max_sessions < 1 or args.batch_size < 1 or args.timeout < 1:
|
|
110
|
+
p.error("--max-sessions, --batch-size and --timeout must be >= 1")
|
|
111
|
+
args.channels = [c.strip() for c in args.channels.split(",") if c.strip()] if args.channels else None
|
|
112
|
+
return args
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def read_excluded(path):
|
|
116
|
+
if not path:
|
|
117
|
+
return frozenset()
|
|
118
|
+
try:
|
|
119
|
+
return frozenset(splitlib.read_session_ids(path))
|
|
120
|
+
except (OSError, splitlib.SplitError) as e:
|
|
121
|
+
raise fetchlib.FetchError(f"--exclude-sessions {path}: {e}") from None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def read_names(path):
|
|
125
|
+
"""The StdmNames of --stdm-names, or None: the default names."""
|
|
126
|
+
return fetchlib.load_names(path) if path else None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def run_probe(args):
|
|
130
|
+
names = read_names(args.stdm_names)
|
|
131
|
+
with tempfile.TemporaryDirectory(prefix="bt_fetch_") as tmp:
|
|
132
|
+
f = fetchlib.Fetcher(args.org, os.path.join(tmp, "apex"), args.dataspace, timeout=args.timeout, names=names)
|
|
133
|
+
got = fetchlib.probe(f, args.agent, args.start, args.end)
|
|
134
|
+
print(f"STDM probe of {args.org}, data space {args.dataspace} (row counts, read-only):")
|
|
135
|
+
labels = {"agent": f"participant rows of {args.agent}", "window": "sessions started in the window"}
|
|
136
|
+
if names is not None:
|
|
137
|
+
print(f"names: {names.source} (sha256 {names.sha256})" + (f": {names.about}" if names.about else ""))
|
|
138
|
+
for key, table in fetchlib.PROBE_DMOS.items(): # table: the default name, quoted
|
|
139
|
+
labels[key] = f"{key} ({names.dmo(table[1:-1])})"
|
|
140
|
+
labels.update((key, "query: " + what) for key, what in fetchlib.SHAPES.items())
|
|
141
|
+
width = max(len(labels.get(key, key)) for key in got)
|
|
142
|
+
for key, res in got.items():
|
|
143
|
+
print(f" {labels.get(key, key):<{width}} " + (f"{res['rows']}" if "rows" in res else f"ERROR {res['error']}"))
|
|
144
|
+
unreadable = [k for k in fetchlib.REQUIRED_DMOS if "error" in got[k]]
|
|
145
|
+
if unreadable:
|
|
146
|
+
print(f"not readable: {', '.join(unreadable)}. A fetch needs them; quote the errors above in an access "
|
|
147
|
+
"request, or hand someone with access a kit (--emit-kit).")
|
|
148
|
+
return 1
|
|
149
|
+
if names is None:
|
|
150
|
+
print("the DMOs a fetch reads are readable")
|
|
151
|
+
return 0
|
|
152
|
+
failed = [what for key, what in fetchlib.SHAPES.items() if "error" in got[key]]
|
|
153
|
+
if failed:
|
|
154
|
+
print(f"these queries fail with the names of {names.source}: {', '.join(failed)}. The errors above "
|
|
155
|
+
"name what the data space lacks; fix the names map, and probe again.")
|
|
156
|
+
return 1
|
|
157
|
+
print("the DMOs a fetch reads are readable, and every query a fetch runs works with these names")
|
|
158
|
+
return 0
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def prepare_out_dir(out_dir, overwrite):
|
|
162
|
+
if os.path.isfile(os.path.join(out_dir, "fetch.json")) and not overwrite:
|
|
163
|
+
raise fetchlib.FetchError(f"{out_dir} already holds a fetch (fetch.json); use a new --out-dir or --overwrite")
|
|
164
|
+
if not os.path.isdir(out_dir):
|
|
165
|
+
os.makedirs(out_dir)
|
|
166
|
+
os.chmod(out_dir, 0o700) # user utterances inside
|
|
167
|
+
elif overwrite: # the old fetch's files go first: one that stops early must not pass for it
|
|
168
|
+
fetchlib.clear_outputs(out_dir)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def run_fetch(args):
|
|
172
|
+
exclude = read_excluded(args.exclude_sessions)
|
|
173
|
+
names = read_names(args.stdm_names)
|
|
174
|
+
kw = dict(channels=args.channels, exclude=exclude, split=args.split, val_fraction=args.val_fraction,
|
|
175
|
+
split_salt=args.split_salt, order=args.order, sample_salt=args.sample_salt,
|
|
176
|
+
max_sessions=args.max_sessions, batch_size=args.batch_size, log=print,
|
|
177
|
+
params={"exclude_sessions": os.path.basename(args.exclude_sessions) if args.exclude_sessions else None,
|
|
178
|
+
"batch_size": args.batch_size})
|
|
179
|
+
if args.dry_run:
|
|
180
|
+
with tempfile.TemporaryDirectory(prefix="bt_fetch_") as tmp:
|
|
181
|
+
f = fetchlib.Fetcher(args.org, os.path.join(tmp, "apex"), args.dataspace, timeout=args.timeout,
|
|
182
|
+
names=names)
|
|
183
|
+
fetchlib.fetch(f, args.agent, args.start, args.end, dry_run=True, **kw)
|
|
184
|
+
print("dry run: nothing fetched")
|
|
185
|
+
return 0
|
|
186
|
+
prepare_out_dir(args.out_dir, args.overwrite)
|
|
187
|
+
f = fetchlib.Fetcher(args.org, os.path.join(args.out_dir, "apex"), args.dataspace, timeout=args.timeout,
|
|
188
|
+
names=names)
|
|
189
|
+
m = fetchlib.fetch(f, args.agent, args.start, args.end, out_dir=args.out_dir, **kw)
|
|
190
|
+
c = m["counts"]
|
|
191
|
+
print(f"fetched {c['fetched']} sessions, {c['turns']} turns, {c['llm_steps']} LLM steps in {c['apex_runs']} "
|
|
192
|
+
f"Apex runs: {os.path.join(args.out_dir, 'details.json')}")
|
|
193
|
+
if os.path.isfile(os.path.join(HERE, "sfap_replay.py")):
|
|
194
|
+
print(f"next: python3 {os.path.join(HERE, 'sfap_replay.py')} --details "
|
|
195
|
+
f"{os.path.join(args.out_dir, 'details.json')} ...")
|
|
196
|
+
else: # a kit
|
|
197
|
+
print(f"send back the whole {args.out_dir} dir (README.md)")
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
_RUN_KIT = """#!/usr/bin/env bash
|
|
202
|
+
# Fetch the STDM sessions of {agent} for a backtest, with read-only anonymous Apex (README.md).
|
|
203
|
+
# Usage: ./run_kit.sh <org-alias> [data-space] [more bt_fetch.py flags: --probe, --dry-run, --overwrite]
|
|
204
|
+
set -euo pipefail
|
|
205
|
+
here="$(cd "$(dirname "$0")" && pwd)"
|
|
206
|
+
if [ $# -lt 1 ]; then
|
|
207
|
+
echo "usage: $0 <org-alias> [data-space] [--probe | --dry-run | --overwrite]" >&2
|
|
208
|
+
exit 2
|
|
209
|
+
fi
|
|
210
|
+
org="$1"; shift
|
|
211
|
+
dataspace={dataspace}
|
|
212
|
+
if [ $# -gt 0 ] && [ "${{1#-}}" = "$1" ]; then dataspace="$1"; shift; fi
|
|
213
|
+
exec python3 "$here/bt_fetch.py" --org "$org" --dataspace "$dataspace" \\
|
|
214
|
+
{flags} \\
|
|
215
|
+
--out-dir "$here/out" "$@"
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
_README = """# STDM fetch kit: {agent}
|
|
219
|
+
|
|
220
|
+
Fetches up to {max_sessions} Agentforce sessions of the agent `{agent}`, started from {start} to
|
|
221
|
+
{end} (UTC), from the Session Tracing data (STDM) of a Data Cloud data space. They are the
|
|
222
|
+
input of an offline backtest. Made by `bt_fetch.py --emit-kit` (code sha {code_sha}).
|
|
223
|
+
|
|
224
|
+
## What it does
|
|
225
|
+
|
|
226
|
+
`run_kit.sh` runs `bt_fetch.py`, which writes small anonymous Apex scripts and runs each with
|
|
227
|
+
`sf apex run --json --file <script> --target-org <your org>`. They only read:
|
|
228
|
+
|
|
229
|
+
- SELECT queries through `ConnectApi.CdpQuery.queryAnsiSqlV2` on the DMOs
|
|
230
|
+
{session}, {participant}, {interaction},
|
|
231
|
+
{message} and {step} (and a row count of
|
|
232
|
+
{generation} and {gateway} with `--probe`);
|
|
233
|
+
- a SOQL query of GenAiPlannerDefinition (Id, DeveloperName, MasterLabel), only when no session
|
|
234
|
+
names the agent.
|
|
235
|
+
|
|
236
|
+
There is no DML, no callout, nothing is deployed and nothing is changed in the org. Each script
|
|
237
|
+
is checked before it runs (`btlib/fetch.py`, `check_read_only`: only the words those reads need,
|
|
238
|
+
and SELECT statements only) and stays in `out/apex/` for you to read.
|
|
239
|
+
|
|
240
|
+
{names_note}## Run it
|
|
241
|
+
|
|
242
|
+
Needs python3 (3.9 or later) and the Salesforce CLI `sf`, logged in to the org that holds the
|
|
243
|
+
STDM data, as a user who can query its data space.
|
|
244
|
+
|
|
245
|
+
./run_kit.sh <org-alias> --probe # can this user read the STDM DMOs? (counts only)
|
|
246
|
+
./run_kit.sh <org-alias> --dry-run # how many sessions would come (counts only)
|
|
247
|
+
./run_kit.sh <org-alias> # the fetch, into out/
|
|
248
|
+
./run_kit.sh <org-alias> <data-space> ... # another data space than {dataspace}
|
|
249
|
+
|
|
250
|
+
{exclude_note}## What to send back
|
|
251
|
+
|
|
252
|
+
The whole `out/` dir: `details.json` (the conversations), `sessions.json`, `fetch.json` (what was
|
|
253
|
+
asked and the counts) and `apex/` (the scripts that ran). The conversations hold what users
|
|
254
|
+
wrote: share them only the way your team shares production transcripts.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
_NAMES_NOTE = """## Names
|
|
258
|
+
|
|
259
|
+
This data space names the STDM DMOs and fields its own way. The queries are sent in the names
|
|
260
|
+
of `stdm_names.json` (sha256 {sha256}){user_agent}.{about} `--probe` also runs every query
|
|
261
|
+
the fetch runs, with keys no row has, and names each that fails.
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def emit_kit(args):
|
|
267
|
+
"""A self-contained copy of this fetch; returns the kit dir."""
|
|
268
|
+
kit = args.emit_kit
|
|
269
|
+
if os.path.exists(kit) and (not os.path.isdir(kit) or os.listdir(kit)):
|
|
270
|
+
raise fetchlib.FetchError(f"{kit} exists and is not an empty dir")
|
|
271
|
+
fetchlib.check_name(args.agent)
|
|
272
|
+
fetchlib.check_name(args.dataspace, "data space name")
|
|
273
|
+
start, end = fetchlib.window_bound(args.start), fetchlib.window_bound(args.end, end=True)
|
|
274
|
+
exclude = read_excluded(args.exclude_sessions)
|
|
275
|
+
names = read_names(args.stdm_names)
|
|
276
|
+
os.makedirs(os.path.join(kit, "btlib"), exist_ok=True)
|
|
277
|
+
os.chmod(kit, 0o700)
|
|
278
|
+
files = {"bt_fetch.py": os.path.join(HERE, "bt_fetch.py")}
|
|
279
|
+
files.update({"btlib/" + name: os.path.join(HERE, "btlib", name) for name in KIT_MODULES})
|
|
280
|
+
if names is not None:
|
|
281
|
+
files["stdm_names.json"] = args.stdm_names
|
|
282
|
+
for rel, src in files.items():
|
|
283
|
+
shutil.copyfile(src, os.path.join(kit, rel))
|
|
284
|
+
if names is not None:
|
|
285
|
+
os.chmod(os.path.join(kit, "stdm_names.json"), 0o600) # data, like the kit's other files
|
|
286
|
+
flags =["--agent", args.agent, "--start", start, "--end", end, "--max-sessions", str(args.max_sessions),
|
|
287
|
+
"--batch-size", str(args.batch_size), "--order", args.order, "--sample-salt", args.sample_salt]
|
|
288
|
+
if args.channels:
|
|
289
|
+
flags += ["--channels", ",".join(args.channels)]
|
|
290
|
+
if args.split:
|
|
291
|
+
flags += ["--val-fraction", repr(args.val_fraction), "--split", args.split, "--split-salt", args.split_salt]
|
|
292
|
+
texts = {"run_kit.sh": _RUN_KIT.format(agent=args.agent, dataspace=shlex.quote(args.dataspace),
|
|
293
|
+
flags=" ".join(shlex.quote(x) for x in flags)
|
|
294
|
+
+ (' --stdm-names "$here/stdm_names.json"' if names else "")
|
|
295
|
+
+ (' --exclude-sessions "$here/exclude_sessions.txt"' if exclude else ""))}
|
|
296
|
+
if exclude: # the ids are the org's own, and the kit goes to people who can read its sessions
|
|
297
|
+
texts["exclude_sessions.txt"] = ("# sessions this fetch must not return (already in the backtest)\n"
|
|
298
|
+
+ "".join(s + "\n" for s in sorted(exclude)))
|
|
299
|
+
dmos = {key: table[1:-1] for key, table in fetchlib.PROBE_DMOS.items()} # the default names, unquoted
|
|
300
|
+
names_note = ""
|
|
301
|
+
if names is not None:
|
|
302
|
+
dmos = {key: names.dmo(name) for key, name in dmos.items()}
|
|
303
|
+
about = names.about + ("" if names.about.endswith((".", "!", "?")) else ".") if names.about else ""
|
|
304
|
+
names_note = _NAMES_NOTE.format(
|
|
305
|
+
sha256=names.sha256, about=f" It says: {about}" if about else "",
|
|
306
|
+
user_agent=f", each with the userAgent `{names.user_agent}`" if names.user_agent else "")
|
|
307
|
+
texts["README.md"] = _README.format(
|
|
308
|
+
agent=args.agent, max_sessions=args.max_sessions, start=start, end=end, dataspace=args.dataspace,
|
|
309
|
+
code_sha=fetchlib.code_sha(), names_note=names_note, **dmos,
|
|
310
|
+
exclude_note=(f"`exclude_sessions.txt` lists {len(exclude)} sessions the backtest already has; the fetch "
|
|
311
|
+
"skips them.\n\n" if exclude else ""))
|
|
312
|
+
for rel, text in texts.items():
|
|
313
|
+
fetchlib.write_private(os.path.join(kit, rel), text)
|
|
314
|
+
os.chmod(os.path.join(kit, "run_kit.sh"), 0o700)
|
|
315
|
+
shas = {}
|
|
316
|
+
for rel in sorted(list(files) + list(texts)):
|
|
317
|
+
with open(os.path.join(kit, rel), "rb") as fh:
|
|
318
|
+
shas[rel] = hashlib.sha256(fh.read()).hexdigest()[:16]
|
|
319
|
+
manifest = {"schema": SCHEMA_KIT, "created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
320
|
+
"params": {"agent": args.agent, "window": [start, end], "dataspace": args.dataspace,
|
|
321
|
+
"max_sessions": args.max_sessions, "batch_size": args.batch_size,
|
|
322
|
+
"channels": args.channels, "order": args.order, "sample_salt": args.sample_salt,
|
|
323
|
+
"split": None if not args.split else {"split": args.split, "val_fraction": args.val_fraction,
|
|
324
|
+
"salt": args.split_salt},
|
|
325
|
+
"excluded_ids": len(exclude)},
|
|
326
|
+
"code": {"fetch_sha256": fetchlib.code_sha()}, "files": shas}
|
|
327
|
+
if names is not None:
|
|
328
|
+
manifest["params"]["stdm_names"] = names.manifest()
|
|
329
|
+
fetchlib.write_private(os.path.join(kit, "kit.json"), json.dumps(manifest, indent=2) + "\n")
|
|
330
|
+
print(f"kit: {kit} ({len(shas) + 1} files); they run ./run_kit.sh <org-alias> and send back {kit}/out/")
|
|
331
|
+
return kit
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def main(argv=None):
|
|
335
|
+
args = parse_args(argv)
|
|
336
|
+
try:
|
|
337
|
+
if args.emit_kit:
|
|
338
|
+
emit_kit(args)
|
|
339
|
+
return 0
|
|
340
|
+
return run_probe(args) if args.probe else run_fetch(args)
|
|
341
|
+
except fetchlib.FetchError as e:
|
|
342
|
+
print(f"error: {fetchlib.mask_ids(str(e))}", file=sys.stderr)
|
|
343
|
+
return 1
|
|
344
|
+
except OSError as e:
|
|
345
|
+
print(f"error: {fetchlib.mask_ids(str(e))}", file=sys.stderr)
|
|
346
|
+
return 1
|
|
347
|
+
except KeyboardInterrupt:
|
|
348
|
+
print("interrupted: rerun into the same --out-dir to go on from the finished batches", file=sys.stderr)
|
|
349
|
+
return 130
|
|
350
|
+
except Exception as e: # a bug, or an answer it did not foresee: no traceback (it can quote session data)
|
|
351
|
+
frame = traceback.extract_tb(e.__traceback__)[-1]
|
|
352
|
+
print(f"error: bt_fetch failed ({type(e).__name__} at {os.path.basename(frame.filename)}:{frame.lineno}: "
|
|
353
|
+
f"{fetchlib.mask_ids(str(e))[:200]})", file=sys.stderr)
|
|
354
|
+
return 1
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
if __name__ == "__main__":
|
|
358
|
+
sys.exit(main())
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Make a scored patch's edit ops in the .agent file: the version that ships is the one scored.
|
|
3
|
+
|
|
4
|
+
python3 scripts/bt_patch_agent.py AGENT PATCH --out FILE [--overwrite]
|
|
5
|
+
|
|
6
|
+
AGENT is an .agent file, or a retrieved bundle dir holding one. PATCH is the patch file the replay
|
|
7
|
+
scored (sfap_replay.py --patch). The edited .agent goes to --out, never over AGENT, and the change
|
|
8
|
+
is printed as a unified diff. Only the lines an edit changes are rewritten, at their block's
|
|
9
|
+
indent and in its | style.
|
|
10
|
+
|
|
11
|
+
An op is refused, and nothing is written, when its edit in the .agent could differ from the edit
|
|
12
|
+
the replay made to the requests, or cannot be written as intended (references/patching.md, "The
|
|
13
|
+
same edit in the .agent"). Exit 0: written. Exit 2: refused, or a file could not be read or
|
|
14
|
+
written.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import difflib
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import traceback
|
|
22
|
+
|
|
23
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
24
|
+
|
|
25
|
+
from btlib import agentpatch # noqa: E402
|
|
26
|
+
from btlib import patch as patchlib # noqa: E402
|
|
27
|
+
from btlib.agentsrc import AgentSourceError, agent_file # noqa: E402
|
|
28
|
+
from btlib.redact import redact # noqa: E402
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse_args(argv=None):
|
|
32
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
33
|
+
p.add_argument("agent", help=".agent file, or a bundle dir holding one")
|
|
34
|
+
p.add_argument("patch", help="the patch file the replay scored (bt.patch/1)")
|
|
35
|
+
p.add_argument("--out", metavar="FILE", required=True, help="where to write the edited .agent (not AGENT)")
|
|
36
|
+
p.add_argument("--overwrite", action="store_true", help="replace --out if it exists")
|
|
37
|
+
return p.parse_args(argv)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def same_file(a, b):
|
|
41
|
+
return os.path.realpath(a) == os.path.realpath(b) or (os.path.exists(b) and os.path.samefile(a, b))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def read_text(path):
|
|
45
|
+
"""The text as it is: universal newlines would turn CR LF into LF on every line."""
|
|
46
|
+
with open(path, encoding="utf-8", newline="") as f:
|
|
47
|
+
try:
|
|
48
|
+
return f.read()
|
|
49
|
+
except UnicodeDecodeError:
|
|
50
|
+
raise AgentSourceError(f"{path}: not UTF-8 text")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_ops(path):
|
|
54
|
+
try:
|
|
55
|
+
return patchlib.load_patch(path)
|
|
56
|
+
except UnicodeDecodeError:
|
|
57
|
+
raise patchlib.PatchError("not UTF-8 text")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def summary_lines(report):
|
|
61
|
+
out = []
|
|
62
|
+
for r in report:
|
|
63
|
+
n = len(r["edits"])
|
|
64
|
+
out.append(f"op {r['id']} ({r['op']} {r['target']}): " + (f"{n} edit(s)" if n else "no change"))
|
|
65
|
+
return out
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main(argv=None):
|
|
69
|
+
args = parse_args(argv)
|
|
70
|
+
try:
|
|
71
|
+
path = agent_file(args.agent)
|
|
72
|
+
if same_file(path, args.out):
|
|
73
|
+
print(f"error: --out is the input {path}: write the edited agent to another file", file=sys.stderr)
|
|
74
|
+
return 2
|
|
75
|
+
if os.path.exists(args.out) and not args.overwrite:
|
|
76
|
+
print(f"error: {args.out} exists (--overwrite replaces it)", file=sys.stderr)
|
|
77
|
+
return 2
|
|
78
|
+
try:
|
|
79
|
+
ops = load_ops(args.patch)
|
|
80
|
+
except patchlib.PatchError as e:
|
|
81
|
+
print(f"error: {args.patch}: {redact(str(e))}", file=sys.stderr)
|
|
82
|
+
return 2
|
|
83
|
+
text = read_text(path)
|
|
84
|
+
new, report = agentpatch.patch_agent(text, ops, path)
|
|
85
|
+
with open(args.out, "w" if args.overwrite else "x", encoding="utf-8", newline="") as f:
|
|
86
|
+
f.write(new)
|
|
87
|
+
except agentpatch.AgentPatchError as e:
|
|
88
|
+
print(f"error: refused ({e.reason}): {redact(str(e))}; nothing written", file=sys.stderr)
|
|
89
|
+
return 2
|
|
90
|
+
except AgentSourceError as e:
|
|
91
|
+
print(f"error: {redact(str(e))}", file=sys.stderr)
|
|
92
|
+
return 2
|
|
93
|
+
except OSError as e:
|
|
94
|
+
print(f"error: {e.filename or ''}: {e.strerror or e}", file=sys.stderr)
|
|
95
|
+
return 2
|
|
96
|
+
except Exception as e: # a bug: say where, never a half-checked file
|
|
97
|
+
frame = traceback.extract_tb(e.__traceback__)[-1]
|
|
98
|
+
print(f"error: bt_patch_agent failed ({type(e).__name__} at {os.path.basename(frame.filename)}:{frame.lineno})",
|
|
99
|
+
file=sys.stderr)
|
|
100
|
+
return 2
|
|
101
|
+
for line in difflib.unified_diff(text.splitlines(True), new.splitlines(True), fromfile=path, tofile=args.out):
|
|
102
|
+
sys.stdout.write(redact(line) if line.endswith("\n") else redact(line) + "\n\\n")
|
|
103
|
+
for line in summary_lines(report):
|
|
104
|
+
print(line)
|
|
105
|
+
print(f"written: {args.out}" + ("" if new != text else " (unchanged)"))
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""btlib — step-level replay of Agentforce STDM traces (stdlib only).
|
|
2
|
+
|
|
3
|
+
One replay unit = one production LLM_STEP: the exact request that step sent (messages in
|
|
4
|
+
production order, the step's own tool definitions) and the answer it got (the golden).
|
|
5
|
+
|
|
6
|
+
redact scrub bearer tokens / JWTs from anything written or printed
|
|
7
|
+
stdm read STDM_RESULT payloads out of `sf apex run --json` output
|
|
8
|
+
messages rebuild a step's request messages in production layout
|
|
9
|
+
units one bt.unit/1 record per LLM_STEP
|
|
10
|
+
sfap SFAP /chat/generations client (urllib, token read per call, retries)
|
|
11
|
+
judge binary LLM judge for text replies and free-text arguments
|
|
12
|
+
scoring decision / names / args / text scores, failure buckets, schema checks
|
|
13
|
+
results run-dir artifacts: results.jsonl, summary.json, the legacy CSV
|
|
14
|
+
agentsrc read an .agent file: node instruction blocks, tools, rendering rules
|
|
15
|
+
patch edit ops on the captured requests (--patch), with per-op coverage
|
|
16
|
+
agentpatch the same edit ops in the .agent file: the version that ships is the one scored
|
|
17
|
+
versions which retrieved agent version each unit renders from (--agent-versions)
|
|
18
|
+
splits session-level test / val labels (--val-fraction) and --exclude-sessions
|
|
19
|
+
cache answers already received, keyed by the payload as sent (--cache-dir)
|
|
20
|
+
tokens mint a new SFAP token when the run's expires (--token-org)
|
|
21
|
+
compare paired comparison of two runs of the same units, with a noise band (bt_compare.py)
|
|
22
|
+
fetch STDM sessions through read-only anonymous Apex, nothing deployed (bt_fetch.py)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
SCHEMA_UNIT = "bt.unit/1"
|
|
26
|
+
SCHEMA_RESULT = "bt.result/1"
|
|
27
|
+
SCHEMA_SUMMARY = "bt.summary/1"
|
|
28
|
+
SCHEMA_PATCH = "bt.patch/1"
|
|
29
|
+
SCHEMA_COVERAGE = "bt.patch_coverage/1"
|
|
30
|
+
SCHEMA_VERSIONS = "bt.versions/1"
|
|
31
|
+
SCHEMA_CACHE = "bt.cache/1"
|
|
32
|
+
SCHEMA_COMPARE = "bt.compare/1"
|
|
33
|
+
SCHEMA_FETCH = "bt.fetch/1"
|
|
34
|
+
SCHEMA_KIT = "bt.fetch_kit/1"
|