Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 5 additions & 87 deletions apps/web/src/myra-workbench.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,13 @@ import { afterEach, describe, expect, test } from "bun:test";

import {
ensureMyraWorkbench,
findMyraWorkbench,
findMyraDefinition,
isMyraWorkbenchId,
isMyraWorkbenchTitle,
MYRA_WORKBENCH_TITLE,
resetMyraWorkbenchCache,
} from "./myra-workbench";
import type { Workbench } from "@corbits/chat-ui";
import type { AgentDefinition } from "./agents-api";

function workbench(partial: {
readonly id: string;
readonly title: string;
readonly kind?: string;
}): Workbench {
return {
id: partial.id,
title: partial.title,
kind: partial.kind ?? "workbench",
pinned: false,
participants: [],
};
}

describe("myra-workbench helpers", () => {
afterEach(() => {
resetMyraWorkbenchCache();
Expand All @@ -39,29 +22,6 @@ describe("myra-workbench helpers", () => {
test("MYRA_WORKBENCH_TITLE is Myra", () => {
expect(MYRA_WORKBENCH_TITLE).toBe("Myra");
});

test("isMyraWorkbenchTitle is case-insensitive and trims", () => {
expect(isMyraWorkbenchTitle("Myra")).toBe(true);
expect(isMyraWorkbenchTitle(" myra ")).toBe(true);
expect(isMyraWorkbenchTitle("MYRA")).toBe(true);
expect(isMyraWorkbenchTitle("Myra chat")).toBe(false);
expect(isMyraWorkbenchTitle("Assistant")).toBe(false);
});

test("findMyraWorkbench returns the first Myra-titled row", () => {
const items = [
workbench({ id: "a", title: "general" }),
workbench({ id: "b", title: "myra" }),
workbench({ id: "c", title: "Myra" }),
];
expect(findMyraWorkbench(items)?.id).toBe("b");
});

test("findMyraWorkbench returns undefined when none match", () => {
expect(
findMyraWorkbench([workbench({ id: "a", title: "general" })]),
).toBeUndefined();
});
});

function definition(partial: {
Expand Down Expand Up @@ -122,14 +82,8 @@ describe("ensureMyraWorkbench", () => {
headers: { "content-type": "application/json" },
});

test("creates a chat with Myra's definitionId when no Myra row exists", async () => {
test("lands on Myra by issuing exactly the definition-keyed create, with no list-by-title lookup", async () => {
const calls = stubFetch((path) => {
if (path.endsWith("/chat/workbenches?kind=workbench")) {
return json({ items: [] });
}
if (path.endsWith("/chat/workbenches?kind=chat")) {
return json({ items: [] });
}
if (path.includes("/workflows/definitions")) {
return json({
data: [definition({ id: "def-assistant", name: "assistant" })],
Expand All @@ -151,6 +105,10 @@ describe("ensureMyraWorkbench", () => {
const result = await ensureMyraWorkbench("tnt_1");

expect(result).toEqual({ kind: "ready", workbenchId: "chat-1" });
expect(calls.some((call) => call.path.includes("kind=workbench"))).toBe(
false,
);
expect(calls.some((call) => call.path.includes("kind=chat"))).toBe(false);
const createCall = calls.find((call) =>
call.path.endsWith("/chat/workbenches"),
);
Expand All @@ -164,48 +122,8 @@ describe("ensureMyraWorkbench", () => {
expect(isMyraWorkbenchId("chat-1")).toBe(true);
});

test("converts a legacy workbench-kind Myra row carrying the agent into an auto-responding chat", async () => {
const legacyWire = {
id: "legacy-1",
title: "Myra",
kind: "workbench",
pinned: true,
participants: [{ address: "myra@wf_1.tnt_1", handle: "myra" }],
};
const calls = stubFetch((path) => {
if (path.endsWith("/chat/workbenches?kind=workbench")) {
return json({ items: [legacyWire] });
}
if (path.endsWith("/chat/workbenches?kind=chat")) {
return json({ items: [] });
}
if (path.endsWith("/chat/workbenches/legacy-1/settings")) {
return json({
...legacyWire,
kind: "chat",
settings: { "chat/kind": "chat" },
contextWindow: { value: 50, source: "inherit" },
});
}
throw new Error(`unexpected fetch: ${path}`);
});

const result = await ensureMyraWorkbench("tnt_1");

expect(result).toEqual({ kind: "ready", workbenchId: "legacy-1" });
expect(isMyraWorkbenchId("legacy-1")).toBe(true);
const patchCall = calls.find((call) => call.init?.method === "PATCH");
expect(JSON.parse(String(patchCall?.init?.body))).toEqual({
"chat/kind": "chat",
});
});

test("errors when no Myra definition is deployed for the tenant", async () => {
stubFetch((path) => {
if (path.endsWith("/chat/workbenches?kind=workbench"))
return json({ items: [] });
if (path.endsWith("/chat/workbenches?kind=chat"))
return json({ items: [] });
if (path.includes("/workflows/definitions")) {
return json({ data: [], nextCursor: null });
}
Expand Down
21 changes: 3 additions & 18 deletions apps/web/src/myra-workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@

import {
createDefaultAgentWorkbench,
findWorkbenchByTitle,
findDefinitionByAssetName,
isWorkbenchTitleMatch,
type Workbench,
} from "@corbits/chat-ui";
import { WORKFLOW_CATALOG } from "@workbench/templates";

Expand All @@ -31,10 +28,6 @@ const myraWorkbench = createDefaultAgentWorkbench({
assetName: MYRA_ASSET_NAME,
});

export function isMyraWorkbenchTitle(title: string): boolean {
return isWorkbenchTitleMatch(title, MYRA_WORKBENCH_TITLE);
}

/** The last workbench id `ensureMyraWorkbench` resolved to, for the shell's
* col2-wide derivation (CL-5936): "Myra is the active surface" reduces to
* "the open workbench is the one Talk-to-Myra last landed us on". */
Expand All @@ -47,13 +40,6 @@ export function resetMyraWorkbenchCache(): void {
myraWorkbench.resetCache();
}

/** Prefer an exact Myra title; first match wins across the given list. */
export function findMyraWorkbench(
workbenches: readonly Workbench[],
): Workbench | undefined {
return findWorkbenchByTitle(workbenches, MYRA_WORKBENCH_TITLE);
}

/** Myra's deployed agent definition, matched by the seeded `assistant`
* asset name — never by display name, which is a UI label, not a wire
* identifier. */
Expand All @@ -64,10 +50,9 @@ export function findMyraDefinition(
}

/**
* List workbench + chat kinds, reuse a Myra-titled row if one exists — a
* legacy workbench-kind Myra from a bench seeded before CL-5985 included, so
* no bench ever ends up with two — otherwise create a 1:1 chat against
* Myra's deployed agent definition.
* Create (or reopen) the 1:1 chat against Myra's deployed agent definition —
* the server's `definitionId` dedupe on `POST /workbenches` is the one
* find-or-reopen path; this call always issues that create and trusts it.
*/
export function ensureMyraWorkbench(tenantId: string) {
return myraWorkbench.ensure(tenantId, listAgentDefinitions);
Expand Down
Loading
Loading