From aca855dfef8e6675dd4ae2a22bbe935e869e71fd Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 21 Aug 2026 00:31:52 -0700 Subject: [PATCH] CL-6481: stop mention fan-out test from polluting agent-chat reuse dedup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "mention fan-out drives the mentioned run" was rewritten yesterday (80101ecc8) to mint its own kind:"chat" echo conversation so it could exercise a real resident participant. That conversation is indistinguishable from any other echo chat to findExistingAgentChat's tenant-wide dedup, and — being older — became the row it always resolves reuseExisting to (packages/chat/src/routes.ts's documented oldest-wins tie-break), so the later "kind filter ... reuses it" test got this stale chat back instead of its own. Live e2e run with findExistingAgentChat/sameAgent instrumented confirmed both candidate rows matched genuinely and deterministically on definitionId equality; the only difference was creation order. The mention test only needs a resident participant to mention, not a persisted "chat" conversation, so it now invites echo into a dedicated kind:"workbench" room instead — outside the dedup's kind:"chat" listing entirely. Fixes CL-6481 --- scripts/e2e/chat.test.ts | 62 +++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/scripts/e2e/chat.test.ts b/scripts/e2e/chat.test.ts index 9e1a7482f..94010743f 100644 --- a/scripts/e2e/chat.test.ts +++ b/scripts/e2e/chat.test.ts @@ -590,33 +590,43 @@ describe.skipIf(databaseUrl === undefined)("chat e2e", () => { // contract this test was actually meant to guard: mentioning a // participant already resident in the room drives that participant's // existing run — twice, never minting a sibling. + // A dedicated `kind: "workbench"` room, never `kind: "chat"`: an agent + // chat this test minted for itself would sit in the tenant forever as + // a second, older "echo" conversation, and `findExistingAgentChat`'s + // tenant-wide dedup (below) would then find *this* leftover instead + // of the one the "reuses it" test just created and expects back + // (CL-6481 — this collision, introduced when this test was rewritten + // around a real resident agent, was the reuse test's actual failure). + // A plain workbench never enters that dedup's `kind: "chat"` listing, + // so it can host a resident participant to mention without leaving + // that trap behind. test("mention fan-out drives the mentioned run", async () => { - const mentioned = await createWorkbench({ - kind: "chat", - definitionId: await echoDefinitionId(), + const mentionRoom = await createWorkbench({ + kind: "workbench", + name: "mention fan-out room", }); - expectStatus("create mentioned agent chat", mentioned, 201); - const mentionedWorkbenchId = stringField( - mentioned.data, + expectStatus("create mention fan-out room", mentionRoom, 201); + const mentionRoomId = stringField( + mentionRoom.data, "id", - "create mentioned agent chat", + "create mention fan-out room", ); - const mentionedParticipants = arrayField( - mentioned.data, - "participants", - "create mentioned agent chat", - ) as { address: string; handle: string }[]; - const echoParticipant = mentionedParticipants.find( - (participant) => participant.handle === "echo", + + const invited = await api( + "POST", + `/api/tenants/${tenantId}/chat/workbenches/${mentionRoomId}/invite`, + { definitionId: await echoDefinitionId() }, + user1.cookies, ); - if (echoParticipant === undefined) { - throw new Error( - `agent chat has no "echo" participant: ${JSON.stringify(mentionedParticipants)}`, - ); - } - const echoLocalPart = echoParticipant.address.split("@")[0]; + expectStatus("invite echo into mention fan-out room", invited, 201); + const echoAddress = stringField( + invited.data, + "address", + "invite echo into mention fan-out room", + ); + const echoLocalPart = echoAddress.split("@")[0]; if (echoLocalPart === undefined || echoLocalPart === "") { - throw new Error(`malformed echo address: ${echoParticipant.address}`); + throw new Error(`malformed echo address: ${echoAddress}`); } const beforeFirst = highestSeq( @@ -624,7 +634,7 @@ describe.skipIf(databaseUrl === undefined)("chat e2e", () => { ); await postMessage( user1.cookies, - mentionedWorkbenchId, + mentionRoomId, `hey @echo take a look ${crypto.randomUUID()}`, ); const afterFirst = await waitForRunProgress( @@ -644,7 +654,7 @@ describe.skipIf(databaseUrl === undefined)("chat e2e", () => { ); await postMessage( user1.cookies, - mentionedWorkbenchId, + mentionRoomId, `hey @echo one more thing ${crypto.randomUUID()}`, ); const afterSecond = await waitForRunProgress( @@ -656,7 +666,7 @@ describe.skipIf(databaseUrl === undefined)("chat e2e", () => { const settingsAfterSecondMention = await api( "GET", - `/api/tenants/${tenantId}/chat/workbenches/${mentionedWorkbenchId}/settings`, + `/api/tenants/${tenantId}/chat/workbenches/${mentionRoomId}/settings`, undefined, user1.cookies, ); @@ -671,9 +681,7 @@ describe.skipIf(databaseUrl === undefined)("chat e2e", () => { "get settings after second mention", ) as { address: string; handle: string }[]; expect(participantsAfterSecondMention).toHaveLength(1); - expect(participantsAfterSecondMention[0]?.address).toBe( - echoParticipant.address, - ); + expect(participantsAfterSecondMention[0]?.address).toBe(echoAddress); }, 90_000); test("inviting the echo agent launches its own run, joins the workbench, and receives @mentions", async () => {