MWPW-183572: MAS Studio — AI assistant, product catalog, release flow, OST monorepo integration#776
Closed
Axelcureno wants to merge 89 commits into
Closed
MWPW-183572: MAS Studio — AI assistant, product catalog, release flow, OST monorepo integration#776Axelcureno wants to merge 89 commits into
Axelcureno wants to merge 89 commits into
Conversation
|
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch.
Commits
|
…ntime Delivers the MAS Studio AI assistant experience and its supporting infrastructure. Everything ships in one commit so reviewers see a coherent feature set. Studio frontend (studio/src/): - AI chat UI: mas-chat.js + mas-chat-message.js + mas-chat-input.js + mas-chat-product-cards.js + mas-chat-session-selector.js + mas-chat-confirmation-summary.js + mas-chat-button-group.js + mas-chat-drawer.js + mas-chat-fab.js. Persistent sessions via chat-session-manager, markdown rendering with an LRU-bounded cache, operation-preview and destructive-tool confirmation gating. - Product catalog (mas-product-catalog.js + mas-product-detail.js) with multi-select filters, surface-scoped editor/settings access via groups.js (isMasAdmin + canAccessSettings + getUserSurfaces), DRAFT landscape toggle, applied-filter chips. - MCS-integrated release flow: deterministic fragment content from MCS merchandising data, dual-OSI plumbing for plans variants, multi-select OST hooked through rte/ost.js. - Shared services: mcp-client.js (defensive guards on response shape, AbortController on chat fetch, error-log scrubbing via logError). - utils/ai-card-mapper.js: XSS-escaped badge rendering, falsy-value preservation on fragment fields, trim-aware title fallbacks. - utils/mas-chat-helpers.js: pure helpers extracted for unit testing. IO Runtime - AI chat backend (io/studio/src/ai-chat/): - bedrock-client.js with prompt-injection defense via wrapUntrusted sentinel envelopes; attached-card fragment IDs wrapped element-wise instead of raw JSON.stringify. - operations-handler, operations-prompt, prompt-templates, response-parser, variant-configs, variant-knowledge-builder, knowledge-client, validation, index.js. IO Runtime - MCP server actions (io/mcp-server/): - 31 runtime actions (card + collection + offer + product CRUD/search). - Surface-scoped authz on fragment-creating actions (create-release-cards, create-card, create-collection, copy-card): deriveSurfaceFromPath + fetchUserGroups + canEditSurface + requireSurfaceAccess helpers in lib/ims-validator.js. Prevents cross-surface mutation via direct API calls. mas-mcp-server (stdio entry for Claude Code / Cursor): - Retired the Express HTTP bridge (http-server.js). Dropped cors/express deps + the 'http' npm script. Web-components: - Small audit touches on aem-fragment, merch-card-collection, variants/full-pricing-express, variants/simplified-pricing-express. Testing: - 10 new characterization-test files in studio/test/** covering every new service/util plus 2 XSS + 1 prompt-injection repro. - 2 new test files in io/studio/test/ai-chat/. - 1 new test file in io/mcp-server/test/lib/ covering surface authz (23 tests: canEditSurface matrix, fetchUserGroups, full middleware integration). Out of scope (deferred to follow-ups): - Per-fragment surface authz on id-based MCP mutations (update/delete/publish/bulk-*) - currently rely on AEM's own ACLs. - Translation work that entered this branch via a main merge is included as-is from main (no ticket changes to the translation subsystem).
Moves the OST (Offer Selector Tool) source from the separate tacocat.js/mas-ost tree into this repo as the 4th npm workspace alongside studio/, web-components/, and io/studio/. Changes: - mas-ost/ tree with Lit 3 + Spectrum Web Components source, Vite build config, WTR tests, and dev HTML pages. 63 source + test files extracted from tacocat.js commit f5a71b9. - Root package.json registers mas-ost in the workspaces array. - mas-ost/package.json build script outputs the IIFE bundle and copies it to ../studio/ost/index.js in one step (replaces the previous cross-repo deploy:local workflow). - Root package-lock.json regenerated by npm install to wire the new workspace and dedupe Lit/SWC against the root. Bundle output (studio/ost/index.js) is already in the previous commit alongside the frontend consumer changes. This commit adds the source side so the OST is maintained in-repo going forward. Known: 4 mas-ost unit tests fail upstream (3 country-picker SWC lifecycle + 1 OstStore.addOffer.tryBuy). Inherited from the source, not regressions from the relocation. Tracked as follow-up.
- Deterministic router for bare offer IDs, arrangement codes, and OSIs so PA codes and arrangement slugs (e.g. cptv_direct_individual) no longer misclassify as OSIs. - Offer-id fallback in resolveOfferSelector for 32-hex IDs that OST surfaces in the OSI slot for draft offers. - Trial-CTA yes/no step inserted on the OST Use shortcut path so users can still add a free-trial offer when seeding from a single offer. - Rename "Annual, paid monthly" to "Annual, billed monthly" (ABM). - Product card auto-selects its single match via productCardsSelectedValue so the preview card renders as confirmed, not clickable. - OST: pin filled-slot colors to light-palette hex so labels stay readable against saturated-green backgrounds in dark themes. - OST: hide the "BOTH" landscape badge (kept per-offer source badges). - Replace sp-icon-offer with sp-icon-label for the Search offers chip.
…ent_code Replace LLM-mediated list_products call in release flow with a direct get_product_by_arrangement_code IO action — prevents hallucinated arrangement codes from selecting the wrong product.
Adds a hybrid frontend/backend search path that bypasses the LLM for high-confidence patterns (UUID, OSI, "find cards titled X") and surfaces results as clickable Studio deep-links instead of hydrated card previews. Frontend - studio/src/utils/ai-chat-search-router.js: pure intent classifier with UUID / OSI / offer-id / quoted-title / titled-verb detectors and slot resolution; returns either a deterministic dispatch or a missing-slot follow-up prompt. - studio/src/utils/ai-chat-search-telemetry.js: dark-launch flag (localStorage.masChatDeterministicSearch) and dataLayer events. - studio/src/mas-chat.js: tryDeterministicSearch hook in handleSendMessage, pendingSearchIntent state for surface follow-up, get_card 404 graceful conversion, polite empty-result pivots. - studio/src/mas-operation-result.js: new mode=links rendering path that renders an sp-table of anchor-tag rows with no aem-fragment hydration, plus a "View all N in Studio" surface-folder link. - studio/src/utils.js: buildStudioFolderHref helper. - studio/src/utils/mas-chat-helpers.js: extractKnownSurfaceFromPath now handles the short URL forms used by Studio's hash router. Backend (MerchAtScaleMCP) - io/mcp-server/src/actions/search-by-id.js: new fast-path action for UUID/OSI lookups with structured 5s timeout and graceful empty-result on miss (no throw on 404). - io/mcp-server/src/actions/search-cards.js: 5s default / 15s title-search Promise.race timeout, hard 200-result cap, id/osi short-circuit. - io/mcp-server/src/lib/studio-operations.js: searchById method, mapWithConcurrency helper replacing 3 unbounded Promise.all fan-outs, extractFieldValue helper deduplicating OSI extraction, structured SURFACE_REQUIRED error replacing throw, new two-stage title-substring search (full-text narrow with strongest non-stopword token + local title-field substring filter). - io/mcp-server/src/lib/aem-client.js: AbortSignal.timeout per page in findFragmentsByTitleFallback. - io/mcp-server/app.config.yaml: register search-by-id action. Tests - 27 router unit tests (UUID/OSI/offer-id/quoted/titled-verb/slot/resume). - 4 component tests verifying no merch-card hydration in links mode. - 9 backend tests for searchById and structured surface error. End-to-end verified against the deployed action: title-substring search returns 52 "Wide Card" matches in ~3.5s, 40 "Photoshop" matches in ~4.6s, real UUID lookup returns the fragment in less than 1s, fake UUID returns polite "No card found" with two pivots, LLM fallback intact for non-search asks.
The deterministic search router now extracts the locale slot from the message itself instead of always using the user's current locale: - 'in all locales' / 'across all locales' / 'across every locale' → 'all' (backend scans /content/dam/mas/<surface> across every locale folder) - 'in fr_FR' / 'for de_DE' → that explicit locale - otherwise → currentLocale from context Verified end-to-end: 'Find me all cards with fragment title "CC Plans Merch Card: Firefly Pro Plus: Individuals: 50-percent-promo" in all locales' returns 24 matches across sv_SE, tr_TR, ko_KR, fr_FR, lt_LT, he_IL etc. — instead of being silently scoped to the current locale.
The router is now on for everyone instead of behind a localStorage flag. This was a dark-launch gate for the initial rollout; testers on the staged branch (mwpw-183572--mas--adobecom.aem.live) hit the LLM-only path and saw the same unreliability the router was built to fix. Emergency opt-out: localStorage.masChatDeterministicSearch = 'off' Telemetry continues to record source: 'router' vs 'llm' for monitoring.
User reported a result-count discrepancy: AEM admin returned 50+ matches
for a card title across all locales but the AI assistant capped at 24.
Root cause was twofold:
1. We passed the strongest single token (e.g. 'firefly') to AEM's
full-text search instead of the full title — letting the index
narrow on the wrong axis and pulling thousands of unrelated cards
into the candidate pool before the local title-substring filter.
2. The candidate pool was capped at 4 offset pages of 50 = 200 items,
so for any common token we silently dropped real matches.
Fix mirrors what the production studio search already does in
studio/src/aem/aem.js: pass the user's full query as fullText.text and
iterate AEM's cursor until exhausted.
aem-client.js
- searchFragments now accepts cursor and includeCursor params.
Backwards compatible: returns array by default; returns
{items, cursor} only when includeCursor=true.
- When cursor is provided, sends it as a query param in place of offset.
studio-operations.js
- Title-search branch replaces 4-page Promise.all fan-out with a cursor
loop bounded at 40 pages (safety net inside the action's 15s timeout).
- Sends the user's full query as fullText.text so AEM does the actual
narrowing.
- Removes pickStrongestToken (now dead code).
Verified end-to-end: the user's exact prompt
'Find me all cards with fragment title "CC Plans Merch Card: Firefly
Pro Plus: Individuals: 50-percent-promo" in all locales'
now returns 78 cards spanning 39 locales (sv_SE, tr_TR, ko_KR, fr_FR,
lt_LT, he_IL, vi_VN, ro_RO, ja_JP, de_DE, ...) in ~9.5s — exceeding
the 50+ AEM admin shows because we also include the OPT-40208 promo
variants in every locale.
Tests: 49 mcp-server tests passing (44 prior + 5 new cursor tests).
…inks Two issues from a user report on the AI assistant search results: 1. The "Copy all links" button only appeared on the legacy (LLM-driven) render path. Deterministic-router results in the new lightweight mode were missing it. 2. The copy payload was markdown ([title](url)) — paste targets like Slack, Outlook, Confluence don't render markdown, so users got the raw text. Fix - mas-operation-result.js: lightweight render now shows all three actions consistently — Show N more, Copy all links, View all in Studio →. The Copy all links button is no longer conditional. - copyAllCardLinks now writes both text/html and text/plain to the clipboard via ClipboardItem when supported. The HTML payload is a <ul><li><a href="...">title</a></li></ul> list, so rich-text editors paste real clickable hyperlinks. Plain-text fallback (title \n url per entry) keeps backwards compatibility for terminal-style targets and older browsers. - Tooltip wording updated from "as a markdown list" to "Copy all card links". - Adds local escapeHtml helper to prevent broken HTML when titles contain & < > " '. Component tests still pass (4 lightweight-mode component tests, 32 router unit tests).
User feedback: action row was cramped (no gap), View all was an underlined link instead of a button, and all visible buttons looked identical (same secondary variant). mas-operation-result.js - View all in Studio is now an sp-button (primary variant, href + target=_blank) instead of a styled anchor. - Show more uses treatment=outline for low-emphasis disclosure. - Copy all links keeps the secondary fill for medium emphasis. - Pattern applied to both lightweight and legacy renderers so searches look the same regardless of which path produced them. style.css - .search-results-actions: gap 12px (size-150) between buttons, flex-wrap for narrow chat panes, align-items: center. mas-operation-result-links.test.js - View-all assertion updated from anchor.view-all-link to sp-button[variant=primary] with the same href contract. - New tests: Copy and View buttons always render; Show-more renders only when results exceed displayCount.
Previous attempt added gap to studio/style.css, but the chat panel loads styles from studio/src/styles/chat.css which had its own duplicated .search-results-actions rule without gap — so buttons stayed touching. Fix: dedupe the two rules in chat.css, fold in the user's manual margin-left workaround as a sibling-selector fallback for older Spectrum versions, bump gap to size-200 (16px) so the spacing is clearly visible. Both rules (style.css and chat.css) now match: flex with gap, align-items: center, flex-wrap for narrow chat panes.
Conflicts resolved: - web-components/dist/mas.js, merch-card.js, merch-card-collection.js (auto-resolved, then rebuilt from source) - .gitignore: kept both branch-local entries and main's mcp-server.log - io/studio/app.config.yaml: kept both ai-chat (branch) and bulk-publish (main) action declarations - studio/src/rte/ost.js: kept both EVENT_OST_MULTI_OFFER_SELECT (branch) and PLACEHOLDER_CTA_SURFACES (main) imports - web-components/src/variants/simplified-pricing-express.js: combined main's CTA-class refactor (small-font-size-button) with branch's requestAnimationFrame-wrapped syncHeights call Verified post-merge: - web-components: 1286 tests passing - io/mcp-server: 49 tests passing (router + searchById + cursor) - studio router: 38 tests passing (32 router + 6 component)
User feedback: "search for cards containing firefly" should return all
cards that mention firefly anywhere — title, description, CTAs,
prices, etc. — not just title matches.
Frontend: ai-chat-search-router.js
- New CONTENT_VERB_RE matches "find/show/search/list cards
containing|with|mentioning|about|having|referencing|including X"
- New buildContentDispatch + buildContentDispatchFromSlots helpers
that emit search_cards WITHOUT titleSearch=true so AEM full-text
covers all indexed fields
- resumeWithSlot routes to title or content dispatch based on
pending.intent
- Locale parsing ("in all locales", "in fr_FR") works for content
search the same as title search
Frontend: mas-chat.js
- maybeOfferSearchPivots now emits "No cards contain X..." for the
content-search intent and updates the help line to mention "title
or content" alongside ID/OSI/offer ID
Backend: studio-operations.js
- Default keyword-search path (the non-titleSearch branch) now uses
AEM cursor pagination instead of single-page offset + N+1
getFragment. Pulls all matches up to MAX_PAGES=40 (2000 fragments
max) wrapped by the action timeout.
- Removed the per-fragment getFragment fan-out: searchFragments
already returns full fields arrays so the round trip was wasted.
- Removed unused requestLimit local.
Backend: search-cards.js
- Renamed TITLE_SEARCH_TIMEOUT_MS to KEYWORD_SEARCH_TIMEOUT_MS
(15s) and applied it whenever query is set, not just when
titleSearch is true. Keyword/content/title searches all paginate
via cursor and benefit from the longer budget.
Tests
- 7 new router unit tests covering "containing", "mentioning",
"with", "about" phrasings, missing-surface slot fill, locale
carry-through, and resumeWithSlot routing for content-search.
- All 49 mcp-server tests still passing.
- Verified end-to-end: "search for cards containing firefly" with
surface=acom locale=en_US returns 115 cards in ~9s, rendered in
the lightweight links table with three properly spaced action
buttons.
…ault The new registry-driven prompt + envelope validator is now primary for every ai-chat request. Frontend dispatcher reads from envelope.category and envelope.intent; the old type-based switch is preserved as fallback for one release cycle so rollback is git revert only. What this fixes (the four named regression bugs): - slug-fabrication: validator rejects slug-shaped fragmentIds - update-verb-with-card-noun misroute: structural rule 3 + validator - create-flow-drift: validator enforces flow next_intents - quoted-value misroute: structural rule 3 + intent registry Live-LLM pass rate: 13/16 = 81% (gate: 75%) Shadow logging unchanged; old prompt/classifier code retained for Stage 3.3 deletion. Rollback: git revert <THIS_SHA>.
The deterministic title-search bypass in index.js was matching the
possessive apostrophe in 'card's' as a quote delimiter, then capturing
the gibberish between the apostrophe and the user's actual quoted value
('s description to say:') as the title query. Mutation requests like
"Change the card's description to say: 'X'" got routed to title search
with that captured gibberish, bypassing Bedrock entirely.
Skip the bypass when the message contains a mutation verb. The LLM +
envelope validator already handle these cases correctly — proven by the
4 regression cases in the eval harness.
Fixes the exact bug the user hit in production today: 'Change the card's
description to say: ...' now routes to update_card (with lastOperation)
or ASK_USER (without). Verified end-to-end against deployed action v0.0.103.
Member
Author
|
no longer needed. Work will be merged in split PRs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MAS Studio AI assistant, product catalog, release-flow enhancements, and the mas-ost monorepo workspace integration.
Resolves https://jira.corp.adobe.com/browse/MWPW-183572
QA Checklist: https://wiki.corp.adobe.com/display/adobedotcom/M@S+Engineering+QA+Use+Cases
What is in this PR
Two commits, both tagged with the Jira ticket:
MWPW-183572: AI chat assistant, product catalog, release flow + IO Runtime— the full feature delivery plus the pre-review audit fixes baked in.MWPW-183572: integrate mas-ost as a monorepo workspace— OST source relocated from tacocat.js/mas-ost into this repo as the 4th npm workspace. Distinct architectural change, separate commit so reviewers can isolate the build/workspace question.Feature commit contents
Studio frontend
mas-chat.js+ friends): Bedrock-backed, persistent sessions viachat-session-manager, operation preview + confirmation gating, destructive-tool allowlist enforced on the client.mas-product-catalog.js/mas-product-detail.js): multi-select filters, surface-scoped editor/settings access (groups.jsintroducesisMasAdmin,canAccessSettings,getUserSurfaces), DRAFT landscape toggle, applied-filter chips.rte/ost.js.services/mcp-client.js: defensive guards on MCP response shapes (publish/get/copy/update contract drifts),AbortControlleron the AI chat fetch, URL-scrubbedlogErrorhelper applied across 14 call sites.utils/ai-card-mapper.js: XSS-safe badge HTML via escape helpers; preserves explicit falsy field values (0,'',false); trim-aware title fallback chain.utils/mas-chat-helpers.js: pure helpers extracted for direct unit testing.markdown-parser.js: LRU-bounded cache to short-circuit repeat renders.IO Runtime — AI chat backend (
io/studio/src/ai-chat/)wrapUntrustedsentinel envelopes. Attached-card fragment IDs wrapped element-wise instead of rawJSON.stringifyto close a breakout vector.bedrock-client,operations-handler,operations-prompt,prompt-templates,response-parser,variant-configs,variant-knowledge-builder,knowledge-client,validation,index.js.IO Runtime — MCP server (
io/mcp-server/)create-release-cards,create-card,create-collection,copy-card) via new helpers inlib/ims-validator.js—deriveSurfaceFromPath,fetchUserGroups,canEditSurface,requireSurfaceAccess. Prevents cross-surface mutation via direct API calls.mas-mcp-server (stdio entry for Claude Code / Cursor)
http-server.js). Droppedcors/expressdeps + thehttpnpm script.Web components
aem-fragment,merch-card-collection, Express pricing variants.mas-ost workspace commit
tacocat.js/mas-ost@f5a71b9: Lit 3 + Spectrum Web Components, Vite build, WTR tests.package.jsonworkspaces. Build script outputs the IIFE bundle and copies to../studio/ost/index.jsin one step.window.ost.openOfferSelectorToolexposed, all 5 custom elements registered, no JS errors at load).Testing
main).studio/test/**covering every new service/util + 2 XSS + 1 prompt-injection repro.io/studio/test/ai-chat/(bedrock-client,operations-handler).io/mcp-server/test/lib/with 23 tests:canEditSurfacematrix,fetchUserGroupsedge cases, fullrequireSurfaceAccessmiddleware integration.OstStore.addOffer.tryBuy). Not regressions from the integration. Tracked as follow-up.Please do the steps below before submitting your PR for a code review or QA
Follow-up (filed as deferred, not PR-blocking)
update-card,delete-card,publish-card,bulk-*). This PR gates path-based creation; id-based mutations currently rely on AEM's own ACLs to scope access.14257-merchatscale-axelremains hardcoded instudio/src/constants.js,studio/src/services/product-api.js, andio/studio/src/ai-chat/knowledge-client.jswith a TODO pointing at the post-merge swap tomasstudioonce that deploy completes.io/knowledge(the AI knowledge service / RAG backend) was intentionally scoped out of this PR. It's a separate Runtime app with its own lifecycle;RAG_ENABLED=falsekeeps the chat fully functional without it.Test URLs:
Screenshots
To be added: OST dialog opened from AI chat, product catalog with multi-select filters, AI chat confirmation summary, release card preview.