@salesforce/afv-skills 1.58.0 → 1.60.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 (242) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-bot-upgrade/SKILL.md +3 -1
  17. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  18. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  19. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  20. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  21. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  26. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  27. package/skills/agentforce-generate/SKILL.md +21 -1
  28. package/skills/agentforce-generate/references/known-issues.md +51 -1
  29. package/skills/agentforce-generate/references/legacy-nga-regression-eval.md +547 -0
  30. package/skills/agentforce-generate/references/upgrade-legacy-agent-to-agentscript.md +577 -0
  31. package/skills/agentforce-generate/scripts/upgrade-legacy-nga-analyze.py +404 -0
  32. package/skills/agentforce-generate/scripts/upgrade-legacy-nga-run-trials.sh +58 -0
  33. package/skills/agentforce-persona-generate/SKILL.md +505 -0
  34. package/skills/agentforce-persona-generate/assets/checklist-template.md +100 -0
  35. package/skills/agentforce-persona-generate/assets/persona-encoding-template.md +252 -0
  36. package/skills/agentforce-persona-generate/assets/persona-template.md +229 -0
  37. package/skills/agentforce-persona-generate/assets/sample-dialog-template.md +50 -0
  38. package/skills/agentforce-persona-generate/assets/workshop-outline-template.md +97 -0
  39. package/skills/agentforce-persona-generate/examples/concierge-persona.md +189 -0
  40. package/skills/agentforce-persona-generate/examples/concierge-sample-dialog.md +70 -0
  41. package/skills/agentforce-persona-generate/examples/drover-persona.md +194 -0
  42. package/skills/agentforce-persona-generate/examples/drover-sample-dialog.md +62 -0
  43. package/skills/agentforce-persona-generate/examples/steady-hand-persona.md +188 -0
  44. package/skills/agentforce-persona-generate/examples/steady-hand-sample-dialog.md +68 -0
  45. package/skills/agentforce-persona-generate/examples/yt-persona.md +195 -0
  46. package/skills/agentforce-persona-generate/examples/yt-sample-dialog.md +66 -0
  47. package/skills/agentforce-persona-generate/references/attributions.md +36 -0
  48. package/skills/agentforce-persona-generate/references/interaction-design-notes.md +30 -0
  49. package/skills/agentforce-persona-generate/references/persona-encoding-guide.md +566 -0
  50. package/skills/agentforce-persona-generate/references/persona-framework.md +796 -0
  51. package/skills/agentforce-persona-generate/references/refinement-mappings.md +31 -0
  52. package/skills/agentforce-persona-generate/references/scoring-rubric.md +58 -0
  53. package/skills/agentforce-persona-generate/references/voice-catalog.json +2327 -0
  54. package/skills/agentforce-persona-generate/references/workshop-question-policy.md +90 -0
  55. package/skills/agentforce-persona-generate/references/workshop-scenario-seeds.md +119 -0
  56. package/skills/agentforce-persona-generate/scripts/parse_agent_yaml.py +223 -0
  57. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  58. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  59. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  60. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  61. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  62. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  63. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  64. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  65. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  66. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  67. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  68. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  69. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  70. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  71. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  72. package/skills/data360-schema-get/SKILL.md +3 -3
  73. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  74. package/skills/dx-app-analytics-query/SKILL.md +8 -8
  75. package/skills/dx-org-manage/SKILL.md +1 -0
  76. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  77. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  78. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  79. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  80. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  81. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  82. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  83. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  84. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  85. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  86. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  87. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  88. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  89. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  90. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  91. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +12 -12
  92. package/skills/experience-ui-bundle-app-coordinate/references/phase-completion-validation.md +3 -3
  93. package/skills/experience-ui-bundle-app-coordinate/references/phase-execution-pattern.md +9 -7
  94. package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.mjs +132 -0
  95. package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.mjs +62 -0
  96. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.mjs +71 -0
  97. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.mjs +95 -0
  98. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.mjs +78 -0
  99. package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.mjs +21 -0
  100. package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.mjs +28 -0
  101. package/skills/experience-ui-bundle-custom-app-generate/SKILL.md +3 -1
  102. package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.mjs +40 -0
  103. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +7 -5
  104. package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.mjs +70 -0
  105. package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.mjs +76 -0
  106. package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.mjs +115 -0
  107. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +7 -3
  108. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +1 -1
  109. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +1 -1
  110. package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.mjs +43 -0
  111. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.mjs +131 -0
  112. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.mjs +152 -0
  113. package/skills/experience-ui-bundle-mfa-configure/references/branding.md +10 -6
  114. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  115. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  116. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  117. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  118. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  119. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  120. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  121. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  122. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  123. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  124. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  125. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  126. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  127. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  128. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  129. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  130. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  131. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  132. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  133. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  134. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  135. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  136. package/skills/marketing-message-test/SKILL.md +194 -0
  137. package/skills/marketing-message-test/references/api-contract.md +333 -0
  138. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  139. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  140. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  141. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  142. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  143. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  144. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  145. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  146. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  147. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  148. package/skills/platform-models-api-configure/SKILL.md +5 -5
  149. package/skills/platform-soql-query/SKILL.md +1 -0
  150. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  151. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  152. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  153. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  154. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  155. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  156. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  157. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  158. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  159. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  160. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  161. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  162. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  163. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  164. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  165. package/skills/tableau-next-asset-create/README.md +65 -0
  166. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  167. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  168. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  169. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  170. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  171. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  172. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  173. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  174. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  175. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  176. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  177. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  178. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  179. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  180. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  181. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  182. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  183. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  184. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  185. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  186. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  187. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  188. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  189. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  190. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  191. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  192. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  193. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  194. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  195. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  196. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  197. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  198. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  199. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  200. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  201. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  202. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  203. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  204. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  205. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  206. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  207. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  208. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  209. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  210. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  211. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  212. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  213. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  214. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  215. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  216. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  217. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  218. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  219. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  220. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  221. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  222. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  223. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  224. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  225. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  226. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  227. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  228. package/skills/experience-ui-bundle-app-coordinate/scripts/check-dangling-refs.sh +0 -42
  229. package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh +0 -26
  230. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-1-complete.sh +0 -35
  231. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-4-complete.sh +0 -47
  232. package/skills/experience-ui-bundle-app-coordinate/scripts/check-phase-6-ready.sh +0 -33
  233. package/skills/experience-ui-bundle-app-coordinate/scripts/check-prerequisites.sh +0 -20
  234. package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh +0 -15
  235. package/skills/experience-ui-bundle-custom-app-generate/scripts/resolve-uibundle-path.sh +0 -30
  236. package/skills/experience-ui-bundle-frontend-generate/scripts/check-preconditions.sh +0 -37
  237. package/skills/experience-ui-bundle-frontend-generate/scripts/resolve-ui-bundle.sh +0 -66
  238. package/skills/experience-ui-bundle-frontend-generate/scripts/verify-rules.sh +0 -58
  239. package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh +0 -30
  240. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +0 -73
  241. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +0 -118
  242. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env python3
