@salesforce/afv-skills 1.14.0 → 1.16.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/activating-datacloud/SKILL.md +0 -1
- package/skills/analyzing-omnistudio-dependencies/SKILL.md +0 -1
- package/skills/applying-slds/SKILL.md +322 -0
- package/skills/applying-slds/checklists.md +83 -0
- package/skills/applying-slds/examples.md +283 -0
- package/skills/applying-slds/guidance/README.md +83 -0
- package/skills/applying-slds/guidance/blueprints-index.md +213 -0
- package/skills/applying-slds/guidance/icons-guidance.md +186 -0
- package/skills/applying-slds/guidance/overviews/borders.md +236 -0
- package/skills/applying-slds/guidance/overviews/color.md +266 -0
- package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
- package/skills/applying-slds/guidance/overviews/icons.md +240 -0
- package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
- package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
- package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
- package/skills/applying-slds/guidance/overviews/typography.md +323 -0
- package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
- package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
- package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
- package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
- package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
- package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
- package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
- package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
- package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
- package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
- package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
- package/skills/applying-slds/guidance/utilities/borders.md +131 -0
- package/skills/applying-slds/guidance/utilities/box.md +125 -0
- package/skills/applying-slds/guidance/utilities/color.md +165 -0
- package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
- package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
- package/skills/applying-slds/guidance/utilities/floats.md +117 -0
- package/skills/applying-slds/guidance/utilities/grid.md +264 -0
- package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
- package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
- package/skills/applying-slds/guidance/utilities/index.md +205 -0
- package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
- package/skills/applying-slds/guidance/utilities/layout.md +109 -0
- package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
- package/skills/applying-slds/guidance/utilities/margin.md +155 -0
- package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
- package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
- package/skills/applying-slds/guidance/utilities/padding.md +155 -0
- package/skills/applying-slds/guidance/utilities/position.md +177 -0
- package/skills/applying-slds/guidance/utilities/print.md +114 -0
- package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
- package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
- package/skills/applying-slds/guidance/utilities/themes.md +121 -0
- package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
- package/skills/applying-slds/guidance/utilities/typography.md +166 -0
- package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
- package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
- package/skills/applying-slds/metadata/README.md +84 -0
- package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
- package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
- package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
- package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
- package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
- package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
- package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
- package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
- package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
- package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
- package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
- package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
- package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
- package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
- package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
- package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
- package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
- package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
- package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
- package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
- package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
- package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
- package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
- package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
- package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
- package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
- package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
- package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
- package/skills/applying-slds/metadata/hooks-index.json +6272 -0
- package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
- package/skills/applying-slds/metadata/utilities-index.json +21912 -0
- package/skills/applying-slds/references/component-selection.md +112 -0
- package/skills/applying-slds/references/icons-decision-guide.md +124 -0
- package/skills/applying-slds/references/styling-decision-guide.md +228 -0
- package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
- package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
- package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
- package/skills/applying-slds/scripts/search-icons.cjs +174 -0
- package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
- package/skills/building-mobile-apps/SKILL.md +0 -1
- package/skills/building-omnistudio-callable-apex/SKILL.md +0 -1
- package/skills/building-omnistudio-datamapper/SKILL.md +0 -1
- package/skills/building-omnistudio-flexcard/SKILL.md +0 -1
- package/skills/building-omnistudio-integration-procedure/SKILL.md +0 -1
- package/skills/building-omnistudio-omniscript/SKILL.md +0 -1
- package/skills/building-sf-integrations/SKILL.md +0 -1
- package/skills/configuring-connected-apps/SKILL.md +0 -1
- package/skills/connecting-datacloud/SKILL.md +0 -1
- package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
- package/skills/debugging-apex-logs/SKILL.md +0 -1
- package/skills/deploying-metadata/SKILL.md +0 -1
- package/skills/deploying-omnistudio-datapacks/SKILL.md +0 -1
- package/skills/developing-agentforce/SKILL.md +0 -1
- package/skills/fetching-salesforce-docs/SKILL.md +0 -1
- package/skills/generating-custom-lightning-type/SKILL.md +17 -39
- package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
- package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
- package/skills/generating-lwc-components/SKILL.md +0 -1
- package/skills/generating-mermaid-diagrams/SKILL.md +0 -1
- package/skills/generating-visual-diagrams/SKILL.md +0 -1
- package/skills/handling-sf-data/SKILL.md +0 -1
- package/skills/harmonizing-datacloud/SKILL.md +0 -1
- package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +0 -1
- package/skills/investigating-agentforce-architecture/README.md +156 -0
- package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
- package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
- package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
- package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
- package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
- package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
- package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
- package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
- package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
- package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
- package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
- package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
- package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
- package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
- package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
- package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
- package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
- package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
- package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
- package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
- package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
- package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
- package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
- package/skills/investigating-agentforce-d360/README.md +123 -0
- package/skills/investigating-agentforce-d360/SKILL.md +163 -0
- package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
- package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
- package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
- package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
- package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
- package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
- package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
- package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
- package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
- package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
- package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
- package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
- package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
- package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
- package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
- package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
- package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
- package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
- package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
- package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
- package/skills/managing-managed-event-subscription/SKILL.md +152 -0
- package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
- package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
- package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
- package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
- package/skills/modeling-omnistudio-epc-catalog/SKILL.md +0 -1
- package/skills/observing-agentforce/SKILL.md +0 -1
- package/skills/orchestrating-datacloud/SKILL.md +0 -1
- package/skills/preparing-datacloud/SKILL.md +0 -1
- package/skills/querying-soql/SKILL.md +0 -1
- package/skills/retrieving-datacloud/SKILL.md +0 -1
- package/skills/running-apex-tests/SKILL.md +0 -1
- package/skills/running-code-analyzer/SKILL.md +0 -1
- package/skills/segmenting-datacloud/SKILL.md +0 -1
- package/skills/testing-agentforce/SKILL.md +0 -1
- package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
- package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
- package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
- package/skills/validating-slds/SKILL.md +262 -0
- package/skills/validating-slds/references/quality-checks.md +308 -0
- package/skills/validating-slds/references/report-format.md +302 -0
- package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"""Tests for ``discover_sessions`` — DC-only session picker.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- ``parse_time_expr`` pure str → TimeRange (12 supported expressions)
|
|
5
|
+
- ``compose_sql`` pure args → SQL string (conditional JOINs / WHEREs)
|
|
6
|
+
- ``fetch_agent_names`` DC follow-up call (mocks ``post``)
|
|
7
|
+
- ``render_picker`` pure rows → markdown
|
|
8
|
+
- ``main`` argv → exit code (mocks ``resolve_org`` + ``post``)
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
from datetime import datetime, timedelta, timezone
|
|
14
|
+
from unittest import mock
|
|
15
|
+
|
|
16
|
+
from . import _bootstrap # noqa: F401 — sys.path setup
|
|
17
|
+
|
|
18
|
+
import discover_sessions # type: ignore
|
|
19
|
+
from dc import DCQueryError # type: ignore
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# -----------------------------------------------------------------------------
|
|
23
|
+
# parse_time_expr — pure
|
|
24
|
+
# -----------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ParseTimeExprTests(unittest.TestCase):
|
|
28
|
+
|
|
29
|
+
def test_default_none_returns_last_24_hours(self):
|
|
30
|
+
tr = discover_sessions.parse_time_expr(None, "UTC")
|
|
31
|
+
delta = tr.end_utc - tr.start_utc
|
|
32
|
+
self.assertEqual(delta, timedelta(hours=24))
|
|
33
|
+
self.assertIn("default", tr.expr)
|
|
34
|
+
|
|
35
|
+
def test_last_n_hours(self):
|
|
36
|
+
tr = discover_sessions.parse_time_expr("last 2 hours", "UTC")
|
|
37
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(hours=2))
|
|
38
|
+
self.assertEqual(tr.expr, "last 2 hours")
|
|
39
|
+
|
|
40
|
+
def test_last_n_minutes(self):
|
|
41
|
+
tr = discover_sessions.parse_time_expr("last 30 minutes", "UTC")
|
|
42
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(minutes=30))
|
|
43
|
+
|
|
44
|
+
def test_last_n_days(self):
|
|
45
|
+
tr = discover_sessions.parse_time_expr("last 3 days", "UTC")
|
|
46
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(days=3))
|
|
47
|
+
|
|
48
|
+
def test_last_n_bare_defaults_to_hours(self):
|
|
49
|
+
# Spec: "last 10" is ambiguous; the regex treats unit-less as hours.
|
|
50
|
+
tr = discover_sessions.parse_time_expr("last 10", "UTC")
|
|
51
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(hours=10))
|
|
52
|
+
|
|
53
|
+
def test_today_uses_local_tz_calendar_day(self):
|
|
54
|
+
tr = discover_sessions.parse_time_expr("today", "UTC")
|
|
55
|
+
# End - start should be exactly 24 hours.
|
|
56
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(days=1))
|
|
57
|
+
|
|
58
|
+
def test_yesterday_window_precedes_today(self):
|
|
59
|
+
today = discover_sessions.parse_time_expr("today", "UTC")
|
|
60
|
+
yest = discover_sessions.parse_time_expr("yesterday", "UTC")
|
|
61
|
+
self.assertEqual(yest.end_utc, today.start_utc)
|
|
62
|
+
self.assertEqual(yest.end_utc - yest.start_utc, timedelta(days=1))
|
|
63
|
+
|
|
64
|
+
def test_bare_iso_date(self):
|
|
65
|
+
tr = discover_sessions.parse_time_expr("2026-04-22", "UTC")
|
|
66
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(days=1))
|
|
67
|
+
self.assertEqual(tr.start_utc.year, 2026)
|
|
68
|
+
self.assertEqual(tr.start_utc.month, 4)
|
|
69
|
+
self.assertEqual(tr.start_utc.day, 22)
|
|
70
|
+
|
|
71
|
+
def test_iso_date_range_inclusive(self):
|
|
72
|
+
# "2026-04-22 to 2026-04-25" → 4 calendar days (22, 23, 24, 25).
|
|
73
|
+
tr = discover_sessions.parse_time_expr("2026-04-22 to 2026-04-25", "UTC")
|
|
74
|
+
self.assertEqual(tr.end_utc - tr.start_utc, timedelta(days=4))
|
|
75
|
+
|
|
76
|
+
def test_explicit_iso_datetime(self):
|
|
77
|
+
tr = discover_sessions.parse_time_expr(
|
|
78
|
+
"2026-04-22T10:00:00Z", "UTC"
|
|
79
|
+
)
|
|
80
|
+
self.assertEqual(tr.start_utc.year, 2026)
|
|
81
|
+
self.assertIn("since", tr.expr)
|
|
82
|
+
|
|
83
|
+
def test_unparseable_expression_raises(self):
|
|
84
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
85
|
+
discover_sessions.parse_time_expr("nope", "UTC")
|
|
86
|
+
self.assertIn("cannot parse", str(ctx.exception))
|
|
87
|
+
|
|
88
|
+
def test_unknown_tz_raises(self):
|
|
89
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
90
|
+
discover_sessions.parse_time_expr("today", "Mars/Olympus")
|
|
91
|
+
self.assertIn("unknown IANA timezone", str(ctx.exception))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# -----------------------------------------------------------------------------
|
|
95
|
+
# compose_sql — pure
|
|
96
|
+
# -----------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _basic_tr() -> discover_sessions.TimeRange:
|
|
100
|
+
return discover_sessions.TimeRange(
|
|
101
|
+
start_utc=datetime(2026, 4, 22, 0, 0, 0, tzinfo=timezone.utc),
|
|
102
|
+
end_utc=datetime(2026, 4, 22, 23, 0, 0, tzinfo=timezone.utc),
|
|
103
|
+
expr="test", tz_name="UTC",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ComposeSqlTests(unittest.TestCase):
|
|
108
|
+
|
|
109
|
+
def test_no_filters_skips_joins_and_uses_no_distinct(self):
|
|
110
|
+
sql = discover_sessions.compose_sql(
|
|
111
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
112
|
+
outcome=None, grep=None, limit=20,
|
|
113
|
+
)
|
|
114
|
+
# Strip SQL comments so the JOIN/DISTINCT mentions in the template
|
|
115
|
+
# docblock don't fool the naive substring check.
|
|
116
|
+
executable = "\n".join(
|
|
117
|
+
line for line in sql.splitlines() if not line.strip().startswith("--")
|
|
118
|
+
)
|
|
119
|
+
self.assertNotIn("JOIN ", executable)
|
|
120
|
+
self.assertNotIn("DISTINCT", executable)
|
|
121
|
+
self.assertIn("ssot__StartTimestamp__c >= '2026-04-22T00:00:00.000Z'", executable)
|
|
122
|
+
self.assertIn("ssot__StartTimestamp__c < '2026-04-22T23:00:00.000Z'", executable)
|
|
123
|
+
|
|
124
|
+
def test_agent_filter_adds_participant_join(self):
|
|
125
|
+
sql = discover_sessions.compose_sql(
|
|
126
|
+
tr=_basic_tr(), agent="MyAgent", channel=None,
|
|
127
|
+
outcome=None, grep=None, limit=20,
|
|
128
|
+
)
|
|
129
|
+
self.assertIn("ssot__AiAgentSessionParticipant__dlm", sql)
|
|
130
|
+
self.assertIn("p.ssot__AiAgentApiName__c = 'MyAgent'", sql)
|
|
131
|
+
self.assertIn("DISTINCT", sql)
|
|
132
|
+
|
|
133
|
+
def test_messaging_channel_maps_to_scrt2_string(self):
|
|
134
|
+
sql = discover_sessions.compose_sql(
|
|
135
|
+
tr=_basic_tr(), agent=None, channel="Messaging",
|
|
136
|
+
outcome=None, grep=None, limit=20,
|
|
137
|
+
)
|
|
138
|
+
self.assertIn("'SCRT2 - EmbeddedMessaging'", sql)
|
|
139
|
+
|
|
140
|
+
def test_unknown_channel_passes_through(self):
|
|
141
|
+
sql = discover_sessions.compose_sql(
|
|
142
|
+
tr=_basic_tr(), agent=None, channel="Custom_Channel",
|
|
143
|
+
outcome=None, grep=None, limit=20,
|
|
144
|
+
)
|
|
145
|
+
self.assertIn("'Custom_Channel'", sql)
|
|
146
|
+
|
|
147
|
+
def test_outcome_filter_added(self):
|
|
148
|
+
sql = discover_sessions.compose_sql(
|
|
149
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
150
|
+
outcome="ESCALATED", grep=None, limit=20,
|
|
151
|
+
)
|
|
152
|
+
self.assertIn("ssot__AiAgentSessionEndType__c = 'ESCALATED'", sql)
|
|
153
|
+
|
|
154
|
+
def test_grep_adds_interaction_message_joins(self):
|
|
155
|
+
# Default --grep is case-insensitive (§16d) — both sides are
|
|
156
|
+
# wrapped in LOWER(...). The case-sensitive variant is covered
|
|
157
|
+
# by test_grep_case_sensitive_opt_in below.
|
|
158
|
+
sql = discover_sessions.compose_sql(
|
|
159
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
160
|
+
outcome=None, grep="refund", limit=20,
|
|
161
|
+
)
|
|
162
|
+
self.assertIn("ssot__AiAgentInteraction__dlm", sql)
|
|
163
|
+
self.assertIn("ssot__AiAgentInteractionMessage__dlm", sql)
|
|
164
|
+
self.assertIn("LOWER(m.ssot__ContentText__c) LIKE LOWER('%refund%')", sql)
|
|
165
|
+
self.assertIn("ESCAPE '!'", sql)
|
|
166
|
+
|
|
167
|
+
def test_grep_case_sensitive_opt_in(self):
|
|
168
|
+
# --grep-case-sensitive bypasses the LOWER() wrap — restores the
|
|
169
|
+
# exact-match shape for the rare user who wants it.
|
|
170
|
+
sql = discover_sessions.compose_sql(
|
|
171
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
172
|
+
outcome=None, grep="Refund", grep_case_sensitive=True, limit=20,
|
|
173
|
+
)
|
|
174
|
+
self.assertIn("m.ssot__ContentText__c LIKE '%Refund%'", sql)
|
|
175
|
+
self.assertNotIn("LOWER(", sql)
|
|
176
|
+
self.assertIn("ESCAPE '!'", sql)
|
|
177
|
+
|
|
178
|
+
def test_grep_escapes_like_wildcards(self):
|
|
179
|
+
sql = discover_sessions.compose_sql(
|
|
180
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
181
|
+
outcome=None, grep="100%_off", limit=20,
|
|
182
|
+
)
|
|
183
|
+
# Both `%` and `_` get the `!` escape prefix
|
|
184
|
+
self.assertIn("100!%!_off", sql)
|
|
185
|
+
|
|
186
|
+
def test_grep_escapes_single_quotes(self):
|
|
187
|
+
sql = discover_sessions.compose_sql(
|
|
188
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
189
|
+
outcome=None, grep="O'Brien", limit=20,
|
|
190
|
+
)
|
|
191
|
+
# Single quote doubled per SQL escaping rule
|
|
192
|
+
self.assertIn("O''Brien", sql)
|
|
193
|
+
|
|
194
|
+
def test_limit_substituted(self):
|
|
195
|
+
sql = discover_sessions.compose_sql(
|
|
196
|
+
tr=_basic_tr(), agent=None, channel=None,
|
|
197
|
+
outcome=None, grep=None, limit=42,
|
|
198
|
+
)
|
|
199
|
+
self.assertIn("42", sql)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# -----------------------------------------------------------------------------
|
|
203
|
+
# fetch_agent_names — mocks dc.post
|
|
204
|
+
# -----------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class FetchAgentNamesTests(unittest.TestCase):
|
|
208
|
+
|
|
209
|
+
def test_empty_session_ids_returns_empty_dict_no_post(self):
|
|
210
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
211
|
+
out = discover_sessions.fetch_agent_names(
|
|
212
|
+
[], instance_url="https://x", token="t",
|
|
213
|
+
)
|
|
214
|
+
self.assertEqual(out, {})
|
|
215
|
+
p.assert_not_called()
|
|
216
|
+
|
|
217
|
+
def test_returns_dict_keyed_by_session_id(self):
|
|
218
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
219
|
+
p.return_value = [
|
|
220
|
+
{
|
|
221
|
+
"ssot__AiAgentSessionId__c": "sid1",
|
|
222
|
+
"ssot__AiAgentApiName__c": "AgentA",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"ssot__AiAgentSessionId__c": "sid2",
|
|
226
|
+
"ssot__AiAgentApiName__c": "AgentB",
|
|
227
|
+
},
|
|
228
|
+
]
|
|
229
|
+
out = discover_sessions.fetch_agent_names(
|
|
230
|
+
["sid1", "sid2"], instance_url="https://x", token="t",
|
|
231
|
+
)
|
|
232
|
+
self.assertEqual(out, {"sid1": "AgentA", "sid2": "AgentB"})
|
|
233
|
+
|
|
234
|
+
def test_skips_rows_with_empty_agent_name(self):
|
|
235
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
236
|
+
p.return_value = [
|
|
237
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": ""},
|
|
238
|
+
{"ssot__AiAgentSessionId__c": "sid2", "ssot__AiAgentApiName__c": "AgentB"},
|
|
239
|
+
]
|
|
240
|
+
out = discover_sessions.fetch_agent_names(
|
|
241
|
+
["sid1", "sid2"], instance_url="https://x", token="t",
|
|
242
|
+
)
|
|
243
|
+
self.assertEqual(out, {"sid2": "AgentB"})
|
|
244
|
+
|
|
245
|
+
def test_dcqueryerror_returns_empty_dict_non_fatal(self):
|
|
246
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
247
|
+
p.side_effect = DCQueryError("boom")
|
|
248
|
+
out = discover_sessions.fetch_agent_names(
|
|
249
|
+
["sid1"], instance_url="https://x", token="t",
|
|
250
|
+
)
|
|
251
|
+
self.assertEqual(out, {})
|
|
252
|
+
|
|
253
|
+
def test_uses_user_row_name_when_agent_row_is_not_set(self):
|
|
254
|
+
# MyAgent-shape session: AGENT row carries 'NOT_SET',
|
|
255
|
+
# USER row carries the real api_name. Old code filtered to
|
|
256
|
+
# role=AGENT and would return 'NOT_SET' (or nothing). New code
|
|
257
|
+
# is role-agnostic and recovers the api_name from the USER row.
|
|
258
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
259
|
+
p.return_value = [
|
|
260
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": "NOT_SET"},
|
|
261
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": "MyAgent"},
|
|
262
|
+
]
|
|
263
|
+
out = discover_sessions.fetch_agent_names(
|
|
264
|
+
["sid1"], instance_url="https://x", token="t",
|
|
265
|
+
)
|
|
266
|
+
self.assertEqual(out, {"sid1": "MyAgent"})
|
|
267
|
+
|
|
268
|
+
def test_drops_session_when_every_row_is_not_set(self):
|
|
269
|
+
# No usable api_name anywhere — picker should show '—' for the
|
|
270
|
+
# session, which is more honest than displaying the literal
|
|
271
|
+
# 'NOT_SET' string. Implementation contract: missing key in dict.
|
|
272
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
273
|
+
p.return_value = [
|
|
274
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": "NOT_SET"},
|
|
275
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": ""},
|
|
276
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": None},
|
|
277
|
+
]
|
|
278
|
+
out = discover_sessions.fetch_agent_names(
|
|
279
|
+
["sid1"], instance_url="https://x", token="t",
|
|
280
|
+
)
|
|
281
|
+
self.assertNotIn("sid1", out)
|
|
282
|
+
self.assertEqual(out, {})
|
|
283
|
+
|
|
284
|
+
def test_picks_lexicographic_first_on_handoff_session(self):
|
|
285
|
+
# Multi-agent handoff: two distinct AGENT api_names appear on
|
|
286
|
+
# different rows for the same session. Dominant-agent rule
|
|
287
|
+
# (sorted(...)[0]) matches the policy used by fetch_dc — every
|
|
288
|
+
# writer agrees on the session's namesake.
|
|
289
|
+
with mock.patch.object(discover_sessions, "post") as p:
|
|
290
|
+
p.return_value = [
|
|
291
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": "ZAgent"},
|
|
292
|
+
{"ssot__AiAgentSessionId__c": "sid1", "ssot__AiAgentApiName__c": "AAgent"},
|
|
293
|
+
]
|
|
294
|
+
out = discover_sessions.fetch_agent_names(
|
|
295
|
+
["sid1"], instance_url="https://x", token="t",
|
|
296
|
+
)
|
|
297
|
+
self.assertEqual(out, {"sid1": "AAgent"})
|
|
298
|
+
|
|
299
|
+
def test_sql_does_not_filter_by_role(self):
|
|
300
|
+
# Lock the no-role-filter contract — if a future "tighten this
|
|
301
|
+
# back up" refactor re-adds the AGENT filter, MyAgent
|
|
302
|
+
# sessions silently regress to '—' or 'NOT_SET' in the picker.
|
|
303
|
+
captured = {}
|
|
304
|
+
|
|
305
|
+
def fake_post(sql, *_a, **_k):
|
|
306
|
+
captured["sql"] = sql
|
|
307
|
+
return []
|
|
308
|
+
|
|
309
|
+
with mock.patch.object(discover_sessions, "post", side_effect=fake_post):
|
|
310
|
+
discover_sessions.fetch_agent_names(
|
|
311
|
+
["sid1"], instance_url="https://x", token="t",
|
|
312
|
+
)
|
|
313
|
+
self.assertNotIn("AiAgentSessionParticipantRole", captured["sql"])
|
|
314
|
+
self.assertNotIn("'AGENT'", captured["sql"])
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# -----------------------------------------------------------------------------
|
|
318
|
+
# render_picker — pure
|
|
319
|
+
# -----------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _picker_filters(**overrides) -> dict:
|
|
323
|
+
base = {"agent": None, "channel": None, "outcome": None, "grep": None}
|
|
324
|
+
base.update(overrides)
|
|
325
|
+
return base
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class RenderPickerTests(unittest.TestCase):
|
|
329
|
+
|
|
330
|
+
def test_zero_rows_emits_widen_hint_with_composed_sql(self):
|
|
331
|
+
out = discover_sessions.render_picker(
|
|
332
|
+
rows=[], agent_by_sid={}, org="my-org",
|
|
333
|
+
tr=_basic_tr(), filters=_picker_filters(),
|
|
334
|
+
composed_sql="SELECT 1",
|
|
335
|
+
)
|
|
336
|
+
self.assertIn("No sessions matched", out)
|
|
337
|
+
self.assertIn("```sql\nSELECT 1\n```", out)
|
|
338
|
+
self.assertIn("Try widening", out)
|
|
339
|
+
|
|
340
|
+
def test_renders_markdown_table_with_one_row(self):
|
|
341
|
+
rows = [{
|
|
342
|
+
"ssot__Id__c": "019dface-0000-7000-8000-000000000001",
|
|
343
|
+
"ssot__StartTimestamp__c": "2026-04-22T10:00:00Z",
|
|
344
|
+
"ssot__EndTimestamp__c": "2026-04-22T10:01:30Z",
|
|
345
|
+
"ssot__AiAgentChannelType__c": "SCRT2 - EmbeddedMessaging",
|
|
346
|
+
"ssot__AiAgentSessionEndType__c": "USER_ENDED",
|
|
347
|
+
}]
|
|
348
|
+
out = discover_sessions.render_picker(
|
|
349
|
+
rows=rows, agent_by_sid={"019dface-0000-7000-8000-000000000001": "AgentA"},
|
|
350
|
+
org="my-org", tr=_basic_tr(),
|
|
351
|
+
filters=_picker_filters(), composed_sql="",
|
|
352
|
+
)
|
|
353
|
+
self.assertIn("Found **1** session", out)
|
|
354
|
+
self.assertIn("`019dface-0000-7000-8000-000000000001`", out)
|
|
355
|
+
self.assertIn("AgentA", out)
|
|
356
|
+
# Reverse-aliased channel
|
|
357
|
+
self.assertIn("Messaging", out)
|
|
358
|
+
# Duration formatter
|
|
359
|
+
self.assertIn("1m 30s", out)
|
|
360
|
+
self.assertIn("USER_ENDED", out)
|
|
361
|
+
|
|
362
|
+
def test_handles_session_without_known_agent_with_dash(self):
|
|
363
|
+
rows = [{
|
|
364
|
+
"ssot__Id__c": "sid1",
|
|
365
|
+
"ssot__StartTimestamp__c": "2026-04-22T10:00:00Z",
|
|
366
|
+
"ssot__EndTimestamp__c": "2026-04-22T10:00:30Z",
|
|
367
|
+
"ssot__AiAgentChannelType__c": "Builder",
|
|
368
|
+
"ssot__AiAgentSessionEndType__c": None,
|
|
369
|
+
}]
|
|
370
|
+
out = discover_sessions.render_picker(
|
|
371
|
+
rows=rows, agent_by_sid={}, org="my-org",
|
|
372
|
+
tr=_basic_tr(), filters=_picker_filters(), composed_sql="",
|
|
373
|
+
)
|
|
374
|
+
self.assertIn("| — |", out) # agent + outcome both render as "—"
|
|
375
|
+
self.assertIn("30s", out)
|
|
376
|
+
|
|
377
|
+
def test_filter_bits_appear_in_header(self):
|
|
378
|
+
rows = [{
|
|
379
|
+
"ssot__Id__c": "sid1",
|
|
380
|
+
"ssot__StartTimestamp__c": "2026-04-22T10:00:00Z",
|
|
381
|
+
"ssot__EndTimestamp__c": "2026-04-22T10:00:30Z",
|
|
382
|
+
"ssot__AiAgentChannelType__c": "Builder",
|
|
383
|
+
"ssot__AiAgentSessionEndType__c": "ESCALATED",
|
|
384
|
+
}]
|
|
385
|
+
out = discover_sessions.render_picker(
|
|
386
|
+
rows=rows, agent_by_sid={}, org="my-org", tr=_basic_tr(),
|
|
387
|
+
filters=_picker_filters(agent="MyAgent", outcome="ESCALATED"),
|
|
388
|
+
composed_sql="",
|
|
389
|
+
)
|
|
390
|
+
self.assertIn("agent=MyAgent", out)
|
|
391
|
+
self.assertIn("outcome=ESCALATED", out)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# -----------------------------------------------------------------------------
|
|
395
|
+
# main — argv-driven exit codes
|
|
396
|
+
# -----------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class MainTests(unittest.TestCase):
|
|
400
|
+
|
|
401
|
+
def _argv(self, *extra: str) -> list[str]:
|
|
402
|
+
# `--org` is required; everything else has a default or is optional.
|
|
403
|
+
return ["discover_sessions.py", "--org", "my-org", *extra]
|
|
404
|
+
|
|
405
|
+
def test_main_exit_zero_when_rows_returned(self):
|
|
406
|
+
with mock.patch.object(discover_sessions.sys, "argv", self._argv()):
|
|
407
|
+
with mock.patch.object(
|
|
408
|
+
discover_sessions, "resolve_org",
|
|
409
|
+
return_value=("https://example.salesforce.com", "TOKEN"),
|
|
410
|
+
):
|
|
411
|
+
with mock.patch.object(
|
|
412
|
+
discover_sessions, "post",
|
|
413
|
+
return_value=[{
|
|
414
|
+
"ssot__Id__c": "sid1",
|
|
415
|
+
"ssot__StartTimestamp__c": "2026-04-22T00:00:00Z",
|
|
416
|
+
"ssot__EndTimestamp__c": "2026-04-22T00:01:00Z",
|
|
417
|
+
"ssot__AiAgentChannelType__c": "Builder",
|
|
418
|
+
"ssot__AiAgentSessionEndType__c": "USER_ENDED",
|
|
419
|
+
}],
|
|
420
|
+
):
|
|
421
|
+
rc = discover_sessions.main()
|
|
422
|
+
self.assertEqual(rc, 0)
|
|
423
|
+
|
|
424
|
+
def test_main_exit_two_when_zero_rows(self):
|
|
425
|
+
with mock.patch.object(discover_sessions.sys, "argv", self._argv()):
|
|
426
|
+
with mock.patch.object(
|
|
427
|
+
discover_sessions, "resolve_org",
|
|
428
|
+
return_value=("https://x", "T"),
|
|
429
|
+
):
|
|
430
|
+
with mock.patch.object(discover_sessions, "post", return_value=[]):
|
|
431
|
+
rc = discover_sessions.main()
|
|
432
|
+
self.assertEqual(rc, 2)
|
|
433
|
+
|
|
434
|
+
def test_main_exit_one_when_dc_query_fails(self):
|
|
435
|
+
with mock.patch.object(discover_sessions.sys, "argv", self._argv()):
|
|
436
|
+
with mock.patch.object(
|
|
437
|
+
discover_sessions, "resolve_org",
|
|
438
|
+
return_value=("https://x", "T"),
|
|
439
|
+
):
|
|
440
|
+
with mock.patch.object(
|
|
441
|
+
discover_sessions, "post",
|
|
442
|
+
side_effect=DCQueryError("HTTP 500"),
|
|
443
|
+
):
|
|
444
|
+
rc = discover_sessions.main()
|
|
445
|
+
self.assertEqual(rc, 1)
|
|
446
|
+
|
|
447
|
+
def test_main_rejects_zero_or_negative_limit(self):
|
|
448
|
+
with mock.patch.object(
|
|
449
|
+
discover_sessions.sys, "argv",
|
|
450
|
+
self._argv("--limit", "0"),
|
|
451
|
+
):
|
|
452
|
+
with self.assertRaises(SystemExit) as ctx:
|
|
453
|
+
discover_sessions.main()
|
|
454
|
+
self.assertIn("--limit must be >= 1", str(ctx.exception))
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
if __name__ == "__main__":
|
|
458
|
+
unittest.main()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Regression guard for §16d — case-insensitive ``--grep`` default.
|
|
2
|
+
|
|
3
|
+
§16d flipped the default behaviour of ``--grep`` from case-sensitive
|
|
4
|
+
exact match to case-insensitive (``LOWER(...) LIKE LOWER(...)``).
|
|
5
|
+
Operators almost always type the quoted phrase in whatever case they
|
|
6
|
+
remember, not the case the agent rendered it in — the old default
|
|
7
|
+
silently dropped legitimate matches.
|
|
8
|
+
|
|
9
|
+
The compose_sql shape is already pinned in
|
|
10
|
+
``test_discover_sessions.py`` (LOWER wrap on default, bypass on opt-in,
|
|
11
|
+
escape semantics). What this file pins down is the
|
|
12
|
+
**argparse → filters dict → picker header** pipeline — the integration
|
|
13
|
+
seams where a refactor could:
|
|
14
|
+
|
|
15
|
+
- flip the argparse default back to ``True`` (forgot ``default=False``)
|
|
16
|
+
- drop the ``grep_case_sensitive`` key from the filters dict (header
|
|
17
|
+
no longer warns the operator that the picker is in opt-in mode)
|
|
18
|
+
- surface ``grep_case_sensitive`` even when ``--grep`` isn't set
|
|
19
|
+
(false signal in the header for unrelated runs)
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import unittest
|
|
25
|
+
from unittest import mock
|
|
26
|
+
|
|
27
|
+
from . import _bootstrap # noqa: F401 — sys.path setup
|
|
28
|
+
|
|
29
|
+
import discover_sessions # type: ignore
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class GrepCaseSensitiveArgparseDefaultTests(unittest.TestCase):
|
|
33
|
+
"""The argparse flag must default to ``False`` so case-insensitive
|
|
34
|
+
is the path of least resistance."""
|
|
35
|
+
|
|
36
|
+
def _build_parser(self) -> argparse.ArgumentParser:
|
|
37
|
+
# Re-export the parser the same way main() does — we don't have
|
|
38
|
+
# a parser factory, so spy on the module-level ``argparse`` to
|
|
39
|
+
# capture the parser before main() invokes parse_args().
|
|
40
|
+
captured: dict[str, argparse.ArgumentParser] = {}
|
|
41
|
+
real_parse_args = argparse.ArgumentParser.parse_args
|
|
42
|
+
|
|
43
|
+
def capture(self, *a, **kw): # type: ignore[no-redef]
|
|
44
|
+
captured["parser"] = self
|
|
45
|
+
# Stop main() before it calls anything network-y.
|
|
46
|
+
raise SystemExit(0)
|
|
47
|
+
|
|
48
|
+
with mock.patch.object(argparse.ArgumentParser, "parse_args", capture):
|
|
49
|
+
with mock.patch.object(
|
|
50
|
+
discover_sessions.sys, "argv",
|
|
51
|
+
["discover_sessions.py", "--org", "x"],
|
|
52
|
+
):
|
|
53
|
+
with self.assertRaises(SystemExit):
|
|
54
|
+
discover_sessions.main()
|
|
55
|
+
# Restore (the patch.object exit already did this, but be explicit
|
|
56
|
+
# in case something held onto the bound method.)
|
|
57
|
+
argparse.ArgumentParser.parse_args = real_parse_args
|
|
58
|
+
return captured["parser"]
|
|
59
|
+
|
|
60
|
+
def test_grep_case_sensitive_default_is_false(self):
|
|
61
|
+
# parse_args without --grep-case-sensitive must return False.
|
|
62
|
+
parser = self._build_parser()
|
|
63
|
+
ns = parser.parse_args(["--org", "x"])
|
|
64
|
+
self.assertFalse(ns.grep_case_sensitive,
|
|
65
|
+
"default flipped — case-insensitive was the §16d "
|
|
66
|
+
"decision; flipping default=False back to True "
|
|
67
|
+
"regresses to the pre-§16d behaviour")
|
|
68
|
+
|
|
69
|
+
def test_grep_case_sensitive_opt_in_via_flag(self):
|
|
70
|
+
# Pass the flag explicitly — must be True.
|
|
71
|
+
parser = self._build_parser()
|
|
72
|
+
ns = parser.parse_args(["--org", "x", "--grep-case-sensitive"])
|
|
73
|
+
self.assertTrue(ns.grep_case_sensitive)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class FiltersDictSurfacesCaseSensitiveTests(unittest.TestCase):
|
|
77
|
+
"""The filters dict feeds render_picker's header. Without a marker,
|
|
78
|
+
the operator can't tell whether their grep is in case-insensitive
|
|
79
|
+
(default) or case-sensitive (opt-in) mode — a silent footgun when
|
|
80
|
+
a query returns zero rows in opt-in mode by accident."""
|
|
81
|
+
|
|
82
|
+
def _run_main_and_capture_filters(self, *extra_argv: str) -> dict:
|
|
83
|
+
# Stub resolve_org + post so main() doesn't talk to DC, then
|
|
84
|
+
# intercept render_picker to capture the filters dict main()
|
|
85
|
+
# constructed.
|
|
86
|
+
captured: dict[str, dict] = {}
|
|
87
|
+
|
|
88
|
+
def fake_render(*, filters, **_kw):
|
|
89
|
+
captured["filters"] = filters
|
|
90
|
+
return "ok"
|
|
91
|
+
|
|
92
|
+
argv = ["discover_sessions.py", "--org", "x", *extra_argv]
|
|
93
|
+
with mock.patch.object(discover_sessions.sys, "argv", argv), \
|
|
94
|
+
mock.patch.object(
|
|
95
|
+
discover_sessions, "resolve_org",
|
|
96
|
+
return_value=("https://x", "T"),
|
|
97
|
+
), \
|
|
98
|
+
mock.patch.object(
|
|
99
|
+
discover_sessions, "post",
|
|
100
|
+
return_value=[{
|
|
101
|
+
"ssot__Id__c": "sid1",
|
|
102
|
+
"ssot__StartTimestamp__c": "2026-04-22T00:00:00Z",
|
|
103
|
+
"ssot__EndTimestamp__c": "2026-04-22T00:01:00Z",
|
|
104
|
+
"ssot__AiAgentChannelType__c": "Builder",
|
|
105
|
+
"ssot__AiAgentSessionEndType__c": "USER_ENDED",
|
|
106
|
+
}],
|
|
107
|
+
), \
|
|
108
|
+
mock.patch.object(
|
|
109
|
+
discover_sessions, "render_picker",
|
|
110
|
+
side_effect=fake_render,
|
|
111
|
+
):
|
|
112
|
+
discover_sessions.main()
|
|
113
|
+
return captured["filters"]
|
|
114
|
+
|
|
115
|
+
def test_filters_omit_case_sensitive_when_grep_unset(self):
|
|
116
|
+
# No --grep at all: surfacing case-sensitive=yes in the header
|
|
117
|
+
# would be a meaningless artifact (there's no grep to be
|
|
118
|
+
# case-sensitive about). Contract: the value is None so
|
|
119
|
+
# render_picker's `if v:` filter drops the bit.
|
|
120
|
+
filters = self._run_main_and_capture_filters()
|
|
121
|
+
self.assertIsNone(filters["grep_case_sensitive"])
|
|
122
|
+
|
|
123
|
+
def test_filters_omit_case_sensitive_when_grep_set_but_default(self):
|
|
124
|
+
# --grep set, --grep-case-sensitive NOT set: default mode is
|
|
125
|
+
# case-insensitive. Don't surface the marker — the absence is
|
|
126
|
+
# the default and surfacing "yes/no" on every grep would just
|
|
127
|
+
# add noise to the picker header.
|
|
128
|
+
filters = self._run_main_and_capture_filters("--grep", "refund")
|
|
129
|
+
self.assertEqual(filters["grep"], "refund")
|
|
130
|
+
self.assertIsNone(filters["grep_case_sensitive"])
|
|
131
|
+
|
|
132
|
+
def test_filters_surface_case_sensitive_only_when_both_set(self):
|
|
133
|
+
# The interesting case: operator opted in. The header MUST tell
|
|
134
|
+
# them, otherwise a zero-row return looks identical to a
|
|
135
|
+
# case-insensitive zero-row return.
|
|
136
|
+
filters = self._run_main_and_capture_filters(
|
|
137
|
+
"--grep", "Refund", "--grep-case-sensitive",
|
|
138
|
+
)
|
|
139
|
+
self.assertEqual(filters["grep"], "Refund")
|
|
140
|
+
self.assertEqual(filters["grep_case_sensitive"], "yes")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class RenderPickerHeaderShowsCaseSensitiveTests(unittest.TestCase):
|
|
144
|
+
"""End-to-end: when filters dict surfaces ``grep_case_sensitive``,
|
|
145
|
+
render_picker must include it in the header."""
|
|
146
|
+
|
|
147
|
+
def _row(self) -> dict:
|
|
148
|
+
return {
|
|
149
|
+
"ssot__Id__c": "sid1",
|
|
150
|
+
"ssot__StartTimestamp__c": "2026-04-22T10:00:00Z",
|
|
151
|
+
"ssot__EndTimestamp__c": "2026-04-22T10:00:30Z",
|
|
152
|
+
"ssot__AiAgentChannelType__c": "Builder",
|
|
153
|
+
"ssot__AiAgentSessionEndType__c": "USER_ENDED",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def _tr(self) -> "discover_sessions.TimeRange":
|
|
157
|
+
from datetime import datetime, timezone
|
|
158
|
+
return discover_sessions.TimeRange(
|
|
159
|
+
start_utc=datetime(2026, 4, 22, 0, 0, 0, tzinfo=timezone.utc),
|
|
160
|
+
end_utc=datetime(2026, 4, 22, 23, 0, 0, tzinfo=timezone.utc),
|
|
161
|
+
expr="test", tz_name="UTC",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def test_header_includes_case_sensitive_bit_when_set(self):
|
|
165
|
+
out = discover_sessions.render_picker(
|
|
166
|
+
rows=[self._row()], agent_by_sid={}, org="my-org",
|
|
167
|
+
tr=self._tr(),
|
|
168
|
+
filters={
|
|
169
|
+
"agent": None, "channel": None, "outcome": None,
|
|
170
|
+
"grep": "Refund", "grep_case_sensitive": "yes",
|
|
171
|
+
},
|
|
172
|
+
composed_sql="",
|
|
173
|
+
)
|
|
174
|
+
self.assertIn("grep=Refund", out)
|
|
175
|
+
self.assertIn("grep_case_sensitive=yes", out)
|
|
176
|
+
|
|
177
|
+
def test_header_omits_case_sensitive_bit_in_default_mode(self):
|
|
178
|
+
# Default-mode grep (no opt-in): the bit must NOT appear.
|
|
179
|
+
out = discover_sessions.render_picker(
|
|
180
|
+
rows=[self._row()], agent_by_sid={}, org="my-org",
|
|
181
|
+
tr=self._tr(),
|
|
182
|
+
filters={
|
|
183
|
+
"agent": None, "channel": None, "outcome": None,
|
|
184
|
+
"grep": "refund", "grep_case_sensitive": None,
|
|
185
|
+
},
|
|
186
|
+
composed_sql="",
|
|
187
|
+
)
|
|
188
|
+
self.assertIn("grep=refund", out)
|
|
189
|
+
self.assertNotIn("grep_case_sensitive", out)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
unittest.main()
|