From 63a47ca46ae08fb0c3c3d3f48772d005c8ed4536 Mon Sep 17 00:00:00 2001 From: Shreyas Sarve Date: Mon, 19 Oct 2026 14:06:40 +0530 Subject: [PATCH] feat(ask-ai): offer a11y as a product again Reverses abfddf65, which dropped a11y from askBrowserStackAI while Ask AI was in limited alpha. Atlas has served a11y throughout; only the tool stopped offering it. The same four places move back together: * `PRODUCTS` in types.ts is `["tm", "a11y", "tra"]`. * the `product` describe text in register.ts names a11y (Accessibility). * `NEEDS_A11Y_SCAN_ID` and `NEEDS_A11Y_CONFIG_ID` in tool-handoff.ts point at askBrowserStackAI with product "a11y" again, keeping "start a new scan" / "create one" as the fallback, since Ask AI is still entitled per account and the agent route can be refused. The test that pinned the two-product enum now pins the three-product one, checks the describe text names every product, and checks that any product a handoff names is one the schema accepts, so dropping a product without moving its handoffs fails a test instead of shipping an instruction to make a rejected call. npm run build, tsc --noEmit and eslint are clean; 84 files / 1299 tests pass. Co-Authored-By: Claude Opus 5.5 --- src/tools/ask-browserstack/register.ts | 2 +- src/tools/ask-browserstack/types.ts | 9 ++++----- src/tools/tool-handoff.ts | 7 ++++--- tests/tools/askBrowserstackE2E.test.ts | 24 +++++++++++++++--------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/tools/ask-browserstack/register.ts b/src/tools/ask-browserstack/register.ts index 380a4c4c..85f06389 100644 --- a/src/tools/ask-browserstack/register.ts +++ b/src/tools/ask-browserstack/register.ts @@ -551,7 +551,7 @@ export function addAskBrowserStackAITool( product: z .enum(PRODUCTS) .describe( - "Which product to work in: tm (Test Management), " + + "Which product to work in: tm (Test Management), a11y (Accessibility), " + "tra (Test Reporting & Analytics).", ), // OPTIONAL, because a resume does not carry one. `next_step` tells the model to diff --git a/src/tools/ask-browserstack/types.ts b/src/tools/ask-browserstack/types.ts index b490ae4d..b0b21850 100644 --- a/src/tools/ask-browserstack/types.ts +++ b/src/tools/ask-browserstack/types.ts @@ -7,11 +7,10 @@ * other half will never send. Nothing here changes without changing that document first. */ -// a11y is deliberately ABSENT while Ask AI is in limited alpha. Atlas itself serves the -// product; this tool just does not offer it yet. Re-adding it means this list, the `product` -// describe text in register.ts, and the two a11y handoffs in tool-handoff.ts — which stop -// pointing here precisely because the call would now be rejected. -export const PRODUCTS = ["tm", "tra"] as const; +// Dropping a product means this list, the `product` describe text in register.ts, and any +// handoff in tool-handoff.ts that sends the model here with that product — the two a11y ones +// do — or those handoffs become instructions to make a call the schema rejects. +export const PRODUCTS = ["tm", "a11y", "tra"] as const; export type Product = (typeof PRODUCTS)[number]; /** CONTRACT §2 — what Atlas emits on the run's stream when its gate needs a human. */ diff --git a/src/tools/tool-handoff.ts b/src/tools/tool-handoff.ts index ce41a2b3..53c5adff 100644 --- a/src/tools/tool-handoff.ts +++ b/src/tools/tool-handoff.ts @@ -91,13 +91,14 @@ export const NEEDS_SESSION_ID = /** A completed scan's ids come from startAccessibilityScan, or from the agent. */ export const NEEDS_A11Y_SCAN_ID = " Requires the ids of a completed scan, which are returned by startAccessibilityScan. " + - "No tool here lists past scans, so if you do not have the ids, start a new scan rather " + - "than guessing."; + "No tool here lists past scans; for one run earlier, call askBrowserStackAI with product " + + '"a11y" to locate it, or start a new scan rather than guessing.'; /** Auth-config ids are not listable by any tool here. */ export const NEEDS_A11Y_CONFIG_ID = " Requires the numeric id returned by createAccessibilityAuthConfig. No tool here lists " + - "existing configurations, so if you do not have the id, create one rather than guessing."; + "existing configurations, so if you do not have the id, call askBrowserStackAI with " + + 'product "a11y" to find it, or create one rather than guessing.'; /** Test ids come from listTestIds, which itself needs a build id. */ export const NEEDS_TEST_IDS = needsIdFrom("test ids", "listTestIds"); diff --git a/tests/tools/askBrowserstackE2E.test.ts b/tests/tools/askBrowserstackE2E.test.ts index 19969038..9c49e44f 100644 --- a/tests/tools/askBrowserstackE2E.test.ts +++ b/tests/tools/askBrowserstackE2E.test.ts @@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AskError } from "../../src/tools/ask-browserstack/config.js"; import { resetTokenCache } from "../../src/tools/ask-browserstack/central-oauth.js"; import { addAskBrowserStackAITool } from "../../src/tools/ask-browserstack/register.js"; +import { NEEDS_A11Y_CONFIG_ID, NEEDS_A11Y_SCAN_ID } from "../../src/tools/tool-handoff.js"; // `apiClient` adapted onto the global `fetch` stub these tests already install. // @@ -294,20 +295,25 @@ describe("askBrowserStackAI, end to end through the server factory", () => { expect(description).toMatch(/One task per call/); }); - it("offers tm and tra only — a11y is not on the tool while it is in alpha", async () => { - // Atlas serves a11y; this TOOL does not offer it yet. Pinned because dropping a - // product is a one-word edit that four other places have to agree with, and the two - // a11y handoffs in tool-handoff.ts used to send the model here with product "a11y" — - // which the schema now rejects, so a silent re-add would strand them again. + it("offers tm, a11y and tra, and every handoff that names a product names one of them", async () => { + // Pinned because changing a product is a one-word edit that other places have to agree + // with: the `product` describe text, and the a11y handoffs in tool-handoff.ts, which + // send the model here with product "a11y". Drop a11y from the enum without moving those + // and they become instructions to make a call the schema rejects. const server = await buildServer(); const shape = (server.getTools().askBrowserStackAI as any).inputSchema?.shape; const values = shape?.product?._def?.values ?? shape?.product?.options; - expect(values).toEqual(["tm", "tra"]); - expect(values).not.toContain("a11y"); + expect(values).toEqual(["tm", "a11y", "tra"]); - const description = (server.getTools().askBrowserStackAI as any).description as string; - expect(description).not.toMatch(/a11y/); + const productText = shape?.product?.description as string; + for (const value of values) expect(productText).toContain(`${value} (`); + + for (const handoff of [NEEDS_A11Y_SCAN_ID, NEEDS_A11Y_CONFIG_ID]) { + const named = handoff.match(/askBrowserStackAI with product "([^"]+)"/)?.[1]; + expect(named).toBeDefined(); + expect(values).toContain(named); + } }); it("relays an ask, approves it, and forwards the caller's own credentials", async () => {