2
+ """Analyze collected legacy/draft preview trials and emit the comparison report.
3
+
4
+ DIVISION OF LABOR:
5
+ * Rubric #1 (trace / action equality) is MECHANICAL — it compares structured
6
+ identifiers pulled from the local plan traces (subagent topic, ordered
7
+ action names). This is objective equality, so the script does it.
8
+ * Rubric #2 (response discrepancy) is a SEMANTIC judgment — data fidelity,
9
+ formatting, verbosity, tone, spec compliance. This CANNOT be done by regex
10
+ or heuristics. The script does NOT score it. It emits the verbatim responses
11
+ plus an analysis prompt for an LLM judge (or human) to complete.
12
+
13
+ ORACLE MODEL (Rubric #1): legacy is a BASELINE, not the oracle. Three references:
14
+ spec = expected_subagent/expected_actions (authored from intent) — ORACLE
15
+ legacy = what the old agent actually did — descriptive baseline, can be WRONG
16
+ draft = the NGA under test
17
+ We classify the spec-legacy-draft triangle instead of gating on draft==legacy:
18
+ legacy==spec & draft==spec -> PASS
19
+ legacy==spec & draft!=spec -> DRAFT-REGRESSION (fix draft)
20
+ legacy!=spec & draft==spec -> PASS + LEGACY-DEFECT (draft fixed it)
21
+ legacy!=spec & draft!=spec & l==d -> SPEC-REVIEW (spec likely stale)
22
+ legacy!=spec & draft!=spec & l!=d -> ADJUDICATE (LLM judge decides)
23
+ If no spec is supplied the utterance is SPEC-UNVERIFIED: fall back to the
24
+ legacy-vs-draft baseline diff + judge.
25
+
26
+ DRIFT (across iterations): an accepted BASELINE file records the last
27
+ spec-approved (subagent, actions) per utterance. Each run diffs the DRAFT
28
+ signature against it — a change flags DRIFT even if it still matches the spec,
29
+ catching unintended agentscript drift. Re-baseline deliberately (REBASELINE=1)
30
+ when instructions intentionally change, so intended changes update truth
31
+ instead of firing false regressions.
32
+
33
+ Nothing here is agent- or utterance-specific: no field labels, no domain
34
+ vocabulary, no expected values baked in (expected_*/baseline are per run/repo).
35
+
36
+ Rubric #1 source — the LOCAL plan trace `sf agent preview end` writes to
37
+ .sfdx/agents/<agentId>/sessions/<sid>/traces/<planId>.json
38
+ Both sides populate it on CLI>=2.149.9 (@salesforce/agents>=2.0.4). We read
39
+ `topic`/`intent` (subagent) and ordered FunctionStep entries (action name/input/
40
+ status), for legacy (ReAct: UpdateTopicStep+topic) and draft (ScriptAgent:
41
+ TransitionStep). Names match by substring containment (name_match), since legacy
42
+ names append a definition-id suffix of arbitrary format.
43
+
44
+ Usage: analyze.py <run_dir> <utt_id> [expected_subagent] [expected_actions_csv]
45
+ expected_* are OPTIONAL. When omitted, only observed traces + the
46
+ legacy-vs-draft cross-check + drift are reported (no vs-spec triangle verdict).
47
+ Traces live under `.sfdx/agents`; the tool auto-locates it by walking up from
48
+ cwd (then from <run_dir>), so it runs from any directory inside the repo. Only
49
+ if no `.sfdx/agents` exists on either path does a subject report a (correct)
50
+ UNVERIFIED-TRACE. <run_dir> may be relative (e.g. agent-eval/runs/<id>).
51
+ Env (optional):
52
+ EVAL_ORG label only.
53
+ DEMO_CONTACT if set, the first draft action input is checked for this contactId.
54
+ EVAL_BASELINE path to the accepted-baseline JSON (default agent-eval/baseline.json).
55
+ REBASELINE if "1", write the current draft signature into the baseline for
56
+ this utterance (deliberate, human-approved re-baseline) and exit.
57
+ Writes <run_dir>/report.md and prints it.
58
+ """
59
+ import json, sys, re, glob, os, datetime
60
+
61
+ RUN = sys.argv[1]
62
+ UTT = sys.argv[2] if len(sys.argv) > 2 else "utterance"
63
+ EXP_SUB = sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None
64
+ EXP_ACTS = [a for a in (sys.argv[4].split(",") if len(sys.argv) > 4 else []) if a]
65
+ ORG = os.environ.get("EVAL_ORG", "target-org")
66
+ DEMO_CONTACT = os.environ.get("DEMO_CONTACT") # None => no contact-binding assertion
67
+ BASELINE_FILE = os.environ.get("EVAL_BASELINE", os.path.join("agent-eval", "baseline.json"))
68
+ REBASELINE = os.environ.get("REBASELINE") == "1"
69
+
70
+ # Locate `.sfdx/agents` by walking UP from cwd (then from the run dir) so the
71
+ # tool works no matter which directory it's invoked from — otherwise a wrong cwd
72
+ # silently yields a false UNVERIFIED-TRACE for every subject.
73
+ def _find_sfdx_agents():
74
+ for start in (os.getcwd(), os.path.abspath(RUN)):
75
+ d = start
76
+ while True:
77
+ cand = os.path.join(d, ".sfdx", "agents")
78
+ if os.path.isdir(cand):
79
+ return cand
80
+ parent = os.path.dirname(d)
81
+ if parent == d:
82
+ break
83
+ d = parent
84
+ # Fall back to the cwd-relative path; parse_trace() then reports "not found".
85
+ return os.path.join(os.getcwd(), ".sfdx", "agents")
86
+
87
+
88
+ SFDX_AGENTS = _find_sfdx_agents()
89
+
90
+
91
+ def load(subj):
92
+ out = []
93
+ for f in sorted(glob.glob(f"{RUN}/{subj}/{UTT}/trial-*/trial.json")):
94
+ with open(f) as fh:
95
+ out.append(json.load(fh))
96
+ return out
97
+
98
+
99
+ # ---- Trace parsing (mechanical rubric #1) -----------------------------------
100
+ def find_trace(plan_id):
101
+ if not plan_id:
102
+ return None
103
+ hits = glob.glob(f"{SFDX_AGENTS}/**/traces/{plan_id}.json", recursive=True)
104
+ return hits[0] if hits else None
105
+
106
+
107
+ # Legacy topic/action names carry a definition-id suffix appended to the base
108
+ # name (e.g. Get_All_Loan_Application_Details_179KX000000YehM), while the NGA
109
+ # draft uses the clean base name. The suffix format is NOT fixed (length/charset
110
+ # vary), so a regex strip is unreliable. Instead treat two names as the same
111
+ # entity when either is a substring of the other (order-independent, since the
112
+ # suffix may land on either side). Compared case-insensitively.
113
+ def name_match(a, b):
114
+ if a is None or b is None:
115
+ return a == b
116
+ a, b = a.lower(), b.lower()
117
+ return a in b or b in a
118
+
119
+
120
+ def names_match(xs, ys):
121
+ return len(xs) == len(ys) and all(name_match(x, y) for x, y in zip(xs, ys))
122
+
123
+
124
+ def signature(topic, action_names):
125
+ """Route+action signature = [subagent] + ordered actions. Compared with
126
+ names_match so legacy definition-id suffixes are tolerated."""
127
+ return [topic] + list(action_names or [])
128
+
129
+
130
+ def triangle_verdict(leg_sig, draft_sig, spec_sig):
131
+ """Classify the spec-legacy-draft triangle. Returns (verdict, note).
132
+ spec_sig is None when no expected_* was supplied (SPEC-UNVERIFIED)."""
133
+ if not spec_sig or spec_sig == [None]:
134
+ same = names_match(leg_sig, draft_sig)
135
+ return ("SPEC-UNVERIFIED",
136
+ "no spec supplied — "
137
+ + ("draft matches legacy baseline" if same
138
+ else "draft diverges from legacy baseline; judge must decide"))
139
+ ls = names_match(leg_sig, spec_sig)
140
+ ds = names_match(draft_sig, spec_sig)
141
+ if ls and ds:
142
+ return ("PASS", "legacy, draft, and spec all agree")
143
+ if ls and not ds:
144
+ return ("DRAFT-REGRESSION", "legacy matched the spec but the draft does not — fix the draft")
145
+ if ds and not ls:
146
+ return ("PASS + LEGACY-DEFECT",
147
+ "draft matches the spec; legacy diverged — legacy was wrong, the migration fixed it")
148
+ # neither matches the spec
149
+ if names_match(leg_sig, draft_sig):
150
+ return ("SPEC-REVIEW", "legacy and draft agree with each other but not the spec — the spec is likely stale/wrong")
151
+ return ("ADJUDICATE", "three-way disagreement (spec vs legacy vs draft) — LLM judge must adjudicate with traces + responses")
152
+
153
+
154
+ def load_baseline():
155
+ try:
156
+ with open(BASELINE_FILE) as fh:
157
+ return json.load(fh)
158
+ except Exception:
159
+ return {}
160
+
161
+
162
+ def save_baseline(bl):
163
+ os.makedirs(os.path.dirname(BASELINE_FILE) or ".", exist_ok=True)
164
+ with open(BASELINE_FILE, "w") as fh:
165
+ json.dump(bl, fh, indent=2, sort_keys=True)
166
+
167
+
168
+ def parse_trace(plan_id):
169
+ """Return normalized trace evidence, or a reason it's unavailable.
170
+
171
+ Handles BOTH local trace shapes:
172
+ - draft (ScriptAgent): routing via TransitionStep{from_agent,to_agent}
173
+ - legacy (ProductionAgent, CLI>=2.149.9 / @salesforce/agents>=2.0.4):
174
+ routing via UpdateTopicStep + `topic`; names carry a definitionId suffix.
175
+ Legacy populates only when the SDK fetches the v1.1 preview plans endpoint;
176
+ older SDKs (<=2.0.1) stub getTrace()->undefined and write `{}` (empty plan).
177
+ Names are kept RAW here; equality is decided by name_match() (substring),
178
+ not by stripping the suffix.
179
+ """
180
+ tf = find_trace(plan_id)
181
+ if not tf:
182
+ return {"available": False, "reason": "trace file not found"}
183
+ try:
184
+ with open(tf) as fh:
185
+ d = json.load(fh)
186
+ except Exception as e:
187
+ return {"available": False, "reason": f"unreadable: {e}"}
188
+ if not isinstance(d, dict) or not d.get("plan"):
189
+ return {"available": False,
190
+ "reason": "empty plan ({}) — SDK too old? need CLI>=2.149.9 (@salesforce/agents>=2.0.4)"}
191
+ transitions, actions = [], []
192
+ for s in d["plan"]:
193
+ if s.get("type") == "TransitionStep": # draft shape
194
+ dd = s.get("data", {})
195
+ transitions.append((dd.get("from_agent"), dd.get("to_agent")))
196
+ if s.get("type") == "FunctionStep": # both shapes
197
+ fn = s.get("function", {})
198
+ out = fn.get("output", {})
199
+ actions.append({
200
+ "name": fn.get("name"),
201
+ "input": fn.get("input"),
202
+ "status": out.get("__action_execution_status__") if isinstance(out, dict) else None,
203
+ })
204
+ topic = d.get("topic")
205
+ # Legacy routes via UpdateTopicStep rather than TransitionStep; synthesize a
206
+ # transition to the topic so the report row is uniform across both shapes.
207
+ if not transitions and topic:
208
+ transitions = [("Agent Router", topic)]
209
+ return {
210
+ "available": True,
211
+ "topic": topic,
212
+ "intent": d.get("intent"),
213
+ "transitions": transitions,
214
+ "actions": actions,
215
+ "action_names": [a["name"] for a in actions],
216
+ }
217
+
218
+
219
+ leg, dr = load("legacy"), load("draft")
220
+ if not leg or not dr:
221
+ sys.exit(f"missing trials: legacy={len(leg)} draft={len(dr)}")
222
+ wc = lambda t: len((t or "").split())
223
+
224
+ # Trace evidence per subject (use trial 1's planId as representative).
225
+ leg_tr = parse_trace(leg[0].get("planId"))
226
+ dr_tr = parse_trace(dr[0].get("planId"))
227
+
228
+ # Signatures (route + ordered actions) for the triangle + drift comparisons.
229
+ leg_sig = signature(leg_tr.get("topic"), leg_tr.get("action_names")) if leg_tr["available"] else None
230
+ draft_sig = signature(dr_tr.get("topic"), dr_tr.get("action_names")) if dr_tr["available"] else None
231
+ spec_sig = signature(EXP_SUB, EXP_ACTS) if (EXP_SUB or EXP_ACTS) else None
232
+
233
+ # --- Deliberate re-baseline: record the current DRAFT signature and exit. -----
234
+ # This is the human-approved "the draft is now correct; make it the baseline"
235
+ # action. Use after an intentional instruction change (or first approval).
236
+ if REBASELINE:
237
+ if not dr_tr["available"]:
238
+ sys.exit("REBASELINE refused: draft trace unavailable (cannot capture a signature).")
239
+ bl = load_baseline()
240
+ bl[UTT] = {
241
+ "subagent": dr_tr["topic"],
242
+ "actions": dr_tr["action_names"],
243
+ "approved_at": datetime.datetime.now().isoformat(timespec="seconds"),
244
+ "approved_from_run": RUN,
245
+ }
246
+ save_baseline(bl)
247
+ print(f"Re-baselined `{UTT}` in {BASELINE_FILE}: subagent={dr_tr['topic']} actions={dr_tr['action_names']}")
248
+ sys.exit(0)
249
+
250
+ # Triangle verdict (spec vs legacy vs draft) + drift vs accepted baseline.
251
+ tri_verdict, tri_note = (triangle_verdict(leg_sig, draft_sig, spec_sig)
252
+ if (leg_tr["available"] and dr_tr["available"])
253
+ else ("UNVERIFIED-TRACE", "one or both traces unavailable"))
254
+ baseline = load_baseline()
255
+ base_entry = baseline.get(UTT)
256
+ if base_entry and draft_sig:
257
+ base_sig = signature(base_entry.get("subagent"), base_entry.get("actions"))
258
+ drift = not names_match(base_sig, draft_sig)
259
+ else:
260
+ base_sig, drift = None, None # None => no baseline recorded yet
261
+
262
+ L = []
263
+ p = L.append
264
+ p(f"# Legacy vs NGA Regression Report — utterance `{UTT}`\n")
265
+ p(f"- Run: `{RUN}` | Org: `{ORG}` | Trials: {len(leg)} each")
266
+ p(f"- Legacy latency (ms): {[r['latency_ms'] for r in leg]} | Draft: {[r['latency_ms'] for r in dr]}")
267
+ p(f"- content-safe (all): legacy {all(r['isContentSafe'] for r in leg)} | draft {all(r['isContentSafe'] for r in dr)}")
268
+ p(f"- word count (per trial): legacy {[wc(r['response']) for r in leg]} | draft {[wc(r['response']) for r in dr]}")
269
+ p(f"- Legacy planIds: {[r['planId'] for r in leg]}")
270
+ p(f"- Draft planIds: {[r['planId'] for r in dr]}\n")
271
+
272
+ # ---- Rubric #1 — mechanical -------------------------------------------------
273
+ p("## Rubric #1 — Trace / action equality (MECHANICAL, from local planId trace)\n")
274
+ if EXP_SUB or EXP_ACTS:
275
+ p(f"Expected (from suite): subagent `{EXP_SUB}`, actions `{EXP_ACTS}`.\n")
276
+ else:
277
+ p("_No expected subagent/actions supplied — reporting observed traces and the "
278
+ "legacy-vs-draft cross-check only._\n")
279
+ p("| subject | trace | subagent (topic) | router transition | ordered actions | verdict |")
280
+ p("|---|---|---|---|---|---|")
281
+
282
+
283
+ def trace_row(name, tr):
284
+ if not tr["available"]:
285
+ return f"| {name} | ❌ {tr['reason']} | — | — | — | **UNVERIFIED-TRACE** |"
286
+ trans = " → ".join(str(x) for x in tr["transitions"][-1]) if tr["transitions"] else "(none)"
287
+ acts = ", ".join(a["name"] for a in tr["actions"]) or "(none)"
288
+ if EXP_SUB or EXP_ACTS:
289
+ sub_ok = name_match(tr["topic"], EXP_SUB) if EXP_SUB else True
290
+ act_ok = names_match(tr["action_names"], EXP_ACTS) if EXP_ACTS else True
291
+ verdict = "TRACE-VERIFIED ✅" if (sub_ok and act_ok) else "TRACE-DIVERGENT ⚠️"
292
+ else:
293
+ verdict = "(observed)"
294
+ return f"| {name} | ✅ local | {tr['topic']} | {trans} | {acts} | **{verdict}** |"
295
+
296
+
297
+ p(trace_row("legacy", leg_tr))
298
+ p(trace_row("draft", dr_tr))
299
+ if leg_tr["available"] and dr_tr["available"]:
300
+ same_sub = name_match(leg_tr["topic"], dr_tr["topic"])
301
+ same_acts = names_match(leg_tr["action_names"], dr_tr["action_names"])
302
+ p(f"\n**Legacy vs draft:** subagent {'✅ match' if same_sub else '❌ differ'} "
303
+ f"(legacy `{leg_tr['topic']}` vs draft `{dr_tr['topic']}`) · "
304
+ f"actions {'✅ match' if same_acts else '❌ differ'} "
305
+ f"(legacy `{leg_tr['action_names']}` vs draft `{dr_tr['action_names']}`) → "
306
+ f"**{'ROUTE+ACTION EQUIVALENT' if (same_sub and same_acts) else 'TRACE-DIVERGENT'}**")
307
+ # ---- Triangle verdict (spec vs legacy vs draft) + drift ---------------------
308
+ p("\n### Oracle triangle (spec vs legacy vs draft)\n")
309
+ p("| reference | subagent | actions |")
310
+ p("|---|---|---|")
311
+ p(f"| spec (oracle) | {EXP_SUB or '_none supplied_'} | {EXP_ACTS or '_none supplied_'} |")
312
+ p(f"| legacy (baseline) | {leg_tr.get('topic') if leg_tr['available'] else '—'} | "
313
+ f"{leg_tr.get('action_names') if leg_tr['available'] else '—'} |")
314
+ p(f"| draft (under test) | {dr_tr.get('topic') if dr_tr['available'] else '—'} | "
315
+ f"{dr_tr.get('action_names') if dr_tr['available'] else '—'} |")
316
+ p(f"\n**Triangle verdict: `{tri_verdict}`** — {tri_note}\n")
317
+ p("> Legacy is a descriptive baseline, NOT the oracle: it can be wrong or miss "
318
+ "an action. The spec (expected_*) is the oracle. `PASS + LEGACY-DEFECT` means "
319
+ "the draft matches the spec while legacy diverged — the migration fixed a legacy "
320
+ "defect. `DRAFT-REGRESSION` means legacy was right and the draft broke it. "
321
+ "`SPEC-REVIEW`/`ADJUDICATE` escalate to the LLM judge.\n")
322
+
323
+ # Drift vs the accepted baseline (unintended-change detector).
324
+ if drift is None:
325
+ p(f"**Drift:** no accepted baseline recorded for `{UTT}` yet "
326
+ f"(set it deliberately with `REBASELINE=1`). File: `{BASELINE_FILE}`.\n")
327
+ elif drift:
328
+ p(f"**Drift: ⚠️ DETECTED** — the draft signature changed vs the accepted "
329
+ f"baseline (`{base_entry.get('approved_at')}`).\n"
330
+ f"- baseline: subagent `{base_entry.get('subagent')}` actions `{base_entry.get('actions')}`\n"
331
+ f"- current draft: subagent `{dr_tr.get('topic')}` actions `{dr_tr.get('action_names')}`\n"
332
+ f"\n> If this change was intended, re-approve with `REBASELINE=1`. Otherwise the "
333
+ f"draft drifted from its last spec-approved behavior — investigate.\n")
334
+ else:
335
+ p(f"**Drift:** ✅ none — draft matches the accepted baseline "
336
+ f"(`{base_entry.get('approved_at')}`).\n")
337
+
338
+ p("\n> **Local, symmetric trace.** Both sides write a full local plan trace at "
339
+ "`.sfdx/agents/<agentId>/sessions/<sid>/traces/<planId>.json` "
340
+ "(`topic`=subagent + ordered `FunctionStep` action name/input/status, "
341
+ "type-agnostic across Apex/Flow/Prompt). Requires **CLI >=2.149.9 "
342
+ "(@salesforce/agents>=2.0.4)**; older SDKs stubbed `ProductionAgent.getTrace()` "
343
+ "and wrote `{}`. Legacy names carry a definition-id suffix of arbitrary format; "
344
+ "names match by substring containment (either side), not suffix-stripping.\n")
345
+
346
+ if dr_tr["available"] and dr_tr["actions"]:
347
+ ci = dr_tr["actions"][0].get("input")
348
+ if DEMO_CONTACT:
349
+ ok = isinstance(ci, dict) and ci.get("contactId") == DEMO_CONTACT
350
+ p(f"Draft first action input: `{json.dumps(ci)}` — "
351
+ f"{'✅ bound to DEMO_CONTACT' if ok else '⚠️ not bound to DEMO_CONTACT'}\n")
352
+ else:
353
+ p(f"Draft first action input: `{json.dumps(ci)}` (set DEMO_CONTACT env to assert binding)\n")
354
+
355
+ # ---- Responses (verbatim, all trials) ---------------------------------------
356
+ p("## Responses (verbatim)\n")
357
+ for subj, rows in (("Legacy", leg), ("Draft NGA", dr)):
358
+ p(f"### {subj}\n")
359
+ for r in rows:
360
+ p(f"**Trial {r['trial']}** (planId `{r['planId']}`):\n```\n{r['response']}\n```\n")
361
+
362
+ # ---- Rubric #2 — LLM analysis prompt (NOT scored by this script) ------------
363
+ p("## Rubric #2 — Response analysis (LLM judge — complete this)\n")
364
+ p("> The script does not score responses. An LLM (or human) must read the "
365
+ "verbatim responses above and answer the rubric below. Base every claim on the "
366
+ "actual text; do not assume domain-specific fields.\n")
367
+ drift_ctx = ("no baseline recorded yet" if drift is None
368
+ else "DRIFTED from accepted baseline" if drift
369
+ else "matches accepted baseline")
370
+ p(f"> **Mechanical context for the judge (Rubric #1):** triangle verdict "
371
+ f"`{tri_verdict}` — {tri_note}; drift status: {drift_ctx}. Treat the triangle "
372
+ f"verdict as the routing/action ground truth: legacy is a baseline, not the "
373
+ f"oracle. If the verdict is `PASS + LEGACY-DEFECT`, do not penalize the draft "
374
+ f"for differing from legacy — legacy was wrong. If `DRAFT-REGRESSION`, the draft "
375
+ f"broke a behavior legacy got right. If `SPEC-REVIEW`/`ADJUDICATE`, you are the "
376
+ f"tie-breaker — decide which behavior is actually correct from the responses.\n")
377
+ p("""You are comparing a **legacy** agent against its **migrated NGA draft** for
378
+ the same utterance. Using ONLY the response texts above plus the Rubric #1 trace
379
+ facts, produce:
380
+
381
+ 1. **Intra-trial consistency** — for each subject, are the N trials materially
382
+ equivalent (same facts, acceptable wording variance) or do they diverge?
383
+ Pick a canonical trial for each side.
384
+ 2. **Data fidelity** — do legacy and draft agree on every concrete fact the
385
+ responses assert (numbers, amounts, dates, statuses, named entities, IDs)?
386
+ List any value the draft changed, dropped, or added. A factual mismatch is a
387
+ **REGRESSION**, not a style difference.
388
+ 3. **Formatting** — structural differences: bullets/headers/lists, number and
389
+ money formatting, and any leaked internal field/action API names.
390
+ 4. **Verbosity & tone** — length, clarity, empathy, and whether each ends with
391
+ an appropriate follow-up/closing offer.
392
+ 5. **Spec compliance** — does each side satisfy the utterance's intent? Note if
393
+ the Rubric #1 action set differs (e.g. one side grounded via a retrieval
394
+ action and the other answered from model knowledge) and whether that changes
395
+ answer quality or trustworthiness.
396
+ 6. **Classification** — one of `EQUIVALENT` | `IMPROVED` | `ACCEPTABLE-DIFF` |
397
+ `REGRESSION`, with a one-line justification. If not `EQUIVALENT`/`IMPROVED`,
398
+ give a concrete recommended `.agent` change.
399
+ """)
400
+
401
+ report = "\n".join(L)
402
+ with open(f"{RUN}/report.md", "w") as fh:
403
+ fh.write(report)
404
+ print(report)
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # Collect N preview trials for one subject × one utterance.
3
+ # Usage: upgrade-legacy-nga-run-trials.sh <subject:legacy|draft> <utt_id> <trials> <outdir> "<utterance>" [mode:simulate|live]
4
+ # mode defaults to `simulate` (safe: no real side effects). Pass `live` ONLY for
5
+ # utterances the caller has opted in per the side-effect safety gate. mode gates
6
+ # the DRAFT only; legacy (--api-name) has no mode flag and always runs live (CLI).
7
+ # Reliable path: start -> send -> end, fresh session per trial,
8
+ # stdout->file (never 2>&1 into a parser).
9
+ # Agent api-name/bundle and org are config, supplied via env (no baked-in agent):
10
+ # EVAL_AGENT legacy Bot api-name AND draft bundle name (they collide by design)
11
+ # EVAL_ORG target-org alias
12
+ set -euo pipefail
13
+ # Force plain output: some shells/CI still get ANSI color codes on `sf --json`,
14
+ # which corrupt the JSON once stdout is redirected to a file (the parser then
15
+ # fails on the escape bytes). NO_COLOR + FORCE_COLOR=0 make the CLI emit clean JSON.
16
+ export NO_COLOR=1 FORCE_COLOR=0
17
+ SUBJECT="$1"; UTT="$2"; N="$3"; OUT="$4"; TEXT="$5"; MODE="${6:-simulate}"
18
+ case "$MODE" in simulate|live) ;; *) echo "mode must be 'simulate' or 'live' (got '$MODE')" >&2; exit 2 ;; esac
19
+ ORG="${EVAL_ORG:?set EVAL_ORG to the target-org alias}"
20
+ AGENT="${EVAL_AGENT:?set EVAL_AGENT to the agent api-name/bundle}"
21
+ # NOTE: --use-live-actions / --simulate-actions are START-ONLY flags.
22
+ # send/end accept only the subject selector (--api-name | --authoring-bundle) + --session-id.
23
+ if [ "$SUBJECT" = "legacy" ]; then
24
+ SEL=(--api-name "$AGENT") # session selector (start/send/end)
25
+ START_SEL=("${SEL[@]}") # legacy (--api-name) has no mode flag; always live (CLI)
26
+ if [ "$MODE" = "simulate" ]; then
27
+ echo " NOTE: legacy (--api-name) has no simulate mode; it runs live. Gate side-effecting utterances upstream." >&2
28
+ fi
29
+ else
30
+ SEL=(--authoring-bundle "$AGENT") # session selector (start/send/end)
31
+ if [ "$MODE" = "live" ]; then
32
+ START_SEL=(--use-live-actions "${SEL[@]}") # mode flag only on start; explicit opt-in
33
+ else
34
+ START_SEL=(--simulate-actions "${SEL[@]}") # default: safe, no real side effects
35
+ fi
36
+ fi
37
+ mkdir -p "$OUT/$SUBJECT/$UTT"
38
+ for i in $(seq 1 "$N"); do
39
+ d="$OUT/$SUBJECT/$UTT/trial-$i"; mkdir -p "$d"
40
+ sf agent preview start --json "${START_SEL[@]}" -o "$ORG" > "$d/start.json" 2>"$d/start.err"
41
+ SID=$(python3 -c "import json;print(json.load(open('$d/start.json'))['result']['sessionId'])")
42
+ t0=$(python3 -c "import time;print(int(time.time()*1000))")
43
+ sf agent preview send --json "${SEL[@]}" --session-id "$SID" -u "$TEXT" -o "$ORG" > "$d/send.json" 2>"$d/send.err"
44
+ t1=$(python3 -c "import time;print(int(time.time()*1000))")
45
+ sf agent preview end --json "${SEL[@]}" --session-id "$SID" -o "$ORG" > "$d/end.json" 2>"$d/end.err" || true
46
+ python3 - "$d" "$SUBJECT" "$UTT" "$i" "$SID" "$((t1-t0))" <<'PY'
47
+ import json,sys
48
+ d,subject,utt,i,sid,lat=sys.argv[1:7]
49
+ s=json.load(open(f"{d}/send.json"))["result"]
50
+ m=s["messages"][0]
51
+ rec={"subject":subject,"utterance_id":utt,"trial":int(i),"sessionId":sid,
52
+ "planId":m.get("planId"),"isContentSafe":m.get("isContentSafe"),
53
+ "response":m.get("message"),"result":m.get("result"),
54
+ "citedReferences":m.get("citedReferences"),"latency_ms":int(lat)}
55
+ json.dump(rec,open(f"{d}/trial.json","w"),indent=1)
56
+ print(f" {subject} {utt} trial {i}: {int(lat)}ms safe={m.get('isContentSafe')} planId={m.get('planId')}")
57
+ PY
58
+ done