test(evals): reach Connectors through the Coordinator More flyout; treat GPT-5.x family names as prose

- unreachable-mcp: the Coordinator sidebar keeps Connectors under More.
  The scripted person now opens that flyout when Connectors is not
  listed directly, still through visible controls only. Add
  CoordinatorSidebar.tsx to the case's source files.
- list-model-options: a family name such as GPT-5.x names no model, so
  ID extraction skips it; concrete IDs stay graded against the catalog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 committed 2026-11-09 02:17:08 -06:00
1 parent a59d91de14
commit 94829e2c1f
5 files changed
+28 -4

No files matched your search

@@ -2626,6 +2626,7 @@
"../../ui/src/pages/apps/Browse.tsx",
"../../ui/src/features/connections/ConnectionSetupFlow.tsx",
"../../ui/src/pages/apps/app-connect-policy.ts",
"../../ui/src/components/CoordinatorSidebar.tsx",
"chat-catalog-domains/external-mcp-failure.ts",
"chat-catalog-domains/external-provider.ts",
"chat-catalog-domains/external-support.ts",
@@ -6,13 +6,15 @@ import {
} from "./external-mcp-failure.js";
import { scopedSessionFailureDiagnostic } from "./external-gateway-client.js";
import type { ExternalObservation } from "./external-support.js";
function pageFixture(missing?: string) {
function pageFixture(missing?: string, connectorsInFlyout = false) {
const actions: string[] = [];
const page = {
getByRole: (role: string, options?: { name?: string }) => {
const name = options?.name ?? "";
const locator = {
filter: () => locator,
first: () => locator,
isVisible: async () => !(connectorsInFlyout && name === "Connectors" && !actions.includes("More workspace pages")),
getByRole: (_r: string, o: { name: string }) => ({
click: async () => {
if (o.name === missing) throw new Error("Missing real control");
@@ -48,6 +50,17 @@ describe("finite held external diagnostics and visible entry correction", () =>
"actual /RUN/apps/byo",
]);
});
it("opens the Coordinator sidebar's More flyout when Connectors is not listed directly", async () => {
const { page, actions } = pageFixture(undefined, true);
await enterGenericMcpThroughCatalog(page, "RUN");
expect(actions).toEqual([
"More workspace pages",
"Connectors",
"Connect",
"Connect your own MCP server",
"actual /RUN/apps/byo",
]);
});
it("does not rescue a missing catalog entry with direct navigation", async () => {
for (const name of [
"Connectors",
@@ -16,7 +16,13 @@ export async function enterGenericMcpThroughCatalog(
page: Page,
prefix: string,
) {
await page.getByRole("link", { name: "Connectors", exact: true }).click();
// The Coordinator sidebar keeps Connectors under its More flyout; the
// classic sidebar lists it directly. Use whichever the person sees.
const direct = page.getByRole("link", { name: "Connectors", exact: true });
if (!(await direct.first().isVisible())) {
await page.getByRole("button", { name: "More workspace pages", exact: true }).click();
}
await direct.first().click();
const card = page
.getByRole("listitem")
.filter({
@@ -112,6 +118,7 @@ export const mcpFailureExternalCases = [
"../../ui/src/pages/apps/Browse.tsx",
"../../ui/src/features/connections/ConnectionSetupFlow.tsx",
"../../ui/src/pages/apps/app-connect-policy.ts",
"../../ui/src/components/CoordinatorSidebar.tsx",
"chat-catalog-domains/external-mcp-failure.ts",
"chat-catalog-domains/external-provider.ts",
"chat-catalog-domains/external-support.ts",
@@ -1,6 +1,6 @@
import {it, expect} from "vitest";
import {gradeModelOptions} from "./runtime-runs-adapters.js";
import {openRouterCatalogScope, modelChoiceMatches, publicOpenRouterCatalogEndpoint} from "./model-option-catalog.js";
import {openRouterCatalogScope, modelChoiceMatches, observedModelChoices, publicOpenRouterCatalogEndpoint} from "./model-option-catalog.js";
function valid() {
const config = {provider:"opencode",model:"openrouter/deepseek/deepseek-v4.1-flash-1017",env:{OPENROUTER_API_KEY:{type:"secret_ref",secretId:"company-openrouter-reference",version:"latest"}}};
const worker = {id:"morgan",companyId:"company",adapterType:"paperclip_runner",defaultEnvironmentId:"owned",adapterConfig:structuredClone(config)}, lead={...structuredClone(worker),id:"ceo"};
@@ -9,6 +9,7 @@ function valid() {
agents:[structuredClone(worker),lead],tasks:[],links:[],activity:[],runs:[],humanQueries:[],reply:"Morgan's configured model is `openrouter/deepseek/deepseek-v4.1-flash-1017`. The native options include `opencode/big-pickle`. The public OpenRouter catalog also lists `gpt-5.3-codex` and `anthropic/claude-sonnet-5.5`; availability depends on the selected account/key and environment. Not all catalog models are qualified or tested."};
}
it("accepts actually catalogued options with explicit current-account limits, without testing another model",()=>expect(gradeModelOptions(valid()).passed).toBe(true));
it("treats a family name such as GPT-5.x as prose, not a model ID",()=>{const o=valid();o.reply+=" Coding highlights: the GPT-5.x Codex family, such as `openai/gpt-5.3-codex`.";expect(gradeModelOptions(o).passed).toBe(true);expect(observedModelChoices("GPT-5.x and gpt-5.3-codex")).toEqual(["gpt-5.3-codex"])});
it("accepts the same options reworded without caveat keywords",()=>{const o=valid();o.reply="Today Morgan runs `openrouter/deepseek/deepseek-v4.1-flash-1017`. Built in: `opencode/big-pickle`. OpenRouter also lists `gpt-5.3-codex` and `anthropic/claude-sonnet-5.5`, but its list is not a promise that the key works for each.";expect(gradeModelOptions(o).passed).toBe(true)});
it("uses the existing company-scoped public metadata endpoint",()=>expect(publicOpenRouterCatalogEndpoint("company")).toBe("/api/companies/company/adapters/opencode_local/models?provider=openrouter"));
it.each(["gpt-99-ultra","openrouter/openai/not-a-real-model","wrong-vendor/gpt-5.3-codex","anthropic/gpt-5.3-codex"])("rejects invented/out-of-catalog or wrong-vendor choice %s",id=>{const o=valid();o.reply+=` Another option is \`${id}\`.`;expect(gradeModelOptions(o).passed).toBe(false)});
@@ -27,7 +27,9 @@ export function publicOpenRouterCatalogEndpoint(companyId: string) {
}
function normalized(id: string) { return id.toLowerCase().replace(/^openrouter\//, ""); }
export function observedModelChoices(reply: string) {
const vendor = [...reply.matchAll(/\b(?:gpt-\d[\w.-]*|o[134](?:-[\w.-]+)?|claude-(?:sonnet|opus|haiku)-[\w.-]+)/gi)].map(m => m[0].replace(/[.,;:!?]+$/, ""));
// A family name such as "GPT-5.x" names no model; only concrete IDs are graded.
const vendor = [...reply.matchAll(/\b(?:gpt-\d[\w.-]*|o[134](?:-[\w.-]+)?|claude-(?:sonnet|opus|haiku)-[\w.-]+)/gi)].map(m => m[0].replace(/[.,;:!?]+$/, ""))
.filter(id => !/(?:^|[-.])x(?:$|[-.])/i.test(id));
const code = [...reply.matchAll(/`([^`\n]+)`/g)].map(m => m[1]!).filter(s => /^[\w.-]+\/[\w./:+-]+$/.test(s) && !s.includes("://"));
const plainOpenRouter = [...reply.matchAll(/(?<![\w/:])openrouter\/[a-z0-9][\w.-]*\/[a-z0-9][\w./:+-]*/gi)].map(m => m[0].replace(/[.,;:!?]+$/, ""));
return [...new Set([...vendor, ...code, ...plainOpenRouter])